Merge remote-tracking branch 'origin/main'

This commit is contained in:
iris committed 2026-08-29 23:52:58 -04:00
commit dde592ecb7
1 file changed
+28
@@ -12,6 +12,7 @@ import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -20,6 +21,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
/** /**
* The app's root: one title, and four views of the backend behind it. * The app's root: one title, and four views of the backend behind it.
@@ -51,6 +55,30 @@ fun MainScreen(
var tab by remember { mutableStateOf(MainTab.Sessions) } var tab by remember { mutableStateOf(MainTab.Sessions) }
var refreshToken by remember { mutableIntStateOf(0) } var refreshToken by remember { mutableIntStateOf(0) }
// Coming back to the app asks again, on whichever tab is showing.
//
// What these four draw is a snapshot of a backend they are not connected to, so it is only as
// fresh as the last answer -- and a *failed* answer is the one that outstays its welcome. A
// phone that was away while the tunnel was down, or that fetched before the network came up,
// came back to "Couldn't reach the server" sitting at the top of a list the server would now
// answer for perfectly well, and nothing took it off until somebody pressed Refresh. A stale
// failure is worse than a stale list: it is a claim about right now.
//
// Through the same token the Refresh button uses, so this is one instruction the tabs already
// understand rather than a second path into each of them -- which is also what makes it cover
// all four rather than the one the report came from.
//
// Not on the first entry: the tab composing already asks, and bumping here would make every
// cold start fetch twice.
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(lifecycleOwner) {
var opening = true
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
if (!opening) refreshToken++
opening = false
}
}
// A tab the app put over the list has to step back to it rather than fall through to the // A tab the app put over the list has to step back to it rather than fall through to the
// system default, which closes the app -- that reads as a crash to somebody who only meant to // system default, which closes the app -- that reads as a crash to somebody who only meant to
// get back to their sessions. Nested inside AppRoot's handler, so it wins while it is enabled. // get back to their sessions. Nested inside AppRoot's handler, so it wins while it is enabled.