feat(ui): add Stub Scanner screen + route (#12)#41
Conversation
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>
Code Review: #12Decision: PASS FindingsNone blocking. The implementation matches the architect's spec verbatim — route constant, Welcome re-wire, Spot checks:
SummaryClean, minimal, spec-faithful stub. Phase 4 will replace the whole |
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What
ScannerScreencomposable inui/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).Routes.Scanner = "scanner"constant andcomposable(Routes.Scanner) { ... }block inMainActivity.kt. On tap:appPreferences.setPairedServerExists(true)then navigate tochannel_listwithpopUpTo(Routes.Scanner) { inclusive = true }+launchSingleTop = true.onPairedto navigate toRoutes.Scannerinstead of straight toRoutes.ChannelList.@Previewfunctions onScannerScreen.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
WelcomeScreen.ktshape exactly: outerSurface(color = background, fillMaxSize), innerColumnwithsystemBarsPadding(), twowidthDp = 412, heightDp = 892previews usingPyrycodeMobileTheme.AppPreferencesresolved viakoinInject<AppPreferences>()(org.koin.compose) inside thecomposable(...)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 sopairedServerExistsis 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.clickablewould 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