One load state for the two screens that had the same one twice

`ListState` and `UsageState` were the same three cases -- Loading, Loaded,
Error -- differing only in what Loaded carried, which is the shape rule 17
asks to parameterize rather than copy. They are now one `LoadState<T>`,
covariant so a single `LoadState.Loading` serves both.

Worth keeping as a type rather than a value beside a nullable error: it is
what stops "we couldn't find out" from sharing a representation with
"there is nothing", so a failed fetch cannot render as an empty list.

`LoadState.failed(e)` also collects the `e.message ?: "Unknown error"` both
screens were spelling out, so there is one answer to what an ApiException
looks like on screen instead of one per caller.

No behaviour change. Verified on the emulator against a real ai-server, not
just compiled: the list empty, the list with two sessions, usage with three
real windows, and both screens' error state with the server stopped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-28 03:21:42 -04:00
1 parent c12ab7f098
commit 56882d5fa3
3 files changed
+49 -29

No files matched your search

@@ -35,25 +35,19 @@ import java.time.OffsetDateTime
private val WARN_COLOR = Color(0xFFB26A00)
private val OVER_COLOR = Color(0xFFB3261E)
private sealed class UsageState {
data object Loading : UsageState()
data class Loaded(val snapshots: List<UsageSnapshot>) : UsageState()
data class Error(val message: String) : UsageState()
}
/** Window bars for the account's rate limits, with reset times. */
@Composable
fun UsageScreen(settings: ServerSettings, onBack: () -> Unit) {
val scope = rememberCoroutineScope()
var state by remember { mutableStateOf<UsageState>(UsageState.Loading) }
var state by remember { mutableStateOf<LoadState<List<UsageSnapshot>>>(LoadState.Loading) }
fun refresh() {
state = UsageState.Loading
state = LoadState.Loading
scope.launch {
state = try {
withContext(Dispatchers.IO) { UsageState.Loaded(fetchUsage(settings)) }
withContext(Dispatchers.IO) { LoadState.Loaded(fetchUsage(settings)) }
} catch (e: ApiException) {
UsageState.Error(e.message ?: "Unknown error")
LoadState.failed(e)
}
}
}
@@ -72,9 +66,9 @@ fun UsageScreen(settings: ServerSettings, onBack: () -> Unit) {
Spacer(Modifier.height(16.dp))
when (val current = state) {
is UsageState.Loading -> CircularProgressIndicator()
is UsageState.Error -> Text(current.message, color = MaterialTheme.colorScheme.error)
is UsageState.Loaded -> current.snapshots.forEach { snapshot ->
is LoadState.Loading -> CircularProgressIndicator()
is LoadState.Error -> Text(current.message, color = MaterialTheme.colorScheme.error)
is LoadState.Loaded -> current.value.forEach { snapshot ->
Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp)) {
Text(snapshot.provider, style = MaterialTheme.typography.titleMedium)