From ccd519d540a7ea51b678e18bc64f71bd602376de Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 18:16:33 +0000 Subject: [PATCH] Nav-bar settings for every module, player-resolved downloads, programmed Brick tags Navigation bar - The Settings section still listed only Calendar/Tasks/Inbox/Jarvis; it now lists every module, with a searchable "Add a module as a tab" picker. - Fixed the default bar: an empty stored list meant every destination became a tab. It falls back to Home + Calendar/Tasks/Inbox/Jarvis again. Downloader - Dropped the hand-rolled kt_player (KVS) link decoder. Its output was wrong, which is where the 404s and the zero-length file came from: those links are scrambled by obfuscated per-session JavaScript and bound to the browsing session. - Added PlayerResolver: the page runs in an offscreen WebView, the player is muted and nudged into playing, and the media request it makes is recorded along with the session cookies. Used when the static scan finds a scrambled player or nothing at all. - MediaCandidate carries request headers, and the download engine sends them (Referer, Cookie, User-Agent) for both direct files and HLS segments, which is what those links require. - Teasers, sprites, timeline previews and ad creatives are filtered out of the candidate list. Brick NFC - Pairing now programs the tag: a LifeOS MIME record plus an Android Application Record. That is what makes a tap reach LifeOS with the app closed, and it stops a chooser from appearing. - Tag id is read from that record when present, falling back to the serial. - Broader NDEF, TECH and TAG filters plus a fuller technology list, so tags written by other tools still land in Brick. - Pairing reports whether the tag could be programmed, and an NFC-off banner opens NFC settings. Both places state plainly that Android only dispatches tags while the screen is unlocked. Version 0.1.0-alpha.20. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011kSsXNrSk6NDjM2FjFZ7Y9 --- README.md | 3 + app/build.gradle.kts | 4 +- .../kotlin/com/lifeos/app/ui/LifeOsApp.kt | 9 +- .../lifeos/app/ui/settings/SettingsScreen.kt | 56 ++++-- feature/brick/src/main/AndroidManifest.xml | 12 ++ .../com/lifeos/feature/brick/BrickScreen.kt | 75 ++++++- .../lifeos/feature/brick/BrickViewModel.kt | 17 +- .../com/lifeos/feature/brick/nfc/BrickNfc.kt | 34 ++-- .../feature/brick/nfc/BrickTagWriter.kt | 65 +++++++ .../brick/src/main/res/xml/brick_nfc_tech.xml | 12 ++ .../feature/downloader/DownloaderScreen.kt | 21 +- .../feature/downloader/data/DownloadEngine.kt | 25 ++- .../feature/downloader/data/KvsPlayer.kt | 60 ------ .../feature/downloader/data/MediaExtractor.kt | 74 +++---- .../feature/downloader/data/MediaResolver.kt | 38 ++++ .../feature/downloader/data/PlayerResolver.kt | 184 ++++++++++++++++++ .../feature/downloader/data/SiteCatalog.kt | 8 +- 17 files changed, 540 insertions(+), 157 deletions(-) create mode 100644 feature/brick/src/main/kotlin/com/lifeos/feature/brick/nfc/BrickTagWriter.kt delete mode 100644 feature/downloader/src/main/kotlin/com/lifeos/feature/downloader/data/KvsPlayer.kt create mode 100644 feature/downloader/src/main/kotlin/com/lifeos/feature/downloader/data/MediaResolver.kt create mode 100644 feature/downloader/src/main/kotlin/com/lifeos/feature/downloader/data/PlayerResolver.kt diff --git a/README.md b/README.md index 5813119..0e69ec7 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ and rule traces back to a section (and often a community demand source) there. | Navigation bar: any module can be a bottom-bar tab, not just Calendar/Jarvis/Inbox/Tasks | Done | | Clock timer: tap an h/m/s wheel to type the value on a number keyboard; a full number hops to the field on the right automatically | Done | | Clear Sky Map module: full clearoutside.com forecast for any spot (search, coordinates or device location) - hourly good/OK/bad ratings, total/low/medium/high cloud, visibility, fog, precipitation, wind, temperature, dew point, humidity, pressure, ozone, sun/moon ephemeris, dark windows and estimated sky quality/Bortle class. No API key needed; place lookup via OpenStreetMap Nominatim | Done | +| Navigation bar settings list every module (was still showing only the original four), with a searchable picker; the default bar stays Home + Calendar/Tasks/Inbox/Jarvis | Done | +| Downloader resolves session-bound player links (ThisVid and the rest of the kt_player family) by letting the page's own player run in an offscreen WebView and recording the media request, then downloads it with that session's Referer, Cookie and User-Agent; teaser/sprite/ad URLs are filtered out | Done | +| Brick tags are programmed on pairing (LifeOS MIME record + Android Application Record), so a tap flips a mode with the app closed and without a chooser; broader NDEF/TECH/TAG filters, tag id read from the record, and an NFC-off banner that opens NFC settings | Done | | Deferred post-alpha: Glance home-screen widgets, HA WebSocket live state/zones, Vault unlock UI, first-run onboarding checklist (grants live in Settings → System access), FinTS bank sync | Planned | **Google-free by design:** no Google service is ever called at runtime (no Play Services, no Google recognizer, no Google Maps). Remaining Google-*authored* open-source, fully on-device libraries: AndroidX/Jetpack (unavoidable on Android), MediaPipe (Gemma inference), ML Kit on-device OCR/barcode (no network) — swap candidates documented in the plan. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e2268b2..6605881 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,8 +10,8 @@ android { defaultConfig { applicationId = "com.lifeos" - versionCode = 19 - versionName = "0.1.0-alpha.19" + versionCode = 20 + versionName = "0.1.0-alpha.20" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/kotlin/com/lifeos/app/ui/LifeOsApp.kt b/app/src/main/kotlin/com/lifeos/app/ui/LifeOsApp.kt index a32a92f..2eb9885 100644 --- a/app/src/main/kotlin/com/lifeos/app/ui/LifeOsApp.kt +++ b/app/src/main/kotlin/com/lifeos/app/ui/LifeOsApp.kt @@ -69,7 +69,14 @@ fun LifeOsApp(captureRequests: Int = 0, navBarIds: List = emptyList()) { // tabs in their chosen ORDER (the stored list is ordered). val barItems = remember(navBarIds) { if (navBarIds.isEmpty()) { - TopLevelDestination.entries.toList() + // Default four tabs; Settings can swap in any other module. + listOf( + TopLevelDestination.HOME, + TopLevelDestination.CALENDAR, + TopLevelDestination.TASKS, + TopLevelDestination.INBOX, + TopLevelDestination.ASSISTANT, + ) } else { listOf(TopLevelDestination.HOME) + navBarIds.mapNotNull { id -> diff --git a/app/src/main/kotlin/com/lifeos/app/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/lifeos/app/ui/settings/SettingsScreen.kt index 2d5bb6b..5d00a53 100644 --- a/app/src/main/kotlin/com/lifeos/app/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/lifeos/app/ui/settings/SettingsScreen.kt @@ -57,6 +57,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.lifeos.core.designsystem.component.SectionHeader import com.lifeos.core.designsystem.theme.PALETTE_DYNAMIC import com.lifeos.core.designsystem.theme.ThemePalettes +import com.lifeos.core.ui.navigation.TopLevelDestination /** Central settings (§8.4 onboarding grants + endpoints in one place). */ @OptIn(ExperimentalMaterial3Api::class) @@ -130,13 +131,10 @@ fun SettingsRoute( ) SectionHeader(title = "Navigation bar") - val navLabels = mapOf( - "CALENDAR" to "Calendar", - "TASKS" to "Tasks", - "INBOX" to "Inbox", - "ASSISTANT" to "Jarvis", - ) - // Enabled tabs first (orderable), then disabled ones to re-enable. + // Every module can be a tab; Home stays pinned as the first slot. + val navLabels = TopLevelDestination.entries + .filter { it != TopLevelDestination.HOME } + .associate { it.name to it.label } uiState.navBarItems.forEachIndexed { index, id -> ReorderRow( label = navLabels[id] ?: id, @@ -148,24 +146,44 @@ fun SettingsRoute( onToggle = { viewModel.onEvent(SettingsUiEvent.ToggleNavItem(id)) }, ) } - navLabels.keys.filter { it !in uiState.navBarItems }.forEach { id -> - ReorderRow( - label = navLabels[id] ?: id, - enabled = false, - canMoveUp = false, - canMoveDown = false, - onMoveUp = {}, - onMoveDown = {}, - onToggle = { viewModel.onEvent(SettingsUiEvent.ToggleNavItem(id)) }, - ) - } Text( - "Toggle tabs and order them with the arrows — Home stays pinned first. " + + "Toggle tabs and order them with the arrows - Home stays pinned first. " + "Everything stays reachable from the Home grid.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) + var navSearch by remember { mutableStateOf("") } + var showNavPicker by remember { mutableStateOf(false) } + Button(onClick = { showNavPicker = !showNavPicker }) { + Text(if (showNavPicker) "Done adding tabs" else "Add a module as a tab") + } + if (showNavPicker) { + OutlinedTextField( + value = navSearch, + onValueChange = { navSearch = it }, + label = { Text("Search modules") }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + ) + navLabels.entries + .filter { (id, label) -> + id !in uiState.navBarItems && + (navSearch.isBlank() || label.contains(navSearch.trim(), ignoreCase = true)) + } + .forEach { (id, label) -> + ReorderRow( + label = label, + enabled = false, + canMoveUp = false, + canMoveDown = false, + onMoveUp = {}, + onMoveDown = {}, + onToggle = { viewModel.onEvent(SettingsUiEvent.ToggleNavItem(id)) }, + ) + } + } + SectionHeader(title = "AI") OutlinedTextField( value = uiState.ollamaBaseUrl, diff --git a/feature/brick/src/main/AndroidManifest.xml b/feature/brick/src/main/AndroidManifest.xml index d613fc8..4d6dc5c 100644 --- a/feature/brick/src/main/AndroidManifest.xml +++ b/feature/brick/src/main/AndroidManifest.xml @@ -49,13 +49,25 @@ android:noHistory="true" android:taskAffinity="com.lifeos.brick.nfc" android:theme="@style/Theme.LifeOs"> + + + + + + + +