Show the login link as a QR so nobody races the code
All checks were successful
Build TV app / build (push) Successful in 47s
All checks were successful
Build TV app / build (push) Successful in 47s
The identity provider's code lives a minute here, and the screen asked people to type nine digits into a phone within it. That is the wrong argument to have: the phone has a camera. While the login is pending, the QR on the welcome screen points at the provider's confirmation page with the code already in the URL, instead of at this TV's own remote. Scanning ends at "confirm"; the digits stay underneath for whoever prefers them, and the automatic renewal still covers a code that runs out while somebody walks to the phone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user