app: enrollment feedback beside its own button
The enrollment result was written to the same status line as everything else, which renders at the far end of the server card below three text fields — and on a fresh install renders nowhere at all, because that line only appears once a run exists. So enrolling looked identical whether it worked or not. It has its own line now, directly under the Enroll button that caused it, and its own state rather than sharing one with "Check server": two actions, two results. The server fields were also re-read the instant the button was pressed, before the enrollment coroutine had done anything, so they showed the previous server's values. They now refresh when the result lands, which is the point at which there is something new to show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
082a2314ef
commit
bc384531e6
@@ -200,6 +200,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
onSignOut = vm::signOut,
|
onSignOut = vm::signOut,
|
||||||
onEnroll = vm::enroll,
|
onEnroll = vm::enroll,
|
||||||
serverStatus = vm.state.archiveStatus,
|
serverStatus = vm.state.archiveStatus,
|
||||||
|
enrollStatus = vm.state.enrollStatus,
|
||||||
onBack = { screen = Screen.RUN },
|
onBack = { screen = Screen.RUN },
|
||||||
)
|
)
|
||||||
Screen.HISTORY -> HistoryScreen(
|
Screen.HISTORY -> HistoryScreen(
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ data class UiState(
|
|||||||
val archiveStatus: String? = null,
|
val archiveStatus: String? = null,
|
||||||
/** History, newest first. Refreshed after every run and whenever the history screen opens. */
|
/** History, newest first. Refreshed after every run and whenever the history screen opens. */
|
||||||
val history: List<app.echo_lot.archive.ArchivedRun> = emptyList(),
|
val history: List<app.echo_lot.archive.ArchivedRun> = emptyList(),
|
||||||
|
/**
|
||||||
|
* Result of the last enrollment attempt, shown beside the Enroll button.
|
||||||
|
*
|
||||||
|
* Separate from [archiveStatus]: they are two different actions with two different results,
|
||||||
|
* and sharing one line put the answer to "did enrolling work" at the far end of the card,
|
||||||
|
* below three text fields — or nowhere at all on a fresh install, since that line only
|
||||||
|
* renders once a run exists.
|
||||||
|
*/
|
||||||
|
val enrollStatus: String? = null,
|
||||||
/** An enrollment link waiting on confirmation, because this device is already enrolled. */
|
/** An enrollment link waiting on confirmation, because this device is already enrolled. */
|
||||||
val pendingEnroll: PendingEnroll? = null,
|
val pendingEnroll: PendingEnroll? = null,
|
||||||
/** Shell-tier readiness, shown before a run; null message = say nothing (Shizuku not installed). */
|
/** Shell-tier readiness, shown before a run; null message = say nothing (Shizuku not installed). */
|
||||||
@@ -295,18 +304,18 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
fun cancelEnroll() {
|
fun cancelEnroll() {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
pendingEnroll = null,
|
pendingEnroll = null,
|
||||||
archiveStatus = "kept the existing enrollment; nothing changed",
|
enrollStatus = "Kept the existing enrollment; nothing changed.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doEnroll(link: String, deviceName: String?) {
|
private fun doEnroll(link: String, deviceName: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
state = state.copy(archiveStatus = "enrolling …")
|
state = state.copy(enrollStatus = "Enrolling …")
|
||||||
val result = withContext(Dispatchers.IO) { store.enroll(link, deviceName) }
|
val result = withContext(Dispatchers.IO) { store.enroll(link, deviceName) }
|
||||||
// A new server means a new canary zone; the old one would describe somebody else's
|
// A new server means a new canary zone; the old one would describe somebody else's
|
||||||
// deployment. Cleared rather than kept, and relearned from the next profile fetch.
|
// deployment. Cleared rather than kept, and relearned from the next profile fetch.
|
||||||
settings.canaryZone = ""
|
settings.canaryZone = ""
|
||||||
state = state.copy(archiveStatus = result)
|
state = state.copy(enrollStatus = result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ fun SettingsScreen(
|
|||||||
onSignOut: () -> Unit,
|
onSignOut: () -> Unit,
|
||||||
onEnroll: (String) -> Unit,
|
onEnroll: (String) -> Unit,
|
||||||
serverStatus: String?,
|
serverStatus: String?,
|
||||||
|
enrollStatus: String?,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
// SharedPreferences is not observable, so mirror each value into Compose state and write
|
// SharedPreferences is not observable, so mirror each value into Compose state and write
|
||||||
@@ -69,6 +70,14 @@ fun SettingsScreen(
|
|||||||
var serverUrl by remember { mutableStateOf(settings.serverUrl) }
|
var serverUrl by remember { mutableStateOf(settings.serverUrl) }
|
||||||
var serverPin by remember { mutableStateOf(settings.serverPin) }
|
var serverPin by remember { mutableStateOf(settings.serverPin) }
|
||||||
var serverCred by remember { mutableStateOf(settings.serverCredential) }
|
var serverCred by remember { mutableStateOf(settings.serverCredential) }
|
||||||
|
// Enrolling is asynchronous, so these are re-read when its result lands rather than when the
|
||||||
|
// button is pressed — reading them immediately showed the previous server's values and looked
|
||||||
|
// exactly like an enrollment that had silently done nothing.
|
||||||
|
androidx.compose.runtime.LaunchedEffect(enrollStatus) {
|
||||||
|
serverUrl = settings.serverUrl
|
||||||
|
serverPin = settings.serverPin
|
||||||
|
serverCred = settings.serverCredential
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().safeDrawingPadding().verticalScroll(rememberScrollState()).padding(16.dp),
|
Modifier.fillMaxWidth().safeDrawingPadding().verticalScroll(rememberScrollState()).padding(16.dp),
|
||||||
@@ -218,12 +227,14 @@ fun SettingsScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
onEnroll(enrollLink)
|
onEnroll(enrollLink)
|
||||||
enrollLink = "" // spent either way; leaving it around invites a retry
|
enrollLink = "" // spent either way; leaving it around invites a retry
|
||||||
serverUrl = settings.serverUrl
|
|
||||||
serverPin = settings.serverPin
|
|
||||||
serverCred = settings.serverCredential
|
|
||||||
},
|
},
|
||||||
enabled = enrollLink.isNotBlank(),
|
enabled = enrollLink.isNotBlank(),
|
||||||
) { Text("Enroll") }
|
) { Text("Enroll") }
|
||||||
|
// Beside the button that caused it. Enrolling is asynchronous, so without this the
|
||||||
|
// only sign of success is three fields quietly changing further down the card.
|
||||||
|
enrollStatus?.let {
|
||||||
|
Text(it, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = serverUrl, onValueChange = { serverUrl = it; settings.serverUrl = it },
|
value = serverUrl, onValueChange = { serverUrl = it; settings.serverUrl = it },
|
||||||
|
|||||||
Reference in New Issue
Block a user