Phase 1 app: session list, session screen, spawn, QR enrollment over pinned TLS
Compose app mirroring local-updater's stack (single :androidApp module, pinned CA, HttpURLConnection transport) plus what this app needs on top: a bearer token sealed with an Android Keystore AES-GCM key, an aiapp://enroll intent filter so scanning the server's terminal QR with the stock camera enrolls the phone with no QR library, an SSE client that resumes by transcript cursor, and a transcript renderer folding the common event model into user bubbles, streaming text, collapsible tool cards, and answerable question cards. Verified on the tdep emulator against the real server: enrollment deep link, list, spawn, streamed echo turn, question answer round trip, tool card expansion, adjustResize keyboard behavior. Build is warning-clean (compose.* accessors replaced with direct dependencies). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
967fc814ab
commit
213bc72b64
24 files changed
+2086
No files matched your search
@@ -0,0 +1,97 @@
|
||||
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 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 },
|
||||
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.Settings -> {
|
||||
BackHandler { screen = Screen.SessionList }
|
||||
SettingsScreen(
|
||||
existing = current,
|
||||
onSaved = { saved ->
|
||||
settings = saved
|
||||
reloadToken++
|
||||
screen = Screen.SessionList
|
||||
},
|
||||
onBack = { screen = Screen.SessionList },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user