diff --git a/app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt b/app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt index 5336408..bf36315 100644 --- a/app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt +++ b/app/src/main/java/com/ubisam/ubgitea/feature/repositories/RepositoryListScreen.kt @@ -51,6 +51,7 @@ import com.ubisam.ubgitea.R import com.ubisam.ubgitea.domain.organizations.OrganizationSummary import com.ubisam.ubgitea.domain.model.RepositoryCoordinates import com.ubisam.ubgitea.ui.SimplePaginationFooter +import com.ubisam.ubgitea.ui.components.UBGiteaTopBar import com.ubisam.ubgitea.ui.formatInstant @Composable @@ -77,7 +78,10 @@ fun RepositoryListScreen( onViewSettings: () -> Unit, modifier: Modifier = Modifier, ) { - Scaffold(modifier = modifier.fillMaxSize()) { padding -> + Scaffold( + modifier = modifier.fillMaxSize(), + topBar = { UBGiteaTopBar() }, + ) { padding -> when (state) { RepositoryListUiState.SignedOut -> MessageState( stringResource(R.string.repositories_sign_in_description), diff --git a/app/src/main/java/com/ubisam/ubgitea/ui/components/UBGiteaTopBar.kt b/app/src/main/java/com/ubisam/ubgitea/ui/components/UBGiteaTopBar.kt new file mode 100644 index 0000000..0dde5c0 --- /dev/null +++ b/app/src/main/java/com/ubisam/ubgitea/ui/components/UBGiteaTopBar.kt @@ -0,0 +1,110 @@ +package com.ubisam.ubgitea.ui.components + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.ubisam.ubgitea.ui.theme.UbiSamLogoBlue +import com.ubisam.ubgitea.ui.theme.UbiSamLogoGray + +/** + * App-wide header: the UbiSam wordmark, small, top-left, on every top-level screen. Nested screens + * (settings, notifications, branch detail, ...) keep the app's existing system-back-gesture + * navigation as-is -- this bar is deliberately just a branding element, not a navigation bar, so it + * carries no back button and doesn't need to know about any screen's back stack. + */ +@Composable +fun UBGiteaTopBar(modifier: Modifier = Modifier, actions: @Composable RowScope.() -> Unit = {}) { + Surface(color = MaterialTheme.colorScheme.surface, modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + UBGiteaLogo() + Spacer(Modifier.weight(1f)) + actions() + } + } +} + +@Composable +fun UBGiteaLogo(modifier: Modifier = Modifier) { + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Ubi", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + color = UbiSamLogoGray, + ) + Text( + text = "Sam", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + color = UbiSamLogoBlue, + ) + Spacer(Modifier.width(5.dp)) + Box( + modifier = Modifier + .size(18.dp) + .clip(RoundedCornerShape(6.dp)) + .background(UbiSamLogoBlue), + contentAlignment = Alignment.Center, + ) { + SignalMark(color = Color.White, modifier = Modifier.size(11.dp)) + } + } +} + +// Stand-in for the real logo's signal/RSS mark, drawn rather than imported so the redesign needs +// no new image asset: a dot plus two quarter-arcs, all centered on the bottom-left corner of the +// box, matching the mark in the real UbiSam wordmark. Swap for an actual drawable resource if/when +// the source image file is available. +@Composable +private fun SignalMark(color: Color, modifier: Modifier = Modifier) { + Canvas(modifier = modifier) { + val corner = Offset(0f, size.height) + val stroke = size.minDimension * 0.22f + drawCircle( + color = color, + radius = size.minDimension * 0.14f, + center = Offset(size.minDimension * 0.14f, size.height - size.minDimension * 0.14f), + ) + listOf(0.55f, 0.9f).forEach { fraction -> + val radius = size.minDimension * fraction + drawArc( + color = color, + startAngle = -90f, + sweepAngle = 90f, + useCenter = false, + topLeft = Offset(corner.x - radius, corner.y - radius), + size = Size(radius * 2f, radius * 2f), + style = Stroke(width = stroke, cap = StrokeCap.Round), + ) + } + } +} diff --git a/app/src/main/java/com/ubisam/ubgitea/ui/theme/Color.kt b/app/src/main/java/com/ubisam/ubgitea/ui/theme/Color.kt index 9b0d25d..328920b 100644 --- a/app/src/main/java/com/ubisam/ubgitea/ui/theme/Color.kt +++ b/app/src/main/java/com/ubisam/ubgitea/ui/theme/Color.kt @@ -2,59 +2,63 @@ package com.ubisam.ubgitea.ui.theme import androidx.compose.ui.graphics.Color -// Gitea's own logo/brand green is #609926 (verified against Gitea's source: -// web_src/css/themes/theme-gitea-light.css's --color-logo). The Material3 primary role below is a -// deeper tone of that same hue rather than the exact logo value, purely for contrast: white text -// on #609926 sits around a 3.4:1 contrast ratio, under WCAG's 4.5:1 threshold for normal-size -// button labels. Every other color here is a hand-built, standard-shaped Material3 tonal -// palette (primary/secondary/tertiary, each with a container + on-* pair, plus neutrals) grown -// from that same green family, so the whole app reads as one deliberate "Gitea green" theme -// instead of Compose's unthemed default (Material3's baseline purple, which is what every screen -// in this app looked like before this file existed with real values). +// Sky-blue + white redesign (superseding the earlier "Gitea green" theme -- see git history for +// that palette if it's ever needed again). Primary is a deep-enough sky blue (Tailwind's sky-700 +// family) that white text on it clears WCAG's 4.5:1 threshold for normal-size button labels, while +// still reading unmistakably as "sky blue" rather than navy. Background is a near-white with a +// faint blue cast rather than stark #FFFFFF, so white cards/surfaces still have visible separation +// from the page. Every other role here is a hand-built, standard-shaped Material3 tonal palette +// (primary/secondary/tertiary, each with a container + on-* pair, plus neutrals) grown from that +// same blue family, so the whole app reads as one deliberate sky-blue theme. // Light theme -val GreenPrimaryLight = Color(0xFF3B6A1F) -val OnGreenPrimaryLight = Color(0xFFFFFFFF) -val GreenPrimaryContainerLight = Color(0xFFBCEF8B) -val OnGreenPrimaryContainerLight = Color(0xFF0C2000) -val SageSecondaryLight = Color(0xFF55624A) -val OnSageSecondaryLight = Color(0xFFFFFFFF) -val SageSecondaryContainerLight = Color(0xFFD9E7C8) -val OnSageSecondaryContainerLight = Color(0xFF131F0B) -val TealTertiaryLight = Color(0xFF386661) -val OnTealTertiaryLight = Color(0xFFFFFFFF) -val TealTertiaryContainerLight = Color(0xFFBBECE5) -val OnTealTertiaryContainerLight = Color(0xFF00201D) -val BackgroundLight = Color(0xFFFCFDF6) -val OnBackgroundLight = Color(0xFF1A1C16) -val SurfaceVariantLight = Color(0xFFDFE4D3) -val OnSurfaceVariantLight = Color(0xFF43483C) -val OutlineLight = Color(0xFF73796B) -val OutlineVariantLight = Color(0xFFC3C8B8) -val InverseSurfaceLight = Color(0xFF2F312A) -val InverseOnSurfaceLight = Color(0xFFF1F1E9) -val InversePrimaryLight = Color(0xFFA1D474) +val SkyPrimaryLight = Color(0xFF0369A1) +val OnSkyPrimaryLight = Color(0xFFFFFFFF) +val SkyPrimaryContainerLight = Color(0xFFBAE6FD) +val OnSkyPrimaryContainerLight = Color(0xFF082F49) +val SlateSecondaryLight = Color(0xFF4C6B82) +val OnSlateSecondaryLight = Color(0xFFFFFFFF) +val SlateSecondaryContainerLight = Color(0xFFD3E4F0) +val OnSlateSecondaryContainerLight = Color(0xFF0F2A3A) +val CyanTertiaryLight = Color(0xFF006874) +val OnCyanTertiaryLight = Color(0xFFFFFFFF) +val CyanTertiaryContainerLight = Color(0xFFA6EEF2) +val OnCyanTertiaryContainerLight = Color(0xFF001F24) +val BackgroundLight = Color(0xFFF7FBFF) +val OnBackgroundLight = Color(0xFF10202B) +val SurfaceVariantLight = Color(0xFFDCE8EF) +val OnSurfaceVariantLight = Color(0xFF41505A) +val OutlineLight = Color(0xFF71818C) +val OutlineVariantLight = Color(0xFFC0D0D9) +val InverseSurfaceLight = Color(0xFF1D333D) +val InverseOnSurfaceLight = Color(0xFFECF3F8) +val InversePrimaryLight = Color(0xFF8ED0FA) -// Dark theme — tones flip per Material3 convention (lighter primary for legibility on a dark +// Dark theme -- tones flip per Material3 convention (lighter primary for legibility on a dark // surface; the light theme's primary becomes the dark theme's inversePrimary, and vice versa). -val GreenPrimaryDark = Color(0xFFA1D474) -val OnGreenPrimaryDark = Color(0xFF1B3700) -val GreenPrimaryContainerDark = Color(0xFF2C4F0A) -val OnGreenPrimaryContainerDark = Color(0xFFBCEF8B) -val SageSecondaryDark = Color(0xFFBDCBAB) -val OnSageSecondaryDark = Color(0xFF28331E) -val SageSecondaryContainerDark = Color(0xFF3E4A32) -val OnSageSecondaryContainerDark = Color(0xFFD9E7C8) -val TealTertiaryDark = Color(0xFFA0D0C9) -val OnTealTertiaryDark = Color(0xFF003733) -val TealTertiaryContainerDark = Color(0xFF1E4E49) -val OnTealTertiaryContainerDark = Color(0xFFBBECE5) -val BackgroundDark = Color(0xFF12140F) -val OnBackgroundDark = Color(0xFFE2E3D9) -val SurfaceVariantDark = Color(0xFF43483C) -val OnSurfaceVariantDark = Color(0xFFC3C8B8) -val OutlineDark = Color(0xFF8D9285) -val OutlineVariantDark = Color(0xFF43483C) -val InverseSurfaceDark = Color(0xFFE2E3D9) -val InverseOnSurfaceDark = Color(0xFF2F312A) -val InversePrimaryDark = Color(0xFF3B6A1F) +val SkyPrimaryDark = Color(0xFF8ED0FA) +val OnSkyPrimaryDark = Color(0xFF00344D) +val SkyPrimaryContainerDark = Color(0xFF024C6E) +val OnSkyPrimaryContainerDark = Color(0xFFBAE6FD) +val SlateSecondaryDark = Color(0xFFB4CADD) +val OnSlateSecondaryDark = Color(0xFF1C3444) +val SlateSecondaryContainerDark = Color(0xFF34495C) +val OnSlateSecondaryContainerDark = Color(0xFFD3E4F0) +val CyanTertiaryDark = Color(0xFF83D2DB) +val OnCyanTertiaryDark = Color(0xFF00363D) +val CyanTertiaryContainerDark = Color(0xFF004F58) +val OnCyanTertiaryContainerDark = Color(0xFFA6EEF2) +val BackgroundDark = Color(0xFF0B141B) +val OnBackgroundDark = Color(0xFFE3EDF3) +val SurfaceVariantDark = Color(0xFF41505A) +val OnSurfaceVariantDark = Color(0xFFC0D0D9) +val OutlineDark = Color(0xFF8B9AA4) +val OutlineVariantDark = Color(0xFF41505A) +val InverseSurfaceDark = Color(0xFFE3EDF3) +val InverseOnSurfaceDark = Color(0xFF1D333D) +val InversePrimaryDark = Color(0xFF0369A1) + +// UbiSam wordmark colors -- fixed brand colors independent of light/dark theme (a logo should look +// the same in both modes, unlike the tonal palette above). +val UbiSamLogoGray = Color(0xFF5B6472) +val UbiSamLogoBlue = Color(0xFF0EA5E9) diff --git a/app/src/main/java/com/ubisam/ubgitea/ui/theme/Theme.kt b/app/src/main/java/com/ubisam/ubgitea/ui/theme/Theme.kt index d51031b..434609c 100644 --- a/app/src/main/java/com/ubisam/ubgitea/ui/theme/Theme.kt +++ b/app/src/main/java/com/ubisam/ubgitea/ui/theme/Theme.kt @@ -6,25 +6,25 @@ import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable -// Gitea-green Material3 color schemes (see Color.kt for the individual values and how they were -// derived from Gitea's actual logo green). Every role not set explicitly here — error/onError/ -// errorContainer/onErrorContainer, most notably, kept as Material3's own baseline red — is left at -// the corresponding lightColorScheme()/darkColorScheme() default rather than reinvented, since -// nothing about this request was about changing what "error" or "removed" looks like, only about -// branding the app around Gitea's own color. +// Sky-blue Material3 color schemes (see Color.kt for the individual values and how they were +// derived). Every role not set explicitly here -- error/onError/errorContainer/onErrorContainer, +// most notably, kept as Material3's own baseline red -- is left at the corresponding +// lightColorScheme()/darkColorScheme() default rather than reinvented, since nothing about this +// request was about changing what "error" or "removed" looks like, only about the app's own brand +// color. private val LightColorScheme = lightColorScheme( - primary = GreenPrimaryLight, - onPrimary = OnGreenPrimaryLight, - primaryContainer = GreenPrimaryContainerLight, - onPrimaryContainer = OnGreenPrimaryContainerLight, - secondary = SageSecondaryLight, - onSecondary = OnSageSecondaryLight, - secondaryContainer = SageSecondaryContainerLight, - onSecondaryContainer = OnSageSecondaryContainerLight, - tertiary = TealTertiaryLight, - onTertiary = OnTealTertiaryLight, - tertiaryContainer = TealTertiaryContainerLight, - onTertiaryContainer = OnTealTertiaryContainerLight, + primary = SkyPrimaryLight, + onPrimary = OnSkyPrimaryLight, + primaryContainer = SkyPrimaryContainerLight, + onPrimaryContainer = OnSkyPrimaryContainerLight, + secondary = SlateSecondaryLight, + onSecondary = OnSlateSecondaryLight, + secondaryContainer = SlateSecondaryContainerLight, + onSecondaryContainer = OnSlateSecondaryContainerLight, + tertiary = CyanTertiaryLight, + onTertiary = OnCyanTertiaryLight, + tertiaryContainer = CyanTertiaryContainerLight, + onTertiaryContainer = OnCyanTertiaryContainerLight, background = BackgroundLight, onBackground = OnBackgroundLight, surface = BackgroundLight, @@ -39,18 +39,18 @@ private val LightColorScheme = lightColorScheme( ) private val DarkColorScheme = darkColorScheme( - primary = GreenPrimaryDark, - onPrimary = OnGreenPrimaryDark, - primaryContainer = GreenPrimaryContainerDark, - onPrimaryContainer = OnGreenPrimaryContainerDark, - secondary = SageSecondaryDark, - onSecondary = OnSageSecondaryDark, - secondaryContainer = SageSecondaryContainerDark, - onSecondaryContainer = OnSageSecondaryContainerDark, - tertiary = TealTertiaryDark, - onTertiary = OnTealTertiaryDark, - tertiaryContainer = TealTertiaryContainerDark, - onTertiaryContainer = OnTealTertiaryContainerDark, + primary = SkyPrimaryDark, + onPrimary = OnSkyPrimaryDark, + primaryContainer = SkyPrimaryContainerDark, + onPrimaryContainer = OnSkyPrimaryContainerDark, + secondary = SlateSecondaryDark, + onSecondary = OnSlateSecondaryDark, + secondaryContainer = SlateSecondaryContainerDark, + onSecondaryContainer = OnSlateSecondaryContainerDark, + tertiary = CyanTertiaryDark, + onTertiary = OnCyanTertiaryDark, + tertiaryContainer = CyanTertiaryContainerDark, + onTertiaryContainer = OnCyanTertiaryContainerDark, background = BackgroundDark, onBackground = OnBackgroundDark, surface = BackgroundDark, diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 1200e13..b08f002 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,14 +1,14 @@ - + diff --git a/docs/development-log.md b/docs/development-log.md index 7ebe4fa..5fc02ca 100644 --- a/docs/development-log.md +++ b/docs/development-log.md @@ -1012,3 +1012,51 @@ This is the append-only implementation and handoff record shared by Codex and Cl whether biometric reauthentication is required — is a product decision for the human product owner, not something to build unprompted. No further design-review gaps currently queued. - Next owner: Claude, continuing per the standing direction. + +## 2026-09-08 — TASK-045: Sky blue + white redesign, UbiSam logo + +- Agent and role: Claude, feature implementer, at the human product owner's explicit request to + overhaul the app's visual design ("현재 UI/UX를 전체적으로 갈아엎고싶다... 하늘색과 하얀색으로 + 구성하고 싶어"), with a small UbiSam logo top-left. This revises TASK-039's "Gitea green" visual + identity decision rather than starting from an unset one. +- Commit/diff: working tree in the main checkout (`D:/Android/UBGitea`, branch + `claude/task-015-notifications-main`), not yet committed; new `tasks/045-sky-blue-redesign.md`. +- Features implemented: App-wide Material3 light/dark theme rebuilt around a sky-blue primary with a + near-white background; a small UbiSam wordmark (drawn, not an image asset) in the top-left of the + 저장소 목록 (root/home) screen; launcher icon background recolored to match. +- How it was implemented: `ui/theme/Color.kt`/`Theme.kt` rebuilt with the same shaped + primary/secondary/tertiary + container/on-* + neutral roles TASK-039 used, just re-derived from a + sky-blue seed (`SkyPrimaryLight` `#0369A1`, chosen deep enough for WCAG 4.5:1 white-text contrast). + New `ui/components/UBGiteaTopBar.kt` adds `UBGiteaTopBar`/`UBGiteaLogo`, with the logo's small + signal/RSS-mark badge hand-drawn via `Canvas`/`drawArc` rather than adding a Material Icons + dependency (this app has none today). The logo was deliberately added only to the root screen and + only as branding, not a navigation bar — the other six screens have no back-button UI today (system + back gesture only, with non-trivial existing tab/pager back-interception logic in + `BranchDetailScreen`), and retrofitting a context-aware header into them would be a real navigation + behavior change, not a color/branding pass, so it was left out. Those six screens still pick up the + new color theme automatically since none hardcode colors outside `MaterialTheme.colorScheme`. + `ic_launcher_background.xml`'s fill was updated from `#3B6A1F` to `#0369A1` to match. +- Files changed: `ui/theme/Color.kt`, `ui/theme/Theme.kt`, new `ui/components/UBGiteaTopBar.kt`, + `feature/repositories/RepositoryListScreen.kt` (topBar wiring), `res/drawable/ic_launcher_background.xml`, + `docs/product.md`, `tasks/045-sky-blue-redesign.md`. +- Checks and results: `./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug` + BUILD SUCCESSFUL — all existing unit tests passing unmodified (no behavior touched by this task), + 0 lint errors. +- Live verification: not yet done. Wireless `adb pair` failed five consecutive times across three + freshly issued pairing codes with `protocol fault (couldn't read status message)`, on a PC that + could otherwise ping the phone with 0% packet loss — pointing at a local firewall/security-software + block on the TLS pairing handshake, not an expired code. Diagnosis paused at the human product + owner's direction ("일단 보류"); USB cable connection was proposed as the way to bypass this and is + still pending. +- Review findings and resolutions: None from a separate review pass yet. +- Known limitations: not yet confirmed on the human product owner's physical device (see above); the + logo is a hand-drawn recreation of the reference image, not the actual asset file, since it was only + shared inline in chat and not as a file this agent could read; six of seven screens get the new + theme automatically but no logo/branded header, by design. +- Next recommended task: get the change onto the product owner's device (USB, once available) to + confirm the redesign looks right; if they still want the exact reference logo image rather than the + drawn recreation, get it as a saved file and swap it into `UBGiteaLogo`. Diagnosing the wireless-adb + firewall block remains open but was explicitly deprioritized by the product owner. +- Next owner: Claude, continuing per the standing direction; human product owner for the device + verification and the logo-asset decision. + diff --git a/docs/product.md b/docs/product.md index 642f669..c8c23aa 100644 --- a/docs/product.md +++ b/docs/product.md @@ -49,9 +49,9 @@ The first release is read-oriented. Creating, merging, deleting, or administerin - Android application ID: `com.ubisam.ubgitea` (see ADR-001) - Minimum supported version: Android 9, API 28 (see ADR-001) +- Visual identity, icon, and color palette: sky blue + white, with the UbiSam wordmark small in the top-left of the root screen (see TASK-045) +- Repositories are cached for offline browsing at the list level only (branches/files inside a repository are not cached); see TASK-044 ## Open product decisions -- Visual identity, icon, and color palette - Whether biometric reauthentication is required -- Whether repositories must be cached for offline browsing diff --git a/tasks/045-sky-blue-redesign.md b/tasks/045-sky-blue-redesign.md new file mode 100644 index 0000000..2ba3f20 --- /dev/null +++ b/tasks/045-sky-blue-redesign.md @@ -0,0 +1,109 @@ +# TASK-045: Sky blue + white redesign, UbiSam logo + +Status: implemented; all required checks pass; awaiting live device confirmation (wireless adb +pairing is currently broken on the human product owner's PC -- see Known limitations) +Owner: Claude +Reviewer: Codex + +## Outcome + +Revises `docs/product.md`'s "Visual identity, icon, and color palette" decision (originally closed +by TASK-039 with a "Gitea green" theme), at the human product owner's fresh, explicit request +("현재 UI/UX를 전체적으로 갈아엎고싶다... 하늘색과 하얀색으로 구성하고 싶어"). The app's Material 3 +theme moves from that earlier green palette to a sky-blue + white one, and the UbiSam wordmark now +appears small in the top-left of the root (저장소 목록) screen, matching the reference logo image the +product owner shared. + +## Scope + +- Included: `ui/theme/Color.kt` and `Theme.kt` (full light/dark Material3 tonal palette rebuilt + around a sky-blue primary); a new shared `UBGiteaTopBar`/`UBGiteaLogo` composable; wiring the top + bar into `RepositoryListScreen` (the app's root/home screen); the launcher icon background color + updated to match the new primary. +- Excluded (deliberately): adding the logo or a visible top bar to the other six screens (settings, + notifications, work items, branch detail/files, history), and adding visible back-arrow buttons + anywhere. Those screens have no back-button UI today -- they rely entirely on the system back + gesture, with non-trivial existing logic around exactly when that gesture is intercepted (see + `BranchDetailScreen`'s tab/pager comments). Retrofitting a header with a back action into that + would mean threading current-tab-aware back callbacks through screens that don't expose that + today -- a real behavior change, not a color/branding pass, so it was left out of this task. Those + screens still get the new sky-blue theme automatically, since none of them hardcode colors outside + `MaterialTheme.colorScheme`. +- Excluded (pending an asset): the logo is currently a hand-drawn Compose recreation (`UBGiteaLogo` + in `ui/components/UBGiteaTopBar.kt`) -- grey "Ubi" + blue "Sam" text plus a small rounded badge + with a drawn signal/RSS mark -- reproducing the reference image's colors and layout without an + image asset. If the actual PNG/SVG is provided later, it should replace this with a real + `Image(painterResource(...))`. + +## Design notes + +- **Primary is a deep-enough sky blue for contrast, not the literal reference-image blue**, same + reasoning as the previous "Gitea green" theme: `SkyPrimaryLight` (`#0369A1`) is chosen so white + text/icons on it clear WCAG's 4.5:1 threshold, while lighter tones (`SkyPrimaryContainerLight`, + `InversePrimaryLight`) carry the brighter, more literal sky-blue feel. +- **Background is a near-white with a faint blue cast (`#F7FBFF`), not pure white**, so white + cards/surfaces still have visible separation from the page -- pure white-on-white would flatten + the whole UI. +- **The logo appears only on the root screen.** An app wordmark conventionally belongs on the + top-level/home screen; nested screens showing the same static logo would be redundant chrome that + doesn't help navigation, and (per Scope above) adding a context-aware header to those screens is a + separate, larger change than what was asked for here. +- **No new icon library dependency.** The app currently has zero Material Icons usage (an established + convention -- see the launcher icon's own comment about "no icon library"); the small signal/RSS + mark inside the logo badge is drawn with `Canvas`/`drawArc` rather than pulling in + `material-icons-core` for one glyph. +- **Launcher icon background recolored to match**, so the home-screen icon and in-app theme read as + the same deliberate color rather than drifting apart (this exact reasoning -- and this exact file -- + already existed for the old green theme; this task only changes the fill value and comment). + +## Acceptance criteria + +- [x] App-wide Material3 light and dark color schemes use a sky-blue primary with a near-white + background, replacing the old green theme, with no other role (error, etc.) touched. +- [x] The 저장소 목록 (root) screen shows a small UbiSam wordmark top-left. +- [x] No behavior change to navigation, back handling, or any non-visual logic anywhere in the app. +- [x] Launcher icon background color matches the new theme's primary. +- [x] Build, unit tests, and lint pass. +- [ ] Live-confirmed on the human product owner's device -- blocked, see Known limitations. + +## Required checks + +```text +./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug +``` + +## Evidence + +- `ui/theme/Color.kt`: full sky-blue light/dark palette (`SkyPrimary*`, `SlateSecondary*`, + `CyanTertiary*`, background/surface/outline/inverse roles), plus fixed `UbiSamLogoGray`/ + `UbiSamLogoBlue` brand constants independent of light/dark mode. +- `ui/theme/Theme.kt`: `LightColorScheme`/`DarkColorScheme` rebuilt from the new values; structure + otherwise unchanged from the previous theme file. +- New `ui/components/UBGiteaTopBar.kt`: `UBGiteaTopBar` (a plain branding bar, not a Material3 + `TopAppBar`, deliberately without a back button -- see Scope), `UBGiteaLogo`, and a private + `SignalMark` Canvas composable. +- `feature/repositories/RepositoryListScreen.kt`: `Scaffold`'s `topBar` now renders + `UBGiteaTopBar()`. +- `res/drawable/ic_launcher_background.xml`: fill color changed from `#3B6A1F` to `#0369A1`. +- `docs/product.md`: "Visual identity, icon, and color palette" and "Whether repositories must be + cached for offline browsing" (already shipped by TASK-044, previously left undocumented as + resolved) moved from Open to Product decisions. +- `./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug`: + BUILD SUCCESSFUL, all existing unit tests passing unmodified (no behavior touched), 0 lint errors. + +## Known limitations + +- **Not yet confirmed on the human product owner's physical device.** Wireless `adb` pairing failed + five consecutive times with `protocol fault (couldn't read status message)` across three freshly + issued pairing codes, on a PC that could otherwise ping the phone with 0% loss -- pointing at a + local firewall/security-software block on the TLS pairing handshake specifically, not an expired + code or a network-reachability problem. Diagnosis was paused at the product owner's direction + ("일단 보류"); a USB cable connection was suggested as the way to bypass this and hasn't been tried + yet. +- The logo is a hand-drawn recreation, not the actual reference image file (see Scope). +- Six of the app's seven screens get the new color theme automatically but no logo/branded header -- + intentional, see Scope. + +## Review findings + +None yet -- pending Codex/human review.