TASK-045: sky blue + white redesign, UbiSam logo
Rebuilds the Material3 light/dark palette around a sky-blue primary, adds the UBGiteaTopBar wordmark to the repository list, and recolors the launcher icon background to match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
39be1b71dc
commit
71c8457578
@ -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),
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- App icon background layer. This app had no custom launcher icon at all until now (Android's
|
||||
own default was showing), so this is a solid fill in the same green already used as this
|
||||
app's Material 3 primary color (ui/theme/Color.kt's GreenPrimaryLight), so the home-screen
|
||||
icon and the in-app theme read as the same deliberate color, not two different greens. -->
|
||||
<!-- App icon background layer: a solid fill in the same sky blue used as this app's Material 3
|
||||
primary color (ui/theme/Color.kt's SkyPrimaryLight), so the home-screen icon and the in-app
|
||||
theme read as the same deliberate color rather than drifting apart after the sky-blue redesign
|
||||
(this used to match the earlier "Gitea green" theme's primary before that changed). -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3B6A1F"
|
||||
android:fillColor="#0369A1"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
109
tasks/045-sky-blue-redesign.md
Normal file
109
tasks/045-sky-blue-redesign.md
Normal file
@ -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.
|
||||
Loading…
x
Reference in New Issue
Block a user