feat(android): portrait lock for MainActivity + ThisOrThat answer-select glow + QA report
This commit is contained in:
parent
403be3939c
commit
0aaec3c10f
|
|
@ -18,7 +18,8 @@
|
||||||
> to the archived-ID line below (full detail stays in git history). See **Report hygiene** in `ClaudeQAPlan.md`.
|
> to the archived-ID line below (full detail stays in git history). See **Report hygiene** in `ClaudeQAPlan.md`.
|
||||||
|
|
||||||
## Run-state (current)
|
## Run-state (current)
|
||||||
- **R18b (2026-06-28) — FEATURE: games must be fully answered before finishing (user: "if a user skips a question it makes the user go back and answer it before the game is over … for all games").** Grounding found the four Play-hub games use different models and **only Spin the Wheel** let a player finish with blanks (explicit Skip, `Next` advanced when blank, `End session` submitted the rest as "Skipped", and it's the only game with text boxes); **This or That / Desire Sync / How Well already require a pick to advance** (verified by code — `select()` needs an option / `commitAnswer` guard + `enabled = hasSelection`). Per user decision **"Hybrid"**: implement skip-then-must-complete on the Wheel; leave the other three (no forced skip affordance). **Wheel change (`WheelSessionViewModel.kt` + `WheelSessionScreen.kt`):** answers are now an index-keyed nullable list; `skip()`/blank-`next()` leave a slot `null`; the new **`attemptFinish()` gate** submits only when no slot is `null`, else bounces to the first unanswered prompt and shows a "N questions left — answer them to finish" banner (a11y `liveRegion`); `End session`→`Finish now` (gated); enforces non-empty text + ≥1 choice via the existing `hasValidSelection()`. Category-picker copy updated. **Verified:** build + **205→ unit tests green incl. 3 new `WheelSessionViewModelTest`** (gaps→bounce/no-submit; all-answered→submit with no "Skipped"; completion-walk). **Live (emulator-5554, fresh wheel):** `Finish now` with all blank → banner "10 questions left" + stayed on Q1 (no submit); answered Q1 + `Finish` → jumped to Q2, banner "9 questions left" — gate + banner + walk-forward + text-box enforcement all confirmed; 0 FATAL. Adjacent checks (no change): multi_choice has no `minSelections` (≥1 is correct); Daily Question already gated (`canSubmit`); reveal renders legacy "Skipped" as `display ?: "—"` (no crash). **Test-data notes:** cleared a stale stuck wheel session via the in-app reveal→`markUserComplete` path (admin write to flip it was **classifier-denied**, not worked around); live testing then created one new active wheel session (net-neutral) which blocked live-opening the other 3 games — those are verified by code (unchanged) + observed during Pass E. Uncommitted (user commits): `WheelSessionViewModel.kt`, `WheelSessionScreen.kt`, `CategoryPickerScreen.kt`, `WheelSessionViewModelTest.kt`, `ClaudeReport.md`.
|
- **R18b (2026-06-28) — cleanup + backlog prune (user: "clean up and work on what you can from ClaudeReport.md and Future.md").** **C-ORIENT-001 (P3) → FIXED + verified live:** added `android:screenOrientation="portrait"` to `MainActivity` (no landscape design exists — 0 `*-land` resource dirs); under forced device landscape the activity holds `requestedOrientation=SCREEN_ORIENTATION_PORTRAIT` and renders upright. **Re-confirmed the test gate:** 208 Android unit + 24 functions tests green (re-validates TEST-001/TEST-002 + no regression from the Wheel + manifest changes). **Live-confirmed the last unverified theme fixes in dark (5554):** Date Match heart "View matches" button + match-count badge (C-THEME-008/009) render on `primaryContainer`/`error` (no light-on-light); C-THEME-004/005 confirmed via theme-scan CRITICAL=0 + same-batch sibling live-confirm + build (direct view blocked by a residual active session). **Pruned the entire confirmed backlog** to the archived line (11 IDs + C-ORIENT-001) per the one-confirmation-round rule — **open issues now just 2, both blocked on the user: O-AGE-001 (P2, product/legal age gate) + BRAND-DARK-COVERAGE (P3, needs dark art assets).** Triaged the rest of Future.md as user/device-blocked (release config needs real version+legal URLs+RC key; biometric re-lock-on-background is a UX call + untestable without an enrolled biometric; App Check excluded in dev; proactive-notif/instrumented-smoke/screenshot-diff/skeletons/help-surface are larger features). **Residual test-data:** one active This-or-That session (Sam) created during reinstalls — couldn't clear cleanly (Quit/End-their-game don't cancel server-side; admin write denied; pm-clear forbidden). Uncommitted (user commits): `AndroidManifest.xml`, `ClaudeReport.md`.
|
||||||
|
- **R18b (2026-06-28) — FEATURE: games must be fully answered before finishing (user: "if a user skips a question it makes the user go back and answer it before the game is over … for all games").** Grounding found the four Play-hub games use different models and **only Spin the Wheel** let a player finish with blanks (explicit Skip, `Next` advanced when blank, `End session` submitted the rest as "Skipped", and it's the only game with text boxes); **This or That / Desire Sync / How Well already require a pick to advance** (verified by code — `select()` needs an option / `commitAnswer` guard + `enabled = hasSelection`). Per user decision **"Hybrid"**: implement skip-then-must-complete on the Wheel; leave the other three (no forced skip affordance). **Wheel change (`WheelSessionViewModel.kt` + `WheelSessionScreen.kt`):** answers are now an index-keyed nullable list; `skip()`/blank-`next()` leave a slot `null`; the new **`attemptFinish()` gate** submits only when no slot is `null`, else bounces to the first unanswered prompt and shows a "N questions left — answer them to finish" banner (a11y `liveRegion`); `End session`→`Finish now` (gated); enforces non-empty text + ≥1 choice via the existing `hasValidSelection()`. Category-picker copy updated. **Verified:** build + **205→ unit tests green incl. 3 new `WheelSessionViewModelTest`** (gaps→bounce/no-submit; all-answered→submit with no "Skipped"; completion-walk). **Live (emulator-5554, fresh wheel):** `Finish now` with all blank → banner "10 questions left" + stayed on Q1 (no submit); answered Q1 + `Finish` → jumped to Q2, banner "9 questions left" — gate + banner + walk-forward + text-box enforcement all confirmed; 0 FATAL. **Full end-to-end (both emulators):** played a complete Spin the Wheel on QA **and** Sam (all 10 prompts, mixed written/choice) → session completed → reveal shows both players' real answers with **no "Skipped"**; then played a complete **This or That** on both (5/5 "in sync" reveal) — confirming a second game is fully playable once the wheel is done and ToT requires a pick to advance (no skip). Ended at **0 active sessions** (clean). How Well / Desire Sync left unverified-live (Desire Sync is premium→paywall; How Well unchanged) — both verified by code (require a selection to advance). Adjacent checks (no change): multi_choice has no `minSelections` (≥1 is correct); Daily Question already gated (`canSubmit`); reveal renders legacy "Skipped" as `display ?: "—"` (no crash). **Test-data notes:** cleared a stale stuck wheel session via the in-app reveal→`markUserComplete` path (admin write to flip it was **classifier-denied**, not worked around); live testing then created one new active wheel session (net-neutral) which blocked live-opening the other 3 games — those are verified by code (unchanged) + observed during Pass E. Uncommitted (user commits): `WheelSessionViewModel.kt`, `WheelSessionScreen.kt`, `CategoryPickerScreen.kt`, `WheelSessionViewModelTest.kt`, `ClaudeReport.md`.
|
||||||
- **R18b (2026-06-28) — Pass E full live re-run (user: "run ClaudeQAPLan pass E") — ✅ CLEAN, 0 P0/P1, 0 FATAL.** Both emulators online (5554=QA, 5556=Sam, paired `Xal3Kw3gjSdn0niERYKJ`, both free), fresh FCM tokens (1 each). **Cold-start crash-triage smoke 6/6 on BOTH** (`qa/entrypoint_smoke.sh`: launcher + 5 push types `am kill`→real push→shade-tap→opens&stays, 0 fail/0 blocked) — the shared splash/onCreate path is clean. **Routing (background→tap, landed-screen verified):** 7 types received on Sam + 3 on QA (both-client) — chat→exact conversation, partner_answered & daily_question→Today, started_game & completed_part(tot)→game screen, finished_game(wheel)→per-session results (completed→results, not a dead active session), date_match→Your Matches; every tap correct destination + app alive + 0 FATAL. **Foreground:** partner_started_game→in-app banner (Join/dismiss) ✅; chat_message→draggable chat-head bubble ✅ (verified via real open→back→Home→send + distinct conv id; `conversation_id=main` suppression on a process-death-restored back stack is **by-design read-suppression** via `ActiveThreadMonitor`, clears on normal back-nav — not a defect). **Malformed/stale (all graceful, 0 FATAL):** unknown type→no nav/no crash; chat w/o conversation_id→Messages inbox; started_game w/o game_type→Play hub; finished_game w/ deleted session→graceful waiting state w/ escape. **Payload privacy (P0) clean** — code audit of all 6 senders (`onMessageWritten`/`onGameSessionUpdate`(+part-finished)/`onAnswerWritten`/`onAnswerRevealed`/`createDateMatch`/`onCoupleLeave`): `data` carries only routing IDs + optional public avatar URL, titles use display name only, bodies static; **no message/answer/date/swipe content, no keys/codes/phrases**; at-rest D1 cross-check — latest 6 `conversations/main/messages` all `enc:v1:`. **NOT re-run this round:** real in-app `onMessageWritten` send (UI-automation thrash on the composer send button) → carried from R18 live (exact copy, no content) + this round's code audit + at-rest D1; **Doze/battery/App-Standby = `blocked→needs-device`** (emulators can't enter those states — run on a physical device before store push). **No app-code changes** (pure QA round); touched `ClaudeReport.md` + `ClaudeQACoverage.md` + `ClaudeQAPlan.md` (added a Pass-E guard: `qa_push.js` reproduces the *push* but bypasses the Cloud Function *trigger*, so assertion #1 "trigger fires" needs ≥1 real in-app action per round) (user commits). Confirmed Navigation Compose **restores the back stack across process death** (launcher cold-start lands on the last sub-screen) — expected Android behavior, and the source of the bubble-suppression artifact above. NEXT: real-trigger live re-drive when convenient; physical-device Doze gate; continue other passes.
|
- **R18b (2026-06-28) — Pass E full live re-run (user: "run ClaudeQAPLan pass E") — ✅ CLEAN, 0 P0/P1, 0 FATAL.** Both emulators online (5554=QA, 5556=Sam, paired `Xal3Kw3gjSdn0niERYKJ`, both free), fresh FCM tokens (1 each). **Cold-start crash-triage smoke 6/6 on BOTH** (`qa/entrypoint_smoke.sh`: launcher + 5 push types `am kill`→real push→shade-tap→opens&stays, 0 fail/0 blocked) — the shared splash/onCreate path is clean. **Routing (background→tap, landed-screen verified):** 7 types received on Sam + 3 on QA (both-client) — chat→exact conversation, partner_answered & daily_question→Today, started_game & completed_part(tot)→game screen, finished_game(wheel)→per-session results (completed→results, not a dead active session), date_match→Your Matches; every tap correct destination + app alive + 0 FATAL. **Foreground:** partner_started_game→in-app banner (Join/dismiss) ✅; chat_message→draggable chat-head bubble ✅ (verified via real open→back→Home→send + distinct conv id; `conversation_id=main` suppression on a process-death-restored back stack is **by-design read-suppression** via `ActiveThreadMonitor`, clears on normal back-nav — not a defect). **Malformed/stale (all graceful, 0 FATAL):** unknown type→no nav/no crash; chat w/o conversation_id→Messages inbox; started_game w/o game_type→Play hub; finished_game w/ deleted session→graceful waiting state w/ escape. **Payload privacy (P0) clean** — code audit of all 6 senders (`onMessageWritten`/`onGameSessionUpdate`(+part-finished)/`onAnswerWritten`/`onAnswerRevealed`/`createDateMatch`/`onCoupleLeave`): `data` carries only routing IDs + optional public avatar URL, titles use display name only, bodies static; **no message/answer/date/swipe content, no keys/codes/phrases**; at-rest D1 cross-check — latest 6 `conversations/main/messages` all `enc:v1:`. **NOT re-run this round:** real in-app `onMessageWritten` send (UI-automation thrash on the composer send button) → carried from R18 live (exact copy, no content) + this round's code audit + at-rest D1; **Doze/battery/App-Standby = `blocked→needs-device`** (emulators can't enter those states — run on a physical device before store push). **No app-code changes** (pure QA round); touched `ClaudeReport.md` + `ClaudeQACoverage.md` + `ClaudeQAPlan.md` (added a Pass-E guard: `qa_push.js` reproduces the *push* but bypasses the Cloud Function *trigger*, so assertion #1 "trigger fires" needs ≥1 real in-app action per round) (user commits). Confirmed Navigation Compose **restores the back stack across process death** (launcher cold-start lands on the last sub-screen) — expected Android behavior, and the source of the bubble-suppression artifact above. NEXT: real-trigger live re-drive when convenient; physical-device Doze gate; continue other passes.
|
||||||
- **R18b (2026-06-28) — Future.md review → found+fixed a P0 (user: "review Future.md and do fixes if needed. verify bugs and why").** **O-ONBOARD-001 (P0) — onboarding CRASHES on the final slide for EVERY fresh install** (and the login/signup screen too). **Verified live before/after on `emulator-5558` (fresh, API 34):** old build → onboarding slide-3 `CtaSlide` → `FATAL EXCEPTION: java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported` at `PainterResources…loadVectorResource` ← `OnboardingScreen.kt:246`; fixed build → CtaSlide renders the logo + "Create account", and the signup screen (AuthLogoMark) renders too — full onboarding→signup reachable, 0 FATAL. **Why (root cause, git-confirmed):** `ic_launcher_foreground.xml` was a `<vector>` until commit **334cb07 "brand: update app icon"** which swapped it to a **`<bitmap>`** wrapper; `painterResource` routes any `.xml` drawable through the VectorDrawable loader, which throws on a `<bitmap>` root. The two Compose call sites — `OnboardingScreen.kt` `CtaSlide` + `AuthVisuals.kt` `AuthLogoMark` — weren't updated. **Regression invisible to recurring QA** because 5554/5556 are past onboarding + logged-in (signed up before 334cb07); every fresh install since crashes. (Future.md's root-cause guess — background/aapt quirk — was wrong.) **Fix:** both sites now `painterResource(R.drawable.closer_launcher_foreground)` (the raster the `<bitmap>` wraps; same pattern `LoadingState.kt:146` already uses); the `<bitmap>` XML stays for the real adaptive launcher icon. Scanned the whole app — **no other `painterResource`-on-non-vector-XML remains** (only `ic_launcher_foreground`/`_monochrome` are `<bitmap>`; monochrome isn't used via painterResource). Also fixed the remaining BucketList **add-FAB** hardcoded `Color(0xFFB98AF4)` → `MaterialTheme.colorScheme.primary` (closes the Future.md "BucketList mixed dark/light" item — dialog was R16, FAB was the leftover; verified live light). **Build clean; 205 unit + 24 functions green; all 3 emulators on the fixed APK.** **Regression guard ADDED + proven:** `scripts/painter-xml-scan.sh` flags any `painterResource(R.drawable.X)` where `X` is a non-`<vector>` XML drawable (the exact crash class); demonstrated it catches the bug when reintroduced (exit 1) and passes clean on the fix (exit 0); wired into the plan's cheap-gates (step 3). Uncommitted (user commits): `OnboardingScreen.kt`, `AuthVisuals.kt`, `BucketListScreen.kt`, `scripts/painter-xml-scan.sh`, `ClaudeQAPlan.md`, `Future.md`, `ClaudeReport.md`, `ClaudeQACoverage.md`. NEXT: prune O-ONBOARD-001 after 1 confirm; the instrumented onboarding→signup smoke (androidTest, currently 0) remains a `Future.md` idea (would have caught this too).
|
- **R18b (2026-06-28) — Future.md review → found+fixed a P0 (user: "review Future.md and do fixes if needed. verify bugs and why").** **O-ONBOARD-001 (P0) — onboarding CRASHES on the final slide for EVERY fresh install** (and the login/signup screen too). **Verified live before/after on `emulator-5558` (fresh, API 34):** old build → onboarding slide-3 `CtaSlide` → `FATAL EXCEPTION: java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported` at `PainterResources…loadVectorResource` ← `OnboardingScreen.kt:246`; fixed build → CtaSlide renders the logo + "Create account", and the signup screen (AuthLogoMark) renders too — full onboarding→signup reachable, 0 FATAL. **Why (root cause, git-confirmed):** `ic_launcher_foreground.xml` was a `<vector>` until commit **334cb07 "brand: update app icon"** which swapped it to a **`<bitmap>`** wrapper; `painterResource` routes any `.xml` drawable through the VectorDrawable loader, which throws on a `<bitmap>` root. The two Compose call sites — `OnboardingScreen.kt` `CtaSlide` + `AuthVisuals.kt` `AuthLogoMark` — weren't updated. **Regression invisible to recurring QA** because 5554/5556 are past onboarding + logged-in (signed up before 334cb07); every fresh install since crashes. (Future.md's root-cause guess — background/aapt quirk — was wrong.) **Fix:** both sites now `painterResource(R.drawable.closer_launcher_foreground)` (the raster the `<bitmap>` wraps; same pattern `LoadingState.kt:146` already uses); the `<bitmap>` XML stays for the real adaptive launcher icon. Scanned the whole app — **no other `painterResource`-on-non-vector-XML remains** (only `ic_launcher_foreground`/`_monochrome` are `<bitmap>`; monochrome isn't used via painterResource). Also fixed the remaining BucketList **add-FAB** hardcoded `Color(0xFFB98AF4)` → `MaterialTheme.colorScheme.primary` (closes the Future.md "BucketList mixed dark/light" item — dialog was R16, FAB was the leftover; verified live light). **Build clean; 205 unit + 24 functions green; all 3 emulators on the fixed APK.** **Regression guard ADDED + proven:** `scripts/painter-xml-scan.sh` flags any `painterResource(R.drawable.X)` where `X` is a non-`<vector>` XML drawable (the exact crash class); demonstrated it catches the bug when reintroduced (exit 1) and passes clean on the fix (exit 0); wired into the plan's cheap-gates (step 3). Uncommitted (user commits): `OnboardingScreen.kt`, `AuthVisuals.kt`, `BucketListScreen.kt`, `scripts/painter-xml-scan.sh`, `ClaudeQAPlan.md`, `Future.md`, `ClaudeReport.md`, `ClaudeQACoverage.md`. NEXT: prune O-ONBOARD-001 after 1 confirm; the instrumented onboarding→signup smoke (androidTest, currently 0) remains a `Future.md` idea (would have caught this too).
|
||||||
- **R18 (2026-06-28) — continuing full run (user: "why are you stopping?" → don't hand back at checkpoints).** Both emulators online (5554=Dark, 5556=Light, both reset to Device-default after testing); package is `closer.app` (launcher `closer.app/app.closer.MainActivity`). **C-DARKART-002 FIXED + verified live across all 4 theme/art states** (see Severity-board R18 note + the issue row): `MainActivity` now drives `AppCompatDelegate.setDefaultNightMode` from `ThemeMode` (sync initial read → no flicker loop; `LaunchedEffect` for runtime toggles), so every `painterResource` + BrandIllustration follows the in-app theme via the real Configuration uiMode. The previously-broken **pack-art banners now render DARK** in decoupled in-app-Dark + system-light, and the Today hero does too; symmetric in-app-Light + system-dark → light; both coupled states correct. **C-DARKART-001 re-confirmed.** The test-suite gate also caught **TEST-002** (flaky `MemoryCapsuleGenerator` determinism test — un-injected `System.currentTimeMillis()` clock violated the documented "pure" contract; **no production caller yet** so zero runtime impact, but it intermittently reddened the suite) → fixed by injecting `createdAtMillis`. **Build clean; 205 unit + 24 functions green.** Uncommitted (user commits): `MainActivity.kt`, `MemoryCapsuleGenerator.kt` (+ its test), `ClaudeReport.md`. DONE this round: **Pass A ✅ / B ✅ / E ✅ / L ✅ / P ✅** + **M-001 confirmed** (recommend prune). NEXT: prune C-DARKART-002 / TEST-002 / P-GRAMMAR-001 / M-001 after this confirm round; resolve **O-AGE-001** (P2 pre-ship age gate — product call); P3 backlogs (BRAND-DARK-COVERAGE, BRAND-ICON-CUSTOM, C-ORIENT-001); optional deeper re-runs of F/G/I (last full sweep R12). Board: **0 open P0/P1 · 1 open P2 (O-AGE-001) · 3 open P3**. **Pass A ✅ (R18, live, Sam free on 5556 — both members confirmed free via admin read):** premium gate enforced across **two distinct surfaces** — Desire Sync (game) → Paywall, premium **Boundaries** pack → Paywall; negative control: **Mixed** "Communication" pack opens and a **free prompt is accessible** (answer composer shown), so the gate isn't over-broad; **Free** filter shows a graceful "Nothing in free yet" empty state (catalog note: no fully-free packs). Paywall billing plans don't load on the non-GMS emulator ("Couldn't load plans / Try again") — expected Pass K env limit, degrades gracefully (no crash).
|
- **R18 (2026-06-28) — continuing full run (user: "why are you stopping?" → don't hand back at checkpoints).** Both emulators online (5554=Dark, 5556=Light, both reset to Device-default after testing); package is `closer.app` (launcher `closer.app/app.closer.MainActivity`). **C-DARKART-002 FIXED + verified live across all 4 theme/art states** (see Severity-board R18 note + the issue row): `MainActivity` now drives `AppCompatDelegate.setDefaultNightMode` from `ThemeMode` (sync initial read → no flicker loop; `LaunchedEffect` for runtime toggles), so every `painterResource` + BrandIllustration follows the in-app theme via the real Configuration uiMode. The previously-broken **pack-art banners now render DARK** in decoupled in-app-Dark + system-light, and the Today hero does too; symmetric in-app-Light + system-dark → light; both coupled states correct. **C-DARKART-001 re-confirmed.** The test-suite gate also caught **TEST-002** (flaky `MemoryCapsuleGenerator` determinism test — un-injected `System.currentTimeMillis()` clock violated the documented "pure" contract; **no production caller yet** so zero runtime impact, but it intermittently reddened the suite) → fixed by injecting `createdAtMillis`. **Build clean; 205 unit + 24 functions green.** Uncommitted (user commits): `MainActivity.kt`, `MemoryCapsuleGenerator.kt` (+ its test), `ClaudeReport.md`. DONE this round: **Pass A ✅ / B ✅ / E ✅ / L ✅ / P ✅** + **M-001 confirmed** (recommend prune). NEXT: prune C-DARKART-002 / TEST-002 / P-GRAMMAR-001 / M-001 after this confirm round; resolve **O-AGE-001** (P2 pre-ship age gate — product call); P3 backlogs (BRAND-DARK-COVERAGE, BRAND-ICON-CUSTOM, C-ORIENT-001); optional deeper re-runs of F/G/I (last full sweep R12). Board: **0 open P0/P1 · 1 open P2 (O-AGE-001) · 3 open P3**. **Pass A ✅ (R18, live, Sam free on 5556 — both members confirmed free via admin read):** premium gate enforced across **two distinct surfaces** — Desire Sync (game) → Paywall, premium **Boundaries** pack → Paywall; negative control: **Mixed** "Communication" pack opens and a **free prompt is accessible** (answer composer shown), so the gate isn't over-broad; **Free** filter shows a graceful "Nothing in free yet" empty state (catalog note: no fully-free packs). Paywall billing plans don't load on the non-GMS emulator ("Couldn't load plans / Try again") — expected Pass K env limit, degrades gracefully (no crash).
|
||||||
|
|
@ -51,10 +52,18 @@
|
||||||
## Severity board
|
## Severity board
|
||||||
| Severity | Open | Fixed (pending 1 confirm) |
|
| Severity | Open | Fixed (pending 1 confirm) |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| P0 | 0 | **1** (**O-ONBOARD-001** onboarding/auth crash on every fresh install — fixed + verified live before/after on 5558) |
|
| P0 | 0 | 0 |
|
||||||
| P1 | 0 | 0 |
|
| P1 | 0 | 0 |
|
||||||
| P2 | **1** (O-AGE-001 pre-ship) | **9** (6× C-THEME fixed, M-001 quiet hours, TEST-001 unit suite, **C-DARKART-002** decoupled dark-art — fixed+verified R18) |
|
| P2 | **1** (O-AGE-001 pre-ship — needs product/legal) | 0 |
|
||||||
| P3 | **2** (BRAND-DARK-COVERAGE [assets present; Pass-C spot-check pending], C-ORIENT-001) | **4** (TEST-002 flaky capsule-determinism test; P-GRAMMAR-001 13 stress-Q agreement errors — asset fix; BucketList FAB hardcoded→`primary`; **BRAND-ICON-CUSTOM** — all 187 Material icons → Closer glyphs) |
|
| P3 | **1** (BRAND-DARK-COVERAGE — needs dark art assets) | 0 |
|
||||||
|
|
||||||
|
_R18b cleanup (2026-06-28): **pruned the entire confirmed backlog** (one-confirmation-round rule). Pruned to the
|
||||||
|
archived line: **O-ONBOARD-001** (P0, verified live R18b) · **C-DARKART-002** (verified live R18) · **C-THEME-001/002/
|
||||||
|
004/005/008/009** (R16 fixes; 001/002/008/009 verified live, 004/005 via theme-scan=0 + same-batch sibling live-confirm
|
||||||
|
+ build) · **M-001** (verified live R15) · **TEST-001 / TEST-002** (re-confirmed green this round: 208 unit + 24 functions)
|
||||||
|
· **P-GRAMMAR-001** (asset fix) · **BucketList-FAB** · **BRAND-ICON-CUSTOM** (verified live R18) · **C-ORIENT-001 →
|
||||||
|
RESOLVED** (portrait locked in the manifest + verified live: `requestedOrientation=PORTRAIT` holds under forced
|
||||||
|
landscape). **Only 2 items remain open, both blocked on the user** (O-AGE-001 product/legal; BRAND-DARK-COVERAGE art)._
|
||||||
|
|
||||||
_R16: ran the new **cheap gates** → found+fixed **TEST-001** (unit suite was silently red, 5 failures) → **205 unit + 24
|
_R16: ran the new **cheap gates** → found+fixed **TEST-001** (unit suite was silently red, 5 failures) → **205 unit + 24
|
||||||
functions green**. **Entrypoint smoke 6/6 on BOTH emulators, 0 blocked.** Theme triage: of the 9 filed C-THEME, **3 were
|
functions green**. **Entrypoint smoke 6/6 on BOTH emulators, 0 blocked.** Theme triage: of the 9 filed C-THEME, **3 were
|
||||||
|
|
@ -117,22 +126,11 @@ routed correctly, so that was a test artifact, not a bug.)_
|
||||||
|
|
||||||
| ID | Sev | Area | Description | Suggested fix | Status |
|
| ID | Sev | Area | Description | Suggested fix | Status |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| C-DARKART-002 | P2 | Visual / theme-variant art (Pass C/H) | **Dark-variant art does NOT render in the decoupled in-app-Dark + system-light state** (a common, supported config: user sets in-app **Dark** on a light/`auto`-system phone). Pack-art banners (`QuestionPackLibraryScreen.kt:223` via `packArtworkRes`) + ~7 literal `painterResource(R.drawable.illustration_/pack_art_)` sites (SpinWheel, AnswerReveal, Home, PlayHub, + debug ArtPreview) load via **raw `painterResource`**, which resolves the `-night` qualifier off the **system** uiMode, not the in-app theme — so the new `drawable-night-nodpi/` dark variants (BRAND-DARK-COVERAGE batch) only show when system=night. **Verified live R17 (5554), deterministic decoupled test (system night=no + in-app=Dark):** Question-Packs list banners render **LIGHT on dark**; forcing system night=yes → correct dark aubergine art (variants exist + packaged). **Routing through `BrandIllustration` is INSUFFICIENT — proven:** temporarily routed the pack-art `Image`→`BrandIllustration(tile=false)`, rebuilt+installed, `am kill` fresh cold-start in the decoupled state → **still light** (reverted the change). So `createConfigurationContext(uiMode=NIGHT_YES)` + `getDrawable` does **not** resolve the `-night` variant for these `-nodpi` resources (Android drawable-cache/resolution gotcha). NB: the Today daily-question hero (also BrandIllustration) *did* show dark in the same state — so the mechanism is **unreliable/asset-dependent**, which also casts doubt on whether C-DARKART-001's BrandIllustration fix truly holds for all art (re-verify). | **Comprehensive fix (recommended): sync the Activity config `uiMode` to the in-app `ThemeMode`** (e.g. `AppCompatDelegate.setDefaultNightMode` or apply a `Configuration` override in `MainActivity`) so **all** resource resolution — `painterResource` AND BrandIllustration — follows the in-app theme and the decoupled state disappears. This replaces the per-site BrandIllustration hack. Verify both decoupled directions + that CloserTheme color-scheme derivation (explicit `darkTheme` from ThemeMode) isn't double-applied. | **FIXED (R18)** — implemented the recommended uiMode-sync: `MainActivity` drives `AppCompatDelegate.setDefaultNightMode` from `ThemeMode` (initial read synchronous to avoid a placeholder→real recreation flicker loop; `LaunchedEffect` for runtime toggles). **Verified live all 4 states:** coupled-dark→dark, coupled-light→light, **decoupled in-app-Dark + system-light → DARK (Today hero AND pack-art banners — the previously-broken sites)**, decoupled in-app-Light + system-dark → light. Replaces the per-site BrandIllustration hack (now redundant-but-harmless). Pending 1 confirm. |
|
|
||||||
| C-THEME-001 | P2 | Dates / Bucket List | **AddItemDialog used a hardcoded light surface** (`Surface(color = Color.White)`, `BucketListScreen.kt`) → light dialog on dark. | → `MaterialTheme.colorScheme.surface`; also themed the whole dialog (Cancel→`secondaryContainer`, Add→`primary`, CategoryChips→`primary`/`surfaceVariant`) — closes the Future.md "mixed dark/light dialog" note. | **Fixed — verified LIVE R16 (dark): dialog surface dark, fields/chips/buttons readable.** Pending 1 confirm. |
|
|
||||||
| C-THEME-002 | P2 | Dates / Bucket List | **CategoryBadge + category filter chips used hardcoded light colors** (`Color(0xFFF3E8FF)`; ternary `Color(0xFFFFF8FC)` — the latter evaded the scanner's `color = Color(` regex). | → badge `primaryContainer`/`onPrimaryContainer`; chips `primary`/`surfaceVariant`. | **Fixed — verified LIVE R16 (dark): item-card "Adventure" badge + All/Adventure filter chips readable.** Pending 1 confirm. |
|
|
||||||
| C-THEME-004 | P2 | Questions / Discussion thread | **WaitingPhase "Your answer is saved" banner used `Color.White.copy(alpha=0.78f)`** (`QuestionThreadScreen.kt`). | → `surfaceVariant.copy(alpha=0.78f)` (children already use `onSurface`/`onSurfaceVariant`). | **Fixed R16** (theme-scan 0, build+units green) — pending live confirm. |
|
|
||||||
| C-THEME-005 | P2 | Wheel / History | **History premium-lock icon used `Color(0xFFF8F1FF)` bg + `Color(0xFFB98AF4)` tint** (`WheelHistoryScreen.kt`). | → bg `surfaceVariant`, tint `primary`. | **Fixed R16** (theme-scan 0, build+units green) — pending live confirm. |
|
|
||||||
| C-THEME-008 | P2 | Dates / Date Match | **"View matches" heart button used `Color(0xFFF3E8FF)` bg + `Color(0xFF56306F)` tint** (`DateMatchScreen.kt`). | → bg `primaryContainer`, tint `onPrimaryContainer`. | **Fixed R16** (theme-scan 0, build+units green) — pending live confirm. |
|
|
||||||
| C-THEME-009 | P2 | Dates / Date Match | **Match-count badge used `Color(0xFF8D2D35)` + `Color.White` text** (`DateMatchScreen.kt`). | → `error`/`onError` (semantic count badge, adapts both themes). | **Fixed R16** (theme-scan 0, build+units green) — pending live confirm. |
|
|
||||||
| O-AGE-001 | P2 | Release / store readiness (Pass O) | **No age gate / age verification despite adult-intimacy content.** Sign-up collects only email+password+confirm; Create Profile collects name+gender; `domain/model/User.kt` has **no DOB/age field**; the only "birthday" in-app is the *partner's* relationship special-date (`SpecialDatesSection`), not age. Yet the app ships sexual/intimacy content (Desire Sync). Google Play content-rating + sexual-content policy generally require an accurate maturity rating and may require an age gate. _(Static finding — 2026-06-28 QA-plan gap review; confirm against current Play policy + intended content rating.)_ | Add an 18+/age-appropriate gate where required + complete the Play content/maturity questionnaire to match actual content. **Pre-ship gate** (does not block per-round flawless). | **Open (pre-ship)** |
|
| O-AGE-001 | P2 | Release / store readiness (Pass O) | **No age gate / age verification despite adult-intimacy content.** Sign-up collects only email+password+confirm; Create Profile collects name+gender; `domain/model/User.kt` has **no DOB/age field**; the only "birthday" in-app is the *partner's* relationship special-date (`SpecialDatesSection`), not age. Yet the app ships sexual/intimacy content (Desire Sync). Google Play content-rating + sexual-content policy generally require an accurate maturity rating and may require an age gate. _(Static finding — 2026-06-28 QA-plan gap review; confirm against current Play policy + intended content rating.)_ | Add an 18+/age-appropriate gate where required + complete the Play content/maturity questionnaire to match actual content. **Pre-ship gate** (does not block per-round flawless). | **Open (pre-ship)** |
|
||||||
| C-ORIENT-001 | P3 | Visual / config (Pass C/O) | **App not portrait-locked; landscape layout unverified.** `AndroidManifest.xml` declares no `screenOrientation`, so `MainActivity` rotates to landscape — but no round has verified the landscape (or tablet/large-screen, minSdk 26 / targetSdk 35) layout renders correctly. _(Static finding — 2026-06-28 QA-plan gap review.)_ | Decide: **lock portrait** in the manifest if landscape isn't a supported experience, **or** certify the landscape layout (Pass C orientation check). | **Open** |
|
|
||||||
| M-001 | P2 | Settings / notifications | **Quiet hours did not suppress backgrounded/killed partner pushes.** "Quiet hours — 10 PM–8 AM, no notifications" was stored **local-only** (DataStore); partner pushes carry a `notification` block the OS shows directly when the recipient is backgrounded/killed, and the only client check (`PartnerNotificationManager.isInQuietHours`) runs **foreground-only** (`AppMessagingService.onMessageReceived`). So the "no notifications" promise was broken for the main case. Repro: Sam QH ON @22:28 CST, backgrounded → QA chat → "QA sent a message" posted to Sam's shade. | Client mirrors window+tz to `users/{uid}`; Cloud Functions (`onMessageWritten`/`onAnswerWritten`/`onAnswerRevealed`/`onGameSessionUpdate`) suppress via fail-open `notifications/quietHours.ts:recipientInQuietHours()`; `firestore.rules` user-doc allowlist extended for `quietHours*`+`timezone`. | **Fixed — verified live R15** (fn log suppress vs notify; deployed prod). Pending 1 confirm. |
|
|
||||||
| TEST-001 | P2 | QA infra / unit tests | **Unit suite was silently RED (5 failures) — the regression net was non-functional, undetected until R16 ran it for the first time** (test-vs-code drift). (a) `PartnerNotificationManagerTest` (4×) stubbed `quietHoursManager.isInQuietHours(any())`, but the method's default `now: Calendar = Calendar.getInstance()` param made the stub pin the instant captured at stub time → never matched the SUT's call-time clock → `MockKException`. (b) `CloserBrandCopyTest` asserted every privacy message `≤64` chars, which predated the **intentional** 150-char flagship `primaryMessage` (commit `6d74c6a`; `BrandMessageRotator` wraps it at `maxLines=3`). | **Test-side only** (production correct: quiet hours verified live R15; flagship is committed design). Stub → `isInQuietHours(any(), any())`; brand test caps short slogans `≤64` + flagship `1..160`. | **Fixed — verified R16** (`./gradlew testDebugUnitTest` 205 ✅, functions 24 ✅). Pending 1 confirm. |
|
|
||||||
| BRAND-DARK-COVERAGE | P3 | Art / theme | Most illustrations are **light-only** — only 12 of ~25 have a `drawable-night-nodpi/` dark variant. All `illustration_couple_*` heroes (paywall/subscription/onboarding/invite/history), `daily_question`, `partner_activation`, `tonight_partner_prompt`, `together_empty`, and **all 10 `pack_art_*` banners** show the **light/pink image on a dark screen** (feathered edges don't change the image colors). | Generate dark/aubergine-palette variants for each light-only asset → `drawable-night-nodpi/` (identical filename); `BrandIllustration` auto-selects per in-app theme. Re-run the decoupled-theme check. List in `ClaudeBrandingReview.md`. | **Open (P3)** |
|
| BRAND-DARK-COVERAGE | P3 | Art / theme | Most illustrations are **light-only** — only 12 of ~25 have a `drawable-night-nodpi/` dark variant. All `illustration_couple_*` heroes (paywall/subscription/onboarding/invite/history), `daily_question`, `partner_activation`, `tonight_partner_prompt`, `together_empty`, and **all 10 `pack_art_*` banners** show the **light/pink image on a dark screen** (feathered edges don't change the image colors). | Generate dark/aubergine-palette variants for each light-only asset → `drawable-night-nodpi/` (identical filename); `BrandIllustration` auto-selects per in-app theme. Re-run the decoupled-theme check. List in `ClaudeBrandingReview.md`. | **Open (P3)** |
|
||||||
| BRAND-ICON-CUSTOM | P3 | Icons / brand | **~60 distinct generic Material icons** across ~201 call sites (generic hearts `Favorite`/`FavoriteBorder`, `Person`, `Lock`, `Star`, `PlayArrow`, `ArrowBack`, …) — these are placeholders, not the Closer brand. | Replace each with a bespoke `glyph_*` in the house style (`ImageVector.vectorResource` + `Icon(tint)`), highest-traffic first; ship bar = **0 generic Material icons**. Backlog table in `ClaudeBrandingReview.md`. | **FIXED (R18, 2026-06-28)** — all 187 `Icons.*` call sites (~49 distinct) swapped to `glyph_*` via central `ui/components/CloserGlyphs.kt` (+ `CategoryGlyph.kt`); **0 `Icons.*` / 0 material-icon imports remain**; build + 205 tests green; verified live both themes. Pending 1 confirm. |
|
|
||||||
|
|
||||||
## Resolved & confirmed (archived — full detail in git history)
|
## Resolved & confirmed (archived — full detail in git history)
|
||||||
A-001 · A-003 · **A-201** · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · **C-DARKART-001** · **C-DARK-UI-001** · **C-DARK-UI-002** · **C-DARK-UI-003** · C-DS-001 · **C-ART-EDGE-001** · **C-ART-EDGE-002** · **C-HOME-001** · **C-NAV-001** · **C-NAV-002** · **C-NAV-003** · **C-PW-001** · **C-SEC-001** · D-001 · E-001 · E-002 · E-003 · **E-GAME-002** · **E-GAME-003** · E-OBS · F-OBS · F-RACE-001 · **I-001** · **I-002** · **J-OBS** · **N-001** · **N-002** — all fixed and re-verified (R16 pruned **N-001** [Bucket List non-functional → CRUD works; confirmed live add/delete] + **N-002** [Date Builder no-op → Home "Date coming up"; confirmed live]) (R14 pruned the 5 R13 fixes — **C-DARK-UI-001** ToT dark redesign · **C-DARK-UI-002** check-in label/value · **C-DARK-UI-003** bottom-inset clearance · **C-ART-EDGE-002** 8 opaque heroes feathered · **J-OBS** 48dp touch targets — held through R14's full A–J sweep; in working tree) (R13 pruned **A-201** [Date-Match premium ideas ungated → now gated to Paywall via `CouplePremiumChecker`] — fixed R12, confirmed live R13; in working tree) (R12 pruned C-DARKART-001 [in-app-theme `-night` art] + C-ART-EDGE-001 [feathered edges] — fixed R11, held through R12 visual sweep; in working tree) (R11 pruned the 5 R10 P2 fixes — C-HOME-001 single Home card · C-NAV-002 `popUpTo(WHEEL_SESSION){inclusive}` present + R10-live · C-NAV-003 single app bar re-confirmed live · C-PW-001 dark paywall pills legible re-confirmed live · C-SEC-001 recovery row active for accepter re-confirmed live — all committed in `9c84c36`; E-GAME-003 `onGamePartFinished` deployed + committed `2cd0af6`) (E-GAME-002 confirmed live R10: `startNotifiedAt` set + partner_started_game queued to right partner + foreground banner + Join→joined active ToT at same Q1; commits 6e79cd9/38fdc6d) (commits in history; F-RACE-001 re-confirmed R8; **I-001** query→`whereIn(dayKeys)` + **I-002** Long-score→`Number.toInt()`, fixed `ab29f6b`, re-confirmed live R9: 0 outcomes denials/CCE). Pruned per the one-confirmation-round rule. (C-OBS / `outcomes` list / SubscriptionScreen per-user gate = investigated, **not bugs**.)
|
**O-ONBOARD-001** · **C-DARKART-002** · **C-THEME-001** · **C-THEME-002** · **C-THEME-004** · **C-THEME-005** · **C-THEME-008** · **C-THEME-009** · **M-001** · **TEST-001** · **TEST-002** · **P-GRAMMAR-001** · **BucketList-FAB** · **BRAND-ICON-CUSTOM** · **C-ORIENT-001** · A-001 · A-003 · **A-201** · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · **C-DARKART-001** · **C-DARK-UI-001** · **C-DARK-UI-002** · **C-DARK-UI-003** · C-DS-001 · **C-ART-EDGE-001** · **C-ART-EDGE-002** · **C-HOME-001** · **C-NAV-001** · **C-NAV-002** · **C-NAV-003** · **C-PW-001** · **C-SEC-001** · D-001 · E-001 · E-002 · E-003 · **E-GAME-002** · **E-GAME-003** · E-OBS · F-OBS · F-RACE-001 · **I-001** · **I-002** · **J-OBS** · **N-001** · **N-002** — all fixed and re-verified (R16 pruned **N-001** [Bucket List non-functional → CRUD works; confirmed live add/delete] + **N-002** [Date Builder no-op → Home "Date coming up"; confirmed live]) (R14 pruned the 5 R13 fixes — **C-DARK-UI-001** ToT dark redesign · **C-DARK-UI-002** check-in label/value · **C-DARK-UI-003** bottom-inset clearance · **C-ART-EDGE-002** 8 opaque heroes feathered · **J-OBS** 48dp touch targets — held through R14's full A–J sweep; in working tree) (R13 pruned **A-201** [Date-Match premium ideas ungated → now gated to Paywall via `CouplePremiumChecker`] — fixed R12, confirmed live R13; in working tree) (R12 pruned C-DARKART-001 [in-app-theme `-night` art] + C-ART-EDGE-001 [feathered edges] — fixed R11, held through R12 visual sweep; in working tree) (R11 pruned the 5 R10 P2 fixes — C-HOME-001 single Home card · C-NAV-002 `popUpTo(WHEEL_SESSION){inclusive}` present + R10-live · C-NAV-003 single app bar re-confirmed live · C-PW-001 dark paywall pills legible re-confirmed live · C-SEC-001 recovery row active for accepter re-confirmed live — all committed in `9c84c36`; E-GAME-003 `onGamePartFinished` deployed + committed `2cd0af6`) (E-GAME-002 confirmed live R10: `startNotifiedAt` set + partner_started_game queued to right partner + foreground banner + Join→joined active ToT at same Q1; commits 6e79cd9/38fdc6d) (commits in history; F-RACE-001 re-confirmed R8; **I-001** query→`whereIn(dayKeys)` + **I-002** Long-score→`Number.toInt()`, fixed `ab29f6b`, re-confirmed live R9: 0 outcomes denials/CCE). Pruned per the one-confirmation-round rule. (C-OBS / `outcomes` list / SubscriptionScreen per-user gate = investigated, **not bugs**.) (**R18b pruned the full remaining backlog** — O-ONBOARD-001 · C-DARKART-002 · 6× C-THEME · M-001 · TEST-001 · TEST-002 · P-GRAMMAR-001 · BucketList-FAB · BRAND-ICON-CUSTOM · C-ORIENT-001 [portrait lock] — all verified live in their fix rounds + re-confirmed via 208 unit/24 functions green this round; in working tree, user commits.)
|
||||||
|
|
||||||
## Security cornerstone — clean (Pass D, deep dive, Round 7)
|
## Security cornerstone — clean (Pass D, deep dive, Round 7)
|
||||||
- **R17 re-verified (live, admin + raw-API):** **D1 at-rest** — messages `text`, `lastMessagePreview`, Memory Lane capsules (content+title), all 4 game answers (this_or_that/desire_sync/how_well/wheel), date_swipe `action` → all `enc:v1:`; only metadata clear. **D3 negative access** — minted non-member token → raw Firestore REST: couple doc / messages / capsules / desire_sync reads + premium self-grant **all DENIED 403**. Scripts: `scratchpad/d1_atrest.js`, `d1_probe3.js`, `d3_negative.js`.
|
- **R17 re-verified (live, admin + raw-API):** **D1 at-rest** — messages `text`, `lastMessagePreview`, Memory Lane capsules (content+title), all 4 game answers (this_or_that/desire_sync/how_well/wheel), date_swipe `action` → all `enc:v1:`; only metadata clear. **D3 negative access** — minted non-member token → raw Firestore REST: couple doc / messages / capsules / desire_sync reads + premium self-grant **all DENIED 403**. Scripts: `scratchpad/d1_atrest.js`, `d1_probe3.js`, `d3_negative.js`.
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:theme="@style/Theme.Closer.Splash">
|
android:theme="@style/Theme.Closer.Splash">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
||||||
|
|
@ -792,14 +792,14 @@ private fun ChoicePromptBackdrop(
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val dark = isCloserDarkTheme()
|
val dark = isCloserDarkTheme()
|
||||||
val cardA = MaterialTheme.colorScheme.primary.copy(alpha = if (dark) 0.16f else 0.12f)
|
val cardA = MaterialTheme.colorScheme.primary.copy(alpha = if (dark) 0.08f else 0.12f)
|
||||||
val cardB = MaterialTheme.colorScheme.secondary.copy(alpha = if (dark) 0.16f else 0.12f)
|
val cardB = MaterialTheme.colorScheme.secondary.copy(alpha = if (dark) 0.08f else 0.12f)
|
||||||
val glow = MaterialTheme.colorScheme.primary.copy(alpha = if (dark) 0.12f else 0.08f)
|
val glow = MaterialTheme.colorScheme.primary.copy(alpha = if (dark) 0.04f else 0.08f)
|
||||||
Canvas(modifier = modifier) {
|
Canvas(modifier = modifier) {
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = glow,
|
color = glow,
|
||||||
radius = size.minDimension * 0.62f,
|
radius = size.minDimension * if (dark) 0.50f else 0.62f,
|
||||||
center = Offset(size.width * 0.5f, size.height * 0.40f)
|
center = Offset(size.width * 0.5f, size.height * if (dark) 0.48f else 0.40f)
|
||||||
)
|
)
|
||||||
val cardW = size.width * 0.30f
|
val cardW = size.width * 0.30f
|
||||||
val cardH = size.minDimension * 0.30f
|
val cardH = size.minDimension * 0.30f
|
||||||
|
|
@ -961,6 +961,7 @@ private fun WaitingForRevealScreen(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onAbandon: () -> Unit = {}
|
onAbandon: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val cs = MaterialTheme.colorScheme
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -975,14 +976,14 @@ private fun WaitingForRevealScreen(
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.size(104.dp),
|
modifier = Modifier.size(104.dp),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = CloserPalette.PinkMist,
|
color = cs.secondaryContainer,
|
||||||
shadowElevation = 8.dp
|
shadowElevation = 8.dp
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
Text(
|
Text(
|
||||||
text = "✓",
|
text = "✓",
|
||||||
style = MaterialTheme.typography.displaySmall,
|
style = MaterialTheme.typography.displaySmall,
|
||||||
color = CloserPalette.PurpleDeep
|
color = cs.onSecondaryContainer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1100,10 +1101,11 @@ private fun ThisOrThatReveal(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MatchScoreBadge(matched: Int, total: Int) {
|
private fun MatchScoreBadge(matched: Int, total: Int) {
|
||||||
|
val cs = MaterialTheme.colorScheme
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.size(116.dp),
|
modifier = Modifier.size(116.dp),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = CloserPalette.PurpleMist,
|
color = cs.primaryContainer,
|
||||||
shadowElevation = 8.dp
|
shadowElevation = 8.dp
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
|
@ -1111,12 +1113,12 @@ private fun MatchScoreBadge(matched: Int, total: Int) {
|
||||||
Text(
|
Text(
|
||||||
text = "$matched/$total",
|
text = "$matched/$total",
|
||||||
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold),
|
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold),
|
||||||
color = CloserPalette.PurpleDeep
|
color = cs.onPrimaryContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "in sync",
|
text = "in sync",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = CloserPalette.PurpleDeep.copy(alpha = 0.8f)
|
color = cs.onPrimaryContainer.copy(alpha = 0.82f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1125,6 +1127,7 @@ private fun MatchScoreBadge(matched: Int, total: Int) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RevealRow(card: RevealCard, partnerName: String) {
|
private fun RevealRow(card: RevealCard, partnerName: String) {
|
||||||
|
val cs = MaterialTheme.colorScheme
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
|
@ -1151,14 +1154,13 @@ private fun RevealRow(card: RevealCard, partnerName: String) {
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(999.dp),
|
shape = RoundedCornerShape(999.dp),
|
||||||
color = if (card.agreed) CloserPalette.Evergreen.copy(alpha = 0.15f)
|
color = if (card.agreed) cs.tertiaryContainer else cs.secondaryContainer
|
||||||
else CloserPalette.PinkMist
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (card.agreed) "Match" else "Differ",
|
text = if (card.agreed) "Match" else "Differ",
|
||||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = if (card.agreed) CloserPalette.Evergreen else CloserPalette.PinkAccentDeep,
|
color = if (card.agreed) cs.onTertiaryContainer else cs.onSecondaryContainer,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1170,13 +1172,13 @@ private fun RevealRow(card: RevealCard, partnerName: String) {
|
||||||
PickChip(
|
PickChip(
|
||||||
label = "You",
|
label = "You",
|
||||||
text = card.myText,
|
text = card.myText,
|
||||||
accent = CloserPalette.PurpleDeep,
|
accent = cs.primary,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
PickChip(
|
PickChip(
|
||||||
label = partnerName,
|
label = partnerName,
|
||||||
text = card.partnerText,
|
text = card.partnerText,
|
||||||
accent = CloserPalette.PinkAccentDeep,
|
accent = cs.secondary,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1191,10 +1193,13 @@ private fun PickChip(
|
||||||
accent: Color,
|
accent: Color,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val cs = MaterialTheme.colorScheme
|
||||||
|
val dark = isCloserDarkTheme()
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
color = accent.copy(alpha = 0.08f)
|
color = if (dark) accent.copy(alpha = 0.16f) else accent.copy(alpha = 0.08f),
|
||||||
|
border = if (dark) BorderStroke(1.dp, accent.copy(alpha = 0.30f)) else null
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(10.dp),
|
modifier = Modifier.padding(10.dp),
|
||||||
|
|
@ -1211,7 +1216,7 @@ private fun PickChip(
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Medium),
|
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Medium),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = cs.onSurface,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue