Phone card: numbered steps and a two-minute countdown that renews the code (from release 0.108.49)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -179,28 +179,62 @@ private fun OrbitField(value: String, onChange: (String) -> Unit, hint: String,
|
||||
}
|
||||
}
|
||||
|
||||
/** On your phone: a QR code and a code to type, both one-time, while this screen shows. */
|
||||
/**
|
||||
* On your phone: a QR code and a code to type, both one-time, while this screen shows. Laid out as numbered steps (Paul,
|
||||
* 6 November 2026: entering the code and then the PIN was confusing), with a two-minute countdown; when it runs out the
|
||||
* code and QR code are replaced by new ones on their own, until a phone has joined.
|
||||
*/
|
||||
@Composable
|
||||
private fun PhoneCard(modifier: Modifier) {
|
||||
val pairing = remember { PhoneLink.beginPairing() }
|
||||
val url = remember { PhoneLink.pairingUrl(pairing) }
|
||||
var pairing by remember { mutableStateOf(PhoneLink.beginPairing()) }
|
||||
val url = remember(pairing) { PhoneLink.pairingUrl(pairing) }
|
||||
val qr = remember(url) { url?.let { qrBitmap(it, 360) } }
|
||||
Column(modifier.background(Panel, RoundedCornerShape(12.dp)).padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val joined = PhoneLink.active
|
||||
var left by remember { mutableLongStateOf(pairing.untilMs - System.currentTimeMillis()) }
|
||||
LaunchedEffect(pairing, joined) {
|
||||
while (joined == null) {
|
||||
left = pairing.untilMs - System.currentTimeMillis()
|
||||
if (left <= 0) { pairing = PhoneLink.beginPairing(); return@LaunchedEffect }
|
||||
kotlinx.coroutines.delay(1_000)
|
||||
}
|
||||
}
|
||||
Column(modifier.background(Panel, RoundedCornerShape(12.dp)).padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("On your phone", color = Ink, fontFamily = Anton, fontSize = 22.sp)
|
||||
if (url == null || qr == null) { Text("This TV isn't on a home Wi-Fi network, so a phone can't reach it.", color = Muted, fontSize = 12.sp); return@Column }
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(14.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
androidx.compose.foundation.Image(qr, "QR code for the phone page", Modifier.size(130.dp).background(Color.White, RoundedCornerShape(6.dp)).padding(6.dp))
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text("Scan with your phone's camera", color = Muted, fontSize = 12.sp)
|
||||
Text("or go to", color = Muted, fontSize = 12.sp)
|
||||
Text(url.substringBefore("/join"), color = Ink, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text("and enter", color = Muted, fontSize = 12.sp)
|
||||
Text(pairing.code, Modifier.background(Navy, RoundedCornerShape(6.dp)).padding(horizontal = 10.dp, vertical = 2.dp), color = Yellow, fontFamily = Anton, fontSize = 24.sp)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(14.dp), verticalAlignment = Alignment.Top) {
|
||||
androidx.compose.foundation.Image(qr, "QR code for the phone page", Modifier.size(124.dp).background(Color.White, RoundedCornerShape(6.dp)).padding(6.dp))
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
PhoneStep(1, "Scan the code with your phone's camera") {
|
||||
Text("or go to ${url.substringBefore("/join")} and type", color = Muted, fontSize = 11.sp)
|
||||
Text(pairing.code, Modifier.padding(top = 2.dp).background(Navy, RoundedCornerShape(6.dp)).padding(horizontal = 10.dp, vertical = 1.dp), color = Yellow, fontFamily = Anton, fontSize = 22.sp)
|
||||
}
|
||||
// The phone asks for the After Dark PIN next, also four digits: a step of its own, so it isn't mistaken for the code.
|
||||
if (AfterDarkLock.pinSet) PhoneStep(2, "Enter your Adult PIN on the phone") { Text("The one for After Dark, not the code above", color = Muted, fontSize = 11.sp) }
|
||||
}
|
||||
}
|
||||
Text(PhoneLink.active?.let { "Connected: $it" } ?: "Same Wi-Fi as this TV. The code works once, for two minutes.", color = if (PhoneLink.active != null) Mint else Muted, fontSize = 11.sp)
|
||||
// The phone asks for the After Dark PIN next, which is also four digits: say so here, so it isn't mistaken for this code.
|
||||
if (PhoneLink.active == null && AfterDarkLock.pinSet) Text("Then your phone asks for your Adult PIN, the one for After Dark.", color = Muted, fontSize = 11.sp)
|
||||
if (joined != null) Text("Connected: $joined", color = Mint, fontSize = 12.sp, fontWeight = FontWeight.SemiBold)
|
||||
else {
|
||||
val fraction = (left.coerceIn(0, 120_000) / 120_000f)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Box(Modifier.weight(1f).height(4.dp).background(Navy, RoundedCornerShape(2.dp))) {
|
||||
Box(Modifier.fillMaxWidth(fraction).fillMaxHeight().background(Yellow, RoundedCornerShape(2.dp)))
|
||||
}
|
||||
val seconds = ((left + 999) / 1000).coerceAtLeast(0)
|
||||
Text("New code in %d:%02d".format(seconds / 60, seconds % 60), color = Muted, fontSize = 11.sp)
|
||||
}
|
||||
Text("Same Wi-Fi as this TV. Each code works once.", color = Muted, fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A numbered step on the phone card: its number in a circle, what to do, and any detail under it. */
|
||||
@Composable
|
||||
private fun PhoneStep(number: Int, title: String, detail: @Composable ColumnScope.() -> Unit) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Box(Modifier.size(20.dp).background(Yellow, androidx.compose.foundation.shape.CircleShape), contentAlignment = Alignment.Center) {
|
||||
Text("$number", color = Navy, fontSize = 12.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
Column { Text(title, color = Ink, fontSize = 13.sp, fontWeight = FontWeight.SemiBold); detail() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user