diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/MainActivity.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/MainActivity.kt index 04cd61a..603be7e 100644 --- a/echolot-app/app/src/main/kotlin/app/echo_lot/app/MainActivity.kt +++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/MainActivity.kt @@ -200,6 +200,7 @@ class MainActivity : ComponentActivity() { onSignOut = vm::signOut, onEnroll = vm::enroll, serverStatus = vm.state.archiveStatus, + enrollStatus = vm.state.enrollStatus, onBack = { screen = Screen.RUN }, ) Screen.HISTORY -> HistoryScreen( diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/RunViewModel.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/RunViewModel.kt index be3c873..e2114b3 100644 --- a/echolot-app/app/src/main/kotlin/app/echo_lot/app/RunViewModel.kt +++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/RunViewModel.kt @@ -42,6 +42,15 @@ data class UiState( val archiveStatus: String? = null, /** History, newest first. Refreshed after every run and whenever the history screen opens. */ val history: List = 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. */ val pendingEnroll: PendingEnroll? = null, /** 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() { state = state.copy( pendingEnroll = null, - archiveStatus = "kept the existing enrollment; nothing changed", + enrollStatus = "Kept the existing enrollment; nothing changed.", ) } private fun doEnroll(link: String, deviceName: String?) { viewModelScope.launch { - state = state.copy(archiveStatus = "enrolling …") + state = state.copy(enrollStatus = "Enrolling …") 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 // deployment. Cleared rather than kept, and relearned from the next profile fetch. settings.canaryZone = "" - state = state.copy(archiveStatus = result) + state = state.copy(enrollStatus = result) } } diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt index 6ff9a45..7e29dc3 100644 --- a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt +++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt @@ -54,6 +54,7 @@ fun SettingsScreen( onSignOut: () -> Unit, onEnroll: (String) -> Unit, serverStatus: String?, + enrollStatus: String?, onBack: () -> Unit, ) { // 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 serverPin by remember { mutableStateOf(settings.serverPin) } 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( Modifier.fillMaxWidth().safeDrawingPadding().verticalScroll(rememberScrollState()).padding(16.dp), @@ -218,12 +227,14 @@ fun SettingsScreen( onClick = { onEnroll(enrollLink) enrollLink = "" // spent either way; leaving it around invites a retry - serverUrl = settings.serverUrl - serverPin = settings.serverPin - serverCred = settings.serverCredential }, enabled = enrollLink.isNotBlank(), ) { 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( value = serverUrl, onValueChange = { serverUrl = it; settings.serverUrl = it },