From 95cad84cb513db61761c86e9cf9770742e3dc218 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 25 Jun 2026 15:24:46 -0500 Subject: [PATCH] brand: loading state, themes, manifest, art preview, pairing screen updates --- .gitignore | 2 + ClaudeBrandingReview.md | 7 +- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/app/closer/MainActivity.kt | 15 ++ .../app/closer/ui/components/LoadingState.kt | 155 ++++++++++++++++-- .../app/closer/ui/debug/ArtPreviewScreen.kt | 11 ++ .../closer/ui/pairing/PairingSuccessScreen.kt | 20 ++- .../res/drawable-nodpi/closer_mark_loader.png | Bin 23887 -> 0 bytes app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/themes.xml | 9 + docs/brand/asset-system.md | 5 +- docs/brand/visual-identity.md | 7 +- 13 files changed, 212 insertions(+), 27 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/closer_mark_loader.png create mode 100644 app/src/main/res/values/colors.xml diff --git a/.gitignore b/.gitignore index 0a826448..843774f1 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ ClaudeReport.md docs/brand/visual-identity.md docs/brand/asset-system.md docs/brand/visual-identity.md +docs/brand/asset-system.md +ClaudeBrandingReview.md diff --git a/ClaudeBrandingReview.md b/ClaudeBrandingReview.md index a0a5cc73..bad7458b 100644 --- a/ClaudeBrandingReview.md +++ b/ClaudeBrandingReview.md @@ -99,8 +99,9 @@ Legend: ✅ on-brand / no art needed · ➕ add/þwire art (prompt below) · Priority order = the moments a user feels most. Each is self-contained after you paste the House Style block first. **A1 · Pairing success celebration** — *1:1, transparent bg.* -> Scene: two equal heart-halves (soft pink + soft lavender) sliding together into one whole heart at the center, with -> a gentle burst of small floating hearts and petals around it. Convey "you're connected now." Calm, joyful, no text. +> Scene: the Closer mark resolving into place — a soft-pink upper C-arc and a lavender lower sweep curving together to +> enclose a heart-shaped space with a small keyhole at its center — with a gentle burst of small floating hearts and +> petals around it. Convey "you're connected now." Calm, joyful, no text. (White keyhole if on a dark background.) **A2 · Answer history empty state** — *1:1.* > Scene: a small soft journal/photo-album, slightly open, with two paired cards tucked inside and a few faint floating @@ -151,7 +152,7 @@ Priority order = the moments a user feels most. Each is self-contained after you > alarm imagery. **G-set · Notification + relationship glyphs** — *single-color vector, square, legible at 24 dp.* (One prompt each:) -> Simple single-color flat glyph in the Closer style, no text, no background: **heart-of-two-halves**, **paired sealed +> Simple single-color flat glyph in the Closer style, no text, no background: **the Closer C-heart-keyhole mark**, **paired sealed > cards**, **daily card**, **sealed answer (card with small lock)**, **memory capsule**, **date-card with heart**, > **quiet-hours moon**, **couple-premium (heart + small crown/spark, tasteful)**, **export-data**, **delete-account**. > Export in lavender for in-app and as high-contrast monochrome for the platform notification glyph. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cc60fab8..6de58705 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -131,6 +131,7 @@ dependencies { implementation(composeBom) implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.activity:activity-compose:1.9.3") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 87559a64..4a8ef50d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,7 +25,7 @@ android:name=".MainActivity" android:exported="true" android:windowSoftInputMode="adjustResize" - android:theme="@style/Theme.Closer"> + android:theme="@style/Theme.Closer.Splash"> diff --git a/app/src/main/java/app/closer/MainActivity.kt b/app/src/main/java/app/closer/MainActivity.kt index ef501675..46f21d18 100644 --- a/app/src/main/java/app/closer/MainActivity.kt +++ b/app/src/main/java/app/closer/MainActivity.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope @@ -62,7 +63,21 @@ class MainActivity : AppCompatActivity() { private val pendingDeepLink = mutableStateOf(null) override fun onCreate(savedInstanceState: Bundle?) { + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) + // Gentle scale-up + fade exit, handing off to the animated in-app logo loader. + splashScreen.setOnExitAnimationListener { provider -> + provider.iconView.animate() + .scaleX(1.15f) + .scaleY(1.15f) + .setDuration(300L) + .start() + provider.view.animate() + .alpha(0f) + .setDuration(300L) + .withEndAction { provider.remove() } + .start() + } maybeRequestNotificationPermission() registerFcmToken() pendingDeepLink.value = deepLinkRouteFromIntent(intent) diff --git a/app/src/main/java/app/closer/ui/components/LoadingState.kt b/app/src/main/java/app/closer/ui/components/LoadingState.kt index d2530821..3fc31548 100644 --- a/app/src/main/java/app/closer/ui/components/LoadingState.kt +++ b/app/src/main/java/app/closer/ui/components/LoadingState.kt @@ -4,29 +4,43 @@ import app.closer.R import app.closer.ui.theme.closerCardColor import android.provider.Settings import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween -import androidx.compose.foundation.Image +import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.imageResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @Composable @@ -68,6 +82,15 @@ fun CloserHeartLoader( CloserMarkLoader(modifier = modifier, size = size) } +/** Below this size we use a lightweight inline spinner instead of the full app-icon chip. */ +private val ChipLoaderMinSize = 40.dp + +/** + * The brand loader. At prominent sizes it renders the **white-keyhole logo on an aubergine app-icon + * chip** with a soft fill that rises through the mark (the "charging the connection" motion). At small + * inline sizes (e.g. inside buttons) it falls back to a lightweight indeterminate arc tinted to the + * current content color, so it never shows a dark square on a colored button. Honors "remove animations". + */ @Composable fun CloserMarkLoader( modifier: Modifier = Modifier, @@ -81,21 +104,48 @@ fun CloserMarkLoader( 1f ) == 0f } + if (size >= ChipLoaderMinSize) { + CloserMarkChipLoader(modifier = modifier, size = size, reducedMotion = reducedMotion) + } else { + CloserInlineSpinner( + modifier = modifier, + size = size, + color = LocalContentColor.current, + reducedMotion = reducedMotion + ) + } +} + +@Composable +private fun CloserMarkChipLoader( + modifier: Modifier, + size: Dp, + reducedMotion: Boolean +) { val transition = rememberInfiniteTransition(label = "closerMarkLoader") - val animatedPulse = transition.animateFloat( - initialValue = 0.96f, - targetValue = 1.04f, + val fill = if (reducedMotion) 1f else transition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1500, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Restart + ), + label = "closerMarkFill" + ).value + val pulse = if (reducedMotion) 1f else transition.animateFloat( + initialValue = 0.97f, + targetValue = 1.03f, animationSpec = infiniteRepeatable( animation = tween(durationMillis = 900, easing = FastOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = "closerMarkPulse" - ) - val pulse = if (reducedMotion) 1f else animatedPulse.value + ).value - Image( - painter = painterResource(R.drawable.closer_mark_loader), - contentDescription = null, + // White-keyhole mark (already safe-zone padded) drawn over the aubergine app-icon chip. + val mark = ImageBitmap.imageResource(R.drawable.closer_launcher_foreground) + + Canvas( modifier = modifier .size(size) .graphicsLayer { @@ -103,5 +153,88 @@ fun CloserMarkLoader( scaleY = pulse } .clearAndSetSemantics {} - ) + ) { + val w = this.size.width + val h = this.size.height + val corner = CornerRadius(w * 0.22f, h * 0.22f) + // Aubergine gradient chip (matches ic_launcher_background palette). + drawRoundRect( + brush = Brush.linearGradient( + colors = listOf(Color(0xFF5A2F74), Color(0xFF3A1D4D), Color(0xFF24122F)), + start = Offset(0f, 0f), + end = Offset(w, h) + ), + cornerRadius = corner + ) + val src = IntSize(mark.width, mark.height) + val dst = IntSize(w.toInt(), h.toInt()) + // Ghost of the full mark… + drawImage( + image = mark, + srcOffset = IntOffset.Zero, + srcSize = src, + dstOffset = IntOffset.Zero, + dstSize = dst, + alpha = 0.16f + ) + // …then the fill rises bottom→top. + clipRect(top = h * (1f - fill)) { + drawImage( + image = mark, + srcOffset = IntOffset.Zero, + srcSize = src, + dstOffset = IntOffset.Zero, + dstSize = dst, + alpha = 1f + ) + } + // Faint ring so the dark chip separates on dark backgrounds. + drawRoundRect( + color = Color.White.copy(alpha = 0.10f), + cornerRadius = corner, + style = Stroke(width = 1.dp.toPx()) + ) + } +} + +@Composable +private fun CloserInlineSpinner( + modifier: Modifier, + size: Dp, + color: Color, + reducedMotion: Boolean +) { + val angle = if (reducedMotion) 0f else rememberInfiniteTransition(label = "closerInlineSpinner") + .animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 900, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ), + label = "closerInlineAngle" + ).value + + Canvas( + modifier = modifier + .size(size) + .clearAndSetSemantics {} + ) { + val stroke = this.size.minDimension * 0.12f + val inset = stroke / 2f + val d = this.size.minDimension - stroke + rotate(degrees = angle) { + drawArc( + brush = Brush.sweepGradient( + colors = listOf(color.copy(alpha = 0.12f), color) + ), + startAngle = 0f, + sweepAngle = 270f, + useCenter = false, + topLeft = Offset(inset, inset), + size = Size(d, d), + style = Stroke(width = stroke, cap = StrokeCap.Round) + ) + } + } } diff --git a/app/src/main/java/app/closer/ui/debug/ArtPreviewScreen.kt b/app/src/main/java/app/closer/ui/debug/ArtPreviewScreen.kt index 86f6c252..da606194 100644 --- a/app/src/main/java/app/closer/ui/debug/ArtPreviewScreen.kt +++ b/app/src/main/java/app/closer/ui/debug/ArtPreviewScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp import app.closer.R import app.closer.ui.components.CelebrationOverlay import app.closer.ui.components.CloserActionButton +import app.closer.ui.components.CloserMarkLoader import app.closer.ui.components.CloserButtonStyle import app.closer.ui.components.CloserCard import app.closer.ui.settings.SettingsMuted @@ -84,6 +85,16 @@ fun ArtPreviewScreen(onNavigate: (String) -> Unit = {}) { Image(painterResource(R.drawable.particle_petal), null, Modifier.size(56.dp)) } } + ArtCard("Loading mark (animated)") { + androidx.compose.foundation.layout.Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CloserMarkLoader(size = 76.dp) // chip + rising fill + CloserMarkLoader(size = 40.dp) // chip threshold + CloserMarkLoader(size = 24.dp) // inline arc + } + } CloserActionButton( label = "Play the celebration", diff --git a/app/src/main/java/app/closer/ui/pairing/PairingSuccessScreen.kt b/app/src/main/java/app/closer/ui/pairing/PairingSuccessScreen.kt index 73b5e9d5..0943e4c5 100644 --- a/app/src/main/java/app/closer/ui/pairing/PairingSuccessScreen.kt +++ b/app/src/main/java/app/closer/ui/pairing/PairingSuccessScreen.kt @@ -203,19 +203,25 @@ fun PairingSuccessScreen( .size(60.dp) .zIndex(2f) .align(Alignment.Center) + .scale(pulse) .clip(CircleShape) .background(MaterialTheme.colorScheme.background) - .padding(5.dp) - .clip(CircleShape) - .background(Color(0xFFFFF8FC)), + .padding(4.dp) + .clip(CircleShape), contentAlignment = Alignment.Center ) { + // White-keyhole app-icon chip: aubergine gradient + the brand mark. Image( - painter = painterResource(R.drawable.closer_mark_loader), + painter = painterResource(R.drawable.ic_launcher_background), contentDescription = null, - modifier = Modifier - .size(38.dp) - .scale(pulse) + contentScale = ContentScale.Crop, + modifier = Modifier.matchParentSize() + ) + Image( + painter = painterResource(R.drawable.ic_launcher_foreground), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.matchParentSize() ) } } diff --git a/app/src/main/res/drawable-nodpi/closer_mark_loader.png b/app/src/main/res/drawable-nodpi/closer_mark_loader.png deleted file mode 100644 index cddf19b2038c199a3bebb3df51037e5a09e7422a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23887 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 Ml-?><%Ugy04|*@g+yDRo diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..6b4881cf --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + + #FF24122F + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b4e91603..8783d6bd 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,4 +1,13 @@ diff --git a/docs/brand/asset-system.md b/docs/brand/asset-system.md index a5a1b714..8108ebfe 100644 --- a/docs/brand/asset-system.md +++ b/docs/brand/asset-system.md @@ -97,18 +97,17 @@ masters (do not ship them directly); ship the exports and the wired Android rast | `docs/brand/sources/closer-approved-icon-square.png` | Square 1024 master | iOS `AppIcon` 1024, store 1024 | | `docs/brand/sources/closer-mark.svg` | Vector master of the mark | Source for all PNG exports | | `docs/brand/sources/closer-mark-transparent-keyhole-white.png` | Transparent mark, white keyhole (for dark aubergine/purple) | **Source of** `drawable-nodpi/closer_launcher_foreground.png` (adaptive fg over the aubergine bg) + the Auth/Onboarding logo tile (dark surface) | -| `docs/brand/sources/closer-mark-transparent-keyhole-aubergine.png` | Transparent mark, dark keyhole (for light/blush surfaces) | **Source of** `drawable-nodpi/closer_mark_loader.png` (all loaders + the PairingSuccess hero badge on blush) | +| `docs/brand/sources/closer-mark-transparent-keyhole-aubergine.png` | Transparent mark, dark keyhole (for light/blush surfaces) | Logo on light/blush placements + web/docs exports (no in-app wiring today — in-app loaders now use the white-keyhole chip) | | `docs/brand/sources/closer-mark-transparent-keyhole-black.png` | Mono/max-contrast mark | Print/legal, max-contrast light | | `docs/brand/exports/logo/transparent-keyhole-aubergine/closer-mark-{24..1024}.png` | Sized PNGs, dark keyhole | Web/email/social on light, docs, favicons | | `docs/brand/exports/logo/transparent-keyhole-white/closer-mark-{24..1024}.png` | Sized PNGs, white keyhole | Same, on dark surfaces | | `docs/brand/exports/logo/transparent-keyhole-black/closer-mark-{24..1024}.png` | Sized PNGs, mono | One-color/print/footer | | `docs/brand/exports/logo/social/closer-mark-social-{1024,2048}.png` | Square social avatars / OG | Social profile + open-graph square | | `docs/brand/exports/logo/app-icon/closer-app-icon-512.png` | Play listing icon | `docs/store/app-icon-512.png` (Play Console) | -| `app/src/main/res/drawable-nodpi/closer_launcher_foreground.png` | Adaptive **foreground** (white-keyhole mark, safe-zone) | `drawable/ic_launcher_foreground.xml` → `mipmap-anydpi-*/ic_launcher*.xml`; also the Auth/Onboarding logo tile | +| `app/src/main/res/drawable-nodpi/closer_launcher_foreground.png` | Adaptive **foreground** (white-keyhole mark, safe-zone) | `drawable/ic_launcher_foreground.xml` → `mipmap-anydpi-*/ic_launcher*.xml`; Auth/Onboarding logo tile; **`CloserMarkLoader` chip + `PairingSuccess` hero** (white-keyhole logo on the aubergine app-icon chip) | | `app/src/main/res/drawable/ic_launcher_background.xml` | Adaptive **background** (aubergine gradient) | `mipmap-anydpi-*/ic_launcher*.xml` | | `app/src/main/res/drawable-nodpi/closer_launcher_monochrome.png` | Themed-icon **monochrome** (grayscale+alpha) | `drawable/ic_launcher_monochrome.xml` → `mipmap-anydpi-v33/*` | | `app/src/main/res/drawable-nodpi/ic_notification_closer.png` | Notification small icon (white+alpha silhouette) | `NotificationHelper`, `PartnerNotificationManager` `setSmallIcon` | -| `app/src/main/res/drawable-nodpi/closer_mark_loader.png` | Full-color transparent mark | `CloserMarkLoader` (all loaders), `PairingSuccessScreen` hero, `AuthLogoMark`/Onboarding tile | Note: the section-1 rows `closer-mark-on-dark.svg` / `closer-mark-on-light.svg` and the lockup/favicon rows are still **to-build** — the on-disk equivalents today are the `transparent-keyhole-white` (dark) diff --git a/docs/brand/visual-identity.md b/docs/brand/visual-identity.md index 36cca7c2..51c34821 100644 --- a/docs/brand/visual-identity.md +++ b/docs/brand/visual-identity.md @@ -42,8 +42,11 @@ Use the keyhole color to support contrast while keeping the official icon visual the black-keyhole variant only when maximum contrast is needed. - **Notification small icon:** use the single-color platform glyph; do not use the full-color launcher art. -- **Loading mark:** use the aubergine-keyhole variant on light/card surfaces so it stays calmer than - the launcher icon. +- **Loading mark:** the in-app loader is the **white-keyhole logo on an aubergine app-icon chip** with a + soft fill that rises through the mark (a "charging" loop). Below ~40dp (e.g. inside buttons) it falls + back to a lightweight indeterminate arc tinted to the surrounding content color. +- **Cold-launch splash:** aubergine background + the white-keyhole app icon (static, system-masked), with + a scale/fade exit that hands off to the in-app loading mark above. ## Core colors