diff --git a/app/src/main/java/dev/castarr/tv/AppState.kt b/app/src/main/java/dev/castarr/tv/AppState.kt index 5d8f2cd..db62e9a 100644 --- a/app/src/main/java/dev/castarr/tv/AppState.kt +++ b/app/src/main/java/dev/castarr/tv/AppState.kt @@ -190,6 +190,13 @@ class AppState( */ var welcomeCodeRenewed by mutableStateOf(false) + /** + * The identity provider's confirmation link, complete with the code. + * Shown as a QR while the login is pending: scanning it beats typing + * nine digits against a code that lives a minute. + */ + var welcomeLoginUrl by mutableStateOf("") + /** Plain-language fullscreen error state (issue #13). */ var appError by mutableStateOf(AppError.NONE) @@ -410,6 +417,7 @@ class AppState( welcomePhase = WelcomePhase.WAIT_PHONE welcomeUserCode = "" welcomeCodeRenewed = false + welcomeLoginUrl = "" screen = Screen.WELCOME } diff --git a/app/src/main/java/dev/castarr/tv/MainActivity.kt b/app/src/main/java/dev/castarr/tv/MainActivity.kt index d51388b..8139874 100644 --- a/app/src/main/java/dev/castarr/tv/MainActivity.kt +++ b/app/src/main/java/dev/castarr/tv/MainActivity.kt @@ -210,6 +210,7 @@ class MainActivity : ComponentActivity(), ControlServer.Listener { repeat(LOGIN_CODE_ROUNDS) { round -> val session = state.auth.startDeviceFlow() state.welcomeUserCode = session.userCode + state.welcomeLoginUrl = session.verificationUriComplete state.welcomeCodeRenewed = round > 0 state.welcomePhase = AppState.WelcomePhase.WAIT_LOGIN server.broadcastLoginLink(session.verificationUriComplete, session.userCode) @@ -242,6 +243,7 @@ class MainActivity : ComponentActivity(), ControlServer.Listener { server.broadcastToast("Anmeldung abgebrochen — bitte erneut versuchen") state.welcomePhase = AppState.WelcomePhase.WAIT_PHONE state.welcomeUserCode = "" + state.welcomeLoginUrl = "" state.welcomeCodeRenewed = false } catch (e: Exception) { android.util.Log.w("Onboarding", "configure failed: ${e.javaClass.simpleName}") diff --git a/app/src/main/java/dev/castarr/tv/ui/WelcomeScreen.kt b/app/src/main/java/dev/castarr/tv/ui/WelcomeScreen.kt index c2b7ed5..c46012f 100644 --- a/app/src/main/java/dev/castarr/tv/ui/WelcomeScreen.kt +++ b/app/src/main/java/dev/castarr/tv/ui/WelcomeScreen.kt @@ -88,21 +88,40 @@ fun WelcomeScreen(state: AppState) { .background(Color(0xFFFBFCFD)) .padding(18.dp), ) { - val qr = remember(address) { + // While the login is pending the QR points at the + // identity provider's confirmation page instead of at + // this TV: scanning it beats typing nine digits + // against a code that lives a minute. + val target = state.welcomeLoginUrl.ifEmpty { + Pairing.pairingUrl(context, address) + } + val qr = remember(address, target) { Qr.encode( - Pairing.pairingUrl(context, address), + target, 520, android.graphics.Color.parseColor("#101216"), ) } - Image(qr.asImageBitmap(), contentDescription = "Einrichtungs-QR", modifier = Modifier.size(230.dp)) + Image( + qr.asImageBitmap(), + contentDescription = if (state.welcomeLoginUrl.isEmpty()) { + "Einrichtungs-QR" + } else { + "Anmelde-QR" + }, + modifier = Modifier.size(230.dp), + ) } Spacer(Modifier.height(12.dp)) Text( // Both fallbacks in one line: the address for a phone // whose camera will not scan, and the code the remote // asks for when the QR was not what opened it. - "${Pairing.remoteUrl(address)} · Code ${Pairing.code(context)}", + if (state.welcomeLoginUrl.isEmpty()) { + "${Pairing.remoteUrl(address)} · Code ${Pairing.code(context)}" + } else { + "Scannen und bestätigen — oder Code ${state.welcomeUserCode} eingeben" + }, color = CastarrColors.faint, fontFamily = AppFont, fontSize = 13.sp, ) }