diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt b/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt index 8009bf1..f0ff864 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt @@ -1,38 +1,51 @@ package de.jeanlucmakiola.clockula import android.content.Intent +import android.graphics.Color import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint +import de.jeanlucmakiola.clockula.data.prefs.SettingsPrefs import de.jeanlucmakiola.clockula.ui.crash.CrashReportActivity import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme import de.jeanlucmakiola.floret.crash.CrashReportDialog import de.jeanlucmakiola.floret.crash.CrashReporter import de.jeanlucmakiola.floret.crash.submitCrashReport +import de.jeanlucmakiola.floret.prefs.Appearance +import de.jeanlucmakiola.floret.prefs.isDark +import javax.inject.Inject /** - * Single activity. M0 scaffolding: it brings up the theme and the crash surface - * and nothing else. The four-tab shell and the live pill (docs/PLAN.md §9) land - * in M4, and the theme starts following stored preferences once the data layer - * exists in M2 — until then [ClockulaTheme]'s own defaults (follow-system, - * dynamic colour) stand in. + * Single activity. M1 scaffolding: it brings up the theme — now following the + * stored appearance preference — and the crash surface, and nothing else. The + * four-tab shell and the live pill (docs/PLAN.md §9) land in M4. */ @AndroidEntryPoint class MainActivity : ComponentActivity() { + // Field-injected rather than read through a ViewModel: the appearance feeds + // the theme before any screen exists. The settings ViewModel arrives with + // the settings screen in M10. + @Inject + lateinit var settingsPrefs: SettingsPrefs + // A captured crash report awaiting the user's decision, surfaced as a dialog // over the app on the next launch (the single-crash path). A startup // crash-loop is handled out of band, before setContent — see below. @@ -50,13 +63,27 @@ class MainActivity : ComponentActivity() { return } - enableEdgeToEdge() - // Surface a single captured crash as a dialog on the next launch. if (CrashReporter.shouldPrompt(this)) pendingCrashReport = CrashReporter.pendingReport(this) setContent { - ClockulaTheme { + // Until the first snapshot arrives, the family's defaults stand in — + // follow the system, dynamic colour on — so the first frame is never + // blocked on disk. + val appearance by settingsPrefs.appearance + .collectAsStateWithLifecycle(initialValue = Appearance()) + val darkTheme = appearance.themeMode.isDark(isSystemInDarkTheme()) + + // The system bars follow the *resolved* theme, not the device night + // flag: with the device dark and ThemeMode.LIGHT stored, deriving the + // icon colours from the system would paint white icons onto a light + // background. Re-applied whenever the stored preference changes. + LaunchedEffect(darkTheme) { applyEdgeToEdge(darkTheme) } + + ClockulaTheme( + darkTheme = darkTheme, + dynamicColor = appearance.dynamicColor, + ) { Placeholder(Modifier.fillMaxSize()) pendingCrashReport?.let { report -> CrashReportDialog( @@ -76,6 +103,13 @@ class MainActivity : ComponentActivity() { } } + private fun applyEdgeToEdge(darkTheme: Boolean) { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { darkTheme }, + navigationBarStyle = SystemBarStyle.auto(NAV_BAR_LIGHT_SCRIM, NAV_BAR_DARK_SCRIM) { darkTheme }, + ) + } + override fun onResume() { super.onResume() // A successful start breaks any loop; reset the timing trail so a later @@ -98,6 +132,10 @@ private fun Placeholder(modifier: Modifier = Modifier) { } } +/** The scrims androidx uses for a three-button navigation bar, matched exactly. */ +private val NAV_BAR_LIGHT_SCRIM = Color.argb(0xe6, 0xFF, 0xFF, 0xFF) +private val NAV_BAR_DARK_SCRIM = Color.argb(0x80, 0x1b, 0x1b, 0x1b) + @Composable private fun stringResourceAppName(): String = androidx.compose.ui.res.stringResource(R.string.app_name)