Settings → Appearance → Theme gets a fourth option, Black, next to System, Light and Dark. It's the dark theme with pure black backgrounds, and it works with Material You as well as Calendula's own palette. - The month cells and the hour grid are `surfaceContainer` tiles on a `surface` ground, so making every container black would erase the grid lines. floret-kit's `toPureBlack()` sets the ground to #000 and scales the containers down to about #13, #1A, #25 and #30. The tiles stay a shade above black, and sheets and menus still stand out from the page. - Black always means dark. It's an option in the theme picker, so it doesn't follow the system's light/dark setting. - Event colours read `isSystemInDarkTheme()` in 15 places, so an in-app Dark or Black on a phone in light mode got light-theme tints. They now read `LocalFloretDarkTheme`, the theme's own decision. The bug also affected Dark, so the changelog has a Fixed entry for it. - The window backdrop came from the night resource qualifier, so an in-app theme flashed the system's light or dark background before the first frame. `WindowThemeCache` keeps a SharedPreferences copy of the theme choice, and `onCreate` reads it to set the backdrop and bar icons. The copy is written when the stored setting loads, so the first cold start after updating still shows the old backdrop. - Widgets keep their current themes. They get their theme work in the next milestone. Needs jlmakiola/floret-kit#13, which is merged. The submodule is pinned to its merge commit on floret-kit `main` (`b1905da`). Tested with new unit tests for the transform and for saving and reloading `BLACK`, and by hand on the emulator. Closes #181 Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/399
This commit is contained in:
1 parent
0dd7e07aa0
commit
cf3c4e96cd
20 files changed
+128
-36
No files matched your search
@@ -25,8 +25,10 @@ import androidx.core.content.IntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository
|
||||
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.calendula.data.prefs.is24Hour
|
||||
import de.jeanlucmakiola.calendula.domain.EventForm
|
||||
@@ -51,8 +53,11 @@ import de.jeanlucmakiola.floret.crash.CrashReportDialog
|
||||
import de.jeanlucmakiola.floret.crash.CrashReporter
|
||||
import de.jeanlucmakiola.floret.crash.submitCrashReport
|
||||
import de.jeanlucmakiola.calendula.ui.theme.CalendulaTheme
|
||||
import de.jeanlucmakiola.calendula.ui.theme.WindowThemeCache
|
||||
import de.jeanlucmakiola.calendula.ui.theme.windowBackgroundRes
|
||||
import de.jeanlucmakiola.calendula.ui.theme.calendulaTypography
|
||||
import de.jeanlucmakiola.calendula.ui.theme.resolveFontFamily
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
@@ -67,6 +72,7 @@ private data class InsertRequest(val form: EventForm, val source: ImportSource)
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
@Inject lateinit var calendarRepository: CalendarRepository
|
||||
@Inject lateinit var settingsPrefs: SettingsPrefs
|
||||
|
||||
// Which of light/dark the system bars are drawn for. The styles installed
|
||||
// in onCreate read this field live, so androidx's config-change replay
|
||||
@@ -124,9 +130,25 @@ class MainActivity : AppCompatActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
systemBarsDark = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
||||
val systemDark = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
||||
Configuration.UI_MODE_NIGHT_YES
|
||||
// The stored theme only arrives after the first frame, so a Light, Dark
|
||||
// or Black override starts from its last mirrored copy: the matching
|
||||
// backdrop and bar icons instead of the night qualifier's (#181).
|
||||
val cachedTheme = WindowThemeCache.read(this)
|
||||
cachedTheme?.windowBackgroundRes()?.let { window.setBackgroundDrawableResource(it) }
|
||||
systemBarsDark = when (cachedTheme) {
|
||||
null, ThemeMode.SYSTEM -> systemDark
|
||||
ThemeMode.LIGHT -> false
|
||||
ThemeMode.DARK, ThemeMode.BLACK -> true
|
||||
}
|
||||
applyEdgeToEdge()
|
||||
lifecycleScope.launch {
|
||||
settingsPrefs.themeMode.collect { mode ->
|
||||
WindowThemeCache.write(this@MainActivity, mode)
|
||||
window.setBackgroundDrawableResource(mode.windowBackgroundRes() ?: R.color.window_background)
|
||||
}
|
||||
}
|
||||
// Android hands the intent that started the task back to onCreate every
|
||||
// time the activity is recreated — on a rotation, or on a restart after
|
||||
// the system reclaimed the process. Re-running a launch that opens one
|
||||
@@ -161,7 +183,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val darkTheme = when (settings.themeMode) {
|
||||
ThemeMode.SYSTEM -> isSystemInDarkTheme()
|
||||
ThemeMode.LIGHT -> false
|
||||
ThemeMode.DARK -> true
|
||||
ThemeMode.DARK, ThemeMode.BLACK -> true
|
||||
}
|
||||
// onCreate can only see the night resource qualifier, not the in-app
|
||||
// override — re-apply from the resolved theme so the bar icons follow
|
||||
@@ -201,6 +223,7 @@ class MainActivity : AppCompatActivity() {
|
||||
CalendulaTheme(
|
||||
darkTheme = darkTheme,
|
||||
dynamicColor = settings.dynamicColor,
|
||||
pureBlack = settings.themeMode == ThemeMode.BLACK,
|
||||
typography = typography,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
@@ -41,8 +41,11 @@ import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Light/dark override. SYSTEM follows the device setting. */
|
||||
enum class ThemeMode { SYSTEM, LIGHT, DARK }
|
||||
/**
|
||||
* Light/dark override. SYSTEM follows the device setting; BLACK is dark with
|
||||
* pure-black surfaces, for AMOLED screens (#181).
|
||||
*/
|
||||
enum class ThemeMode { SYSTEM, LIGHT, DARK, BLACK }
|
||||
|
||||
/**
|
||||
* Week-start override. [Auto] derives the first day from the active locale;
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.jeanlucmakiola.calendula.ui.agenda
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -39,6 +38,7 @@ import de.jeanlucmakiola.calendula.ui.common.eventFill
|
||||
import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay
|
||||
import de.jeanlucmakiola.floret.components.GroupedRow
|
||||
import de.jeanlucmakiola.floret.components.Position
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||
import de.jeanlucmakiola.floret.locale.localizedDateFormatter
|
||||
import kotlinx.datetime.DateTimeUnit
|
||||
@@ -127,7 +127,7 @@ internal fun AgendaEventRow(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val soften = LocalSoftenColors.current
|
||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||
GroupedRow(
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -95,6 +94,7 @@ import de.jeanlucmakiola.calendula.ui.common.LeadingAvatar
|
||||
import de.jeanlucmakiola.calendula.ui.common.SourceLogo
|
||||
import de.jeanlucmakiola.calendula.ui.common.curatedSourcePackage
|
||||
import de.jeanlucmakiola.floret.components.CollapsingScaffold
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import de.jeanlucmakiola.floret.identity.collapseExit
|
||||
import de.jeanlucmakiola.floret.identity.expandEnter
|
||||
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||
@@ -478,7 +478,7 @@ private fun CalendarNameField(
|
||||
onNameChange: (String) -> Unit,
|
||||
onImeAction: () -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val soften = LocalSoftenColors.current
|
||||
GroupedSurface(position = Position.Alone, modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Row(
|
||||
@@ -522,7 +522,7 @@ private fun CalendarNameField(
|
||||
*/
|
||||
@Composable
|
||||
private fun CalendarColorGrid(selected: Int, onSelect: (Int) -> Unit) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val soften = LocalSoftenColors.current
|
||||
GroupedSurface(position = Position.Alone, modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Column(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@@ -14,6 +13,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
|
||||
/**
|
||||
* Leading avatar for a calendar: a neutral chip holding a calendar glyph tinted
|
||||
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
|
||||
*/
|
||||
@Composable
|
||||
fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val soften = LocalSoftenColors.current
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.MutatePriority
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -56,6 +55,7 @@ import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
||||
import de.jeanlucmakiola.calendula.ui.week.minutesPast
|
||||
import de.jeanlucmakiola.calendula.ui.week.wallClockEndMinutes
|
||||
import de.jeanlucmakiola.calendula.ui.week.wallClockMinutes
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -1152,7 +1152,7 @@ const val SETTLE_FADE_MILLIS: Int = 250
|
||||
fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier = Modifier) {
|
||||
var origin by remember { mutableStateOf(Offset.Zero) }
|
||||
val density = LocalDensity.current
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val use24Hour = LocalUse24HourFormat.current
|
||||
val locale = currentLocale()
|
||||
val reduceMotion = rememberReduceMotion()
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -93,6 +92,7 @@ import de.jeanlucmakiola.calendula.domain.RichText
|
||||
import de.jeanlucmakiola.calendula.domain.TimeZoneOption
|
||||
import de.jeanlucmakiola.calendula.domain.timeZoneOptionOf
|
||||
import de.jeanlucmakiola.calendula.domain.zoneDescriptor
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
||||
import de.jeanlucmakiola.calendula.ui.common.DeleteEventDialog
|
||||
@@ -303,7 +303,7 @@ private fun EventDetailContent(
|
||||
) {
|
||||
val detail = state.detail
|
||||
val instance = detail.instance
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val locale = currentDetailLocale()
|
||||
val accent = eventAccent(instance.color, dark, LocalSoftenColors.current)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -36,6 +35,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Notes
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import kotlinx.coroutines.launch
|
||||
import de.jeanlucmakiola.calendula.ui.common.sendEventShare
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberWriteGate
|
||||
@@ -703,7 +703,7 @@ private fun EventEditContent(
|
||||
val locale = currentLocale()
|
||||
val deviceZone by viewModel.zone.collectAsStateWithLifecycle()
|
||||
val firstDayOfWeek by viewModel.firstDayOfWeek.collectAsStateWithLifecycle()
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
// The title, date and recurrence are managed by the special-dates sync, so
|
||||
// they're locked here; everything else (reminders, location, notes) is the
|
||||
// user's to edit.
|
||||
@@ -2299,7 +2299,7 @@ private fun ColorPickerDialog(
|
||||
onClear: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
FullScreenPicker(
|
||||
title = stringResource(R.string.event_edit_color),
|
||||
onDismiss = onDismiss,
|
||||
|
||||
@@ -24,6 +24,7 @@ import de.jeanlucmakiola.calendula.ui.common.morphEvent
|
||||
import de.jeanlucmakiola.calendula.ui.common.morphPageActive
|
||||
import de.jeanlucmakiola.calendula.ui.common.jumpToPage
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarPageSpec
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
@@ -46,7 +47,6 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
@@ -589,7 +589,7 @@ private const val MONTH_SETTLE_TIMEOUT_MILLIS = 450L
|
||||
@Composable
|
||||
private fun MonthDragOverlay(controller: MonthDragController) {
|
||||
var origin by remember { mutableStateOf(Offset.Zero) }
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val density = LocalDensity.current
|
||||
val timeChipWidth = rememberMonthTimeChipWidth()
|
||||
val reduceMotion = rememberReduceMotion()
|
||||
@@ -1722,7 +1722,7 @@ internal fun SplitMonthGrid(
|
||||
onSelectDay: (LocalDate) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val month = state.month
|
||||
val hasToday = state.today.year == month.year && state.today.month == month.month
|
||||
val ripple = rememberTodayRipple(active = hasToday)
|
||||
@@ -2231,7 +2231,7 @@ private fun MonthWeekRow(
|
||||
*/
|
||||
selected: LocalDate? = null,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val todayCol = week.days.indexOf(today).takeIf { it >= 0 && inMonth(today) }
|
||||
val ripple = rememberTodayRipple(active = todayCol != null)
|
||||
val overflowRow = rememberOverflowRowHeight()
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -83,6 +82,7 @@ import de.jeanlucmakiola.calendula.domain.RecurringWriteScope
|
||||
import de.jeanlucmakiola.calendula.domain.SearchHit
|
||||
import de.jeanlucmakiola.calendula.domain.SearchMonth
|
||||
import de.jeanlucmakiola.calendula.domain.isDeclined
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
||||
import de.jeanlucmakiola.floret.identity.fadeThrough
|
||||
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||
@@ -513,7 +513,7 @@ private fun SearchResultRow(
|
||||
// Spans are empty for a blank title — the query never matches the
|
||||
// placeholder — so marking the substitute is safe.
|
||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val soften = LocalSoftenColors.current
|
||||
// On a picked row the headline is already recoloured for the secondary
|
||||
// container, so the match is carried by weight alone there.
|
||||
|
||||
@@ -215,13 +215,13 @@ internal fun AppearanceScreen(
|
||||
selected = state.themeMode,
|
||||
label = { themeLabel(it) },
|
||||
summary = { mode ->
|
||||
if (mode == ThemeMode.SYSTEM) {
|
||||
stringResource(
|
||||
when (mode) {
|
||||
ThemeMode.SYSTEM -> stringResource(
|
||||
R.string.settings_theme_system_summary,
|
||||
themeLabel(if (systemDark) ThemeMode.DARK else ThemeMode.LIGHT),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
ThemeMode.BLACK -> stringResource(R.string.settings_theme_black_summary)
|
||||
ThemeMode.LIGHT, ThemeMode.DARK -> null
|
||||
}
|
||||
},
|
||||
onSelect = viewModel::setThemeMode,
|
||||
@@ -258,6 +258,7 @@ private fun themeLabel(mode: ThemeMode): String = stringResource(
|
||||
ThemeMode.SYSTEM -> R.string.settings_theme_system
|
||||
ThemeMode.LIGHT -> R.string.settings_theme_light
|
||||
ThemeMode.DARK -> R.string.settings_theme_dark
|
||||
ThemeMode.BLACK -> R.string.settings_theme_black
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@ import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme
|
||||
*
|
||||
* The mechanics live in floret-kit's identity module; the look stays here, so a
|
||||
* sibling app reuses the same theming without inheriting Calendula's palette.
|
||||
* The Settings screen overrides darkTheme, dynamicColor and typography; the bare
|
||||
* defaults (used by the crash screen) just follow the system.
|
||||
* The Settings screen overrides darkTheme, dynamicColor, pureBlack (the
|
||||
* AMOLED theme, #181) and typography; the bare defaults (used by the crash
|
||||
* screen) just follow the system.
|
||||
*/
|
||||
@Composable
|
||||
fun CalendulaTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
pureBlack: Boolean = false,
|
||||
typography: Typography = CalendulaTypography,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
@@ -31,6 +33,7 @@ fun CalendulaTheme(
|
||||
darkScheme = CalendulaDarkFallback,
|
||||
darkTheme = darkTheme,
|
||||
dynamicColor = dynamicColor,
|
||||
pureBlack = pureBlack,
|
||||
typography = typography,
|
||||
content = content,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.jeanlucmakiola.calendula.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.edit
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
|
||||
/**
|
||||
* A synchronous copy of the theme choice, for the window backdrop drawn before
|
||||
* the first Compose frame. The real setting lives in DataStore, which only
|
||||
* answers asynchronously — too late for onCreate, so a Light, Dark or Black
|
||||
* override flashed the night-qualifier backdrop on a cold start or a
|
||||
* recreation (#181). MainActivity mirrors every stored value here and reads it
|
||||
* back before setContent.
|
||||
*/
|
||||
internal object WindowThemeCache {
|
||||
|
||||
private const val FILE = "window_theme"
|
||||
private const val KEY_THEME_MODE = "theme_mode"
|
||||
|
||||
/** The last mirrored theme choice, or null before the first mirror. */
|
||||
fun read(context: Context): ThemeMode? =
|
||||
context.getSharedPreferences(FILE, Context.MODE_PRIVATE)
|
||||
.getString(KEY_THEME_MODE, null)
|
||||
?.let { name -> ThemeMode.entries.firstOrNull { it.name == name } }
|
||||
|
||||
fun write(context: Context, mode: ThemeMode) {
|
||||
val prefs = context.getSharedPreferences(FILE, Context.MODE_PRIVATE)
|
||||
if (prefs.getString(KEY_THEME_MODE, null) == mode.name) return
|
||||
prefs.edit { putString(KEY_THEME_MODE, mode.name) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The window backdrop matching this theme choice's Compose background, or null
|
||||
* for [ThemeMode.SYSTEM], whose night-qualified theme default already matches.
|
||||
*/
|
||||
@ColorRes
|
||||
internal fun ThemeMode.windowBackgroundRes(): Int? = when (this) {
|
||||
ThemeMode.SYSTEM -> null
|
||||
ThemeMode.LIGHT -> R.color.window_background_light
|
||||
ThemeMode.DARK -> R.color.window_background_dark
|
||||
ThemeMode.BLACK -> R.color.window_background_black
|
||||
}
|
||||
@@ -20,12 +20,12 @@ import de.jeanlucmakiola.calendula.ui.common.rememberCalendarPageSpec
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberViewSwitchSpec
|
||||
import de.jeanlucmakiola.calendula.ui.common.jumpToPage
|
||||
import de.jeanlucmakiola.calendula.ui.common.pagedHeight
|
||||
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -1188,7 +1188,7 @@ private fun AllDayStrip(
|
||||
animatedHeight: (() -> Dp)? = null,
|
||||
onEventClick: (EventInstance) -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -1295,7 +1295,7 @@ private fun Timeline(
|
||||
onResize: (TimelineResizeDrop) -> Unit,
|
||||
) {
|
||||
val fullViewportHeight = remember(geometry) { intArrayOf(-1) }
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = LocalFloretDarkTheme.current
|
||||
val zoom = LocalTimelineZoom.current
|
||||
val density = LocalDensity.current
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
|
||||
Reference in new issue
Block a user