GET /usage serves the numbers behind Claude Code's /usage, read with the CLI's own stored OAuth credentials (nothing to configure). The endpoint is undocumented, so parsing is defensive -- the generic limits[] array becomes labeled window bars, unknown kinds surface under their raw name, and any failure degrades to an 'unavailable' snapshot with the reason. One UsageProvider per paid service behind a caching monitor that enforces the >=180s minimum poll regardless of phone refreshes; no background polling at all. ureq (rustls) does the outbound call, with the process-level CryptoProvider now chosen explicitly in main -- ureq brings ring while axum-server brings aws-lc-rs, and with both in the graph rustls refuses to guess. App: a Usage screen off the session list -- per-window bars colored by utilization with relative reset times. Verified live: 74%/26%/16% windows rendered against the real endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
104 lines
3.5 KiB
Kotlin
104 lines
3.5 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import androidx.activity.compose.BackHandler
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
/**
|
|
* One `when` rather than a navigation library: four screens, with the list
|
|
* as the root and the back button the only other way between them.
|
|
*/
|
|
private sealed class Screen {
|
|
data object SessionList : Screen()
|
|
data class Session(val summary: SessionSummary) : Screen()
|
|
data object Spawn : Screen()
|
|
data object Usage : Screen()
|
|
data object Settings : Screen()
|
|
}
|
|
|
|
/**
|
|
* [settingsVersion] bumps when enrollment lands via an `aiapp://` intent
|
|
* (see MainActivity), re-reading the stored settings -- a plain `remember`
|
|
* would keep serving the pre-enrollment null.
|
|
*/
|
|
@Composable
|
|
fun AppRoot(settingsVersion: Int) {
|
|
val context = LocalContext.current
|
|
var settings by remember(settingsVersion) { mutableStateOf(loadServerSettings(context)) }
|
|
var screen by remember { mutableStateOf<Screen>(Screen.SessionList) }
|
|
// Bumped whenever another screen changes something the list shows, so
|
|
// returning to it refetches instead of showing a stale list.
|
|
var reloadToken by remember { mutableStateOf(0) }
|
|
|
|
val current = settings
|
|
if (current == null) {
|
|
// Not enrolled yet: settings is the only usable screen. The QR
|
|
// path lands in MainActivity and recomposes from the top.
|
|
SettingsScreen(
|
|
existing = null,
|
|
onSaved = { saved ->
|
|
settings = saved
|
|
screen = Screen.SessionList
|
|
},
|
|
onBack = null,
|
|
)
|
|
return
|
|
}
|
|
|
|
when (val here = screen) {
|
|
is Screen.SessionList -> SessionListScreen(
|
|
settings = current,
|
|
reloadToken = reloadToken,
|
|
onOpen = { screen = Screen.Session(it) },
|
|
onSpawn = { screen = Screen.Spawn },
|
|
onUsage = { screen = Screen.Usage },
|
|
onSettings = { screen = Screen.Settings },
|
|
)
|
|
is Screen.Session -> {
|
|
BackHandler {
|
|
reloadToken++
|
|
screen = Screen.SessionList
|
|
}
|
|
SessionScreen(
|
|
settings = current,
|
|
summary = here.summary,
|
|
onBack = {
|
|
reloadToken++
|
|
screen = Screen.SessionList
|
|
},
|
|
)
|
|
}
|
|
is Screen.Spawn -> {
|
|
BackHandler { screen = Screen.SessionList }
|
|
SpawnScreen(
|
|
settings = current,
|
|
onSpawned = { spawned ->
|
|
reloadToken++
|
|
screen = Screen.Session(spawned)
|
|
},
|
|
onBack = { screen = Screen.SessionList },
|
|
)
|
|
}
|
|
is Screen.Usage -> {
|
|
BackHandler { screen = Screen.SessionList }
|
|
UsageScreen(settings = current, onBack = { screen = Screen.SessionList })
|
|
}
|
|
is Screen.Settings -> {
|
|
BackHandler { screen = Screen.SessionList }
|
|
SettingsScreen(
|
|
existing = current,
|
|
onSaved = { saved ->
|
|
settings = saved
|
|
reloadToken++
|
|
screen = Screen.SessionList
|
|
},
|
|
onBack = { screen = Screen.SessionList },
|
|
)
|
|
}
|
|
}
|
|
}
|