Skip to content

feat(ui): add Stub Scanner screen + route (#12)#41

Merged
ilmoniemi merged 3 commits into
mainfrom
feature/12
May 12, 2026
Merged

feat(ui): add Stub Scanner screen + route (#12)#41
ilmoniemi merged 3 commits into
mainfrom
feature/12

Conversation

@ilmoniemi
Copy link
Copy Markdown
Contributor

What

  • New ScannerScreen composable in ui/onboarding/ — full-screen tap-anywhere surface with a viewport box + scan-line indicator + "Tap to pair" affordance. No real camera (CameraX + ML Kit are Phase 4).
  • New Routes.Scanner = "scanner" constant and composable(Routes.Scanner) { ... } block in MainActivity.kt. On tap: appPreferences.setPairedServerExists(true) then navigate to channel_list with popUpTo(Routes.Scanner) { inclusive = true } + launchSingleTop = true.
  • Re-wired Welcome's onPaired to navigate to Routes.Scanner instead of straight to Routes.ChannelList.
  • Light + dark @Preview functions on ScannerScreen.

Issue

Closes #12

Testing

  • ./gradlew assembleDebug — passes
  • ./gradlew test — passes (no new unit tests; matches the precedent set by Welcome / Settings / ConversationThread placeholders — see spec "Testing strategy")
  • ./gradlew lint — passes
  • ./gradlew connectedAndroidTest — not run (no device/emulator attached in this environment)

Architecture compliance

  • Matches WelcomeScreen.kt shape exactly: outer Surface(color = background, fillMaxSize), inner Column with systemBarsPadding(), two widthDp = 412, heightDp = 892 previews using PyrycodeMobileTheme.
  • AppPreferences resolved via koinInject<AppPreferences>() (org.koin.compose) inside the composable(...) block — no ViewModel for a stub that has no observable state and will be replaced wholesale in Phase 4.
  • rememberCoroutineScope() for the suspend write; sequential write-then-navigate so pairedServerExists is flipped before the next destination composes (matters once feat(ui): conditional NavHost start destination based on paired-server-exists #13 lands).
  • pointerInput(Unit) { detectTapGestures { onTap() } } for tap-anywhere with no ripple — Modifier.clickable would fight the camera-viewport metaphor with a Material ripple over the whole screen.
  • popUpTo(Routes.Scanner) { inclusive = true } satisfies AC3 (back-press from ChannelList does not return to Scanner). Welcome is intentionally left in the back stack — feat(ui): conditional NavHost start destination based on paired-server-exists #13 owns the conditional start destination.

🤖 Generated with Claude Code

ilmoniemi and others added 2 commits May 12, 2026 21:00
Adds a placeholder Scanner screen that stands in for CameraX + ML Kit
pairing (Phase 4). Tap anywhere → flips pairedServerExists via
AppPreferences and navigates to channel_list with the scanner popped
from the back stack. Welcome's "I already have pyrycode" now routes
through scanner instead of directly to channel_list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ilmoniemi
Copy link
Copy Markdown
Contributor Author

Code Review: #12

Decision: PASS

Findings

None blocking. The implementation matches the architect's spec verbatim — route constant, Welcome re-wire, composable(Routes.Scanner) block (with koinInject<AppPreferences>() + rememberCoroutineScope() + sequential setPairedServerExists(true)navigate(... popUpTo(Routes.Scanner) { inclusive = true }, launchSingleTop = true)), and ScannerScreen.kt composition shape (Surface + pointerInput on the outer Surface, viewport Box with aspectRatio(1f) + surfaceVariant, scan-line, "Tap to pair" affordance, light + dark previews).

Spot checks:

  • Material 3 tokens — every color and typography reference goes through MaterialTheme.colorScheme.* / MaterialTheme.typography.*. No hardcoded Color(0xFF...) or TextStyle() defaults. ✓
  • Recomposition correctnesspointerInput(Unit) keyed on Unit is intentional and documented in the spec's open-questions section; onTap is bound to a stable navigation closure. ✓
  • CoroutinesrememberCoroutineScope() (not GlobalScope, not a fresh CoroutineScope), sequential await before navigation so the preference flip is durable before feat(ui): conditional NavHost start destination based on paired-server-exists #13's collector reads it. ✓
  • Back stackpopUpTo(Routes.Scanner) { inclusive = true } + launchSingleTop = true satisfies AC3 ("scanner removed from the back stack") and guards against double-tap stacking. ✓
  • Accessibility — whole-screen tap target with a visible "Tap to pair" label; no icon-only elements that would need contentDescription. ✓
  • Edge-to-edgepointerInput lives on the Surface (catches taps in the inset region), systemBarsPadding() lives on the inner Column (content avoids the status-bar overlap). Matches WelcomeScreen.kt. ✓
  • Previews — both render in isolation (no Koin/NavController/coroutine dependencies in the composable body). ✓
  • Tests — none required per the spec's testing-strategy section, which aligns with the WelcomeScreen / Settings / ConversationThread placeholder precedent in this Phase-0 codebase. ✓

Summary

Clean, minimal, spec-faithful stub. Phase 4 will replace the whole composable(Routes.Scanner) { ... } block plus ScannerScreen.kt wholesale (CameraX + ML Kit); the Routes.Scanner constant is the only piece that survives. No premature abstractions anticipating the rewrite — appropriate. Ready to merge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ui): add Stub Scanner screen + route

1 participant