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"> + + + + + + + +