release v2026.05.11.1: Flutter caching, navigation, and player polish #39
@@ -0,0 +1,36 @@
|
||||
package com.fabledsword.minstrel.theme
|
||||
|
||||
/**
|
||||
* User-selectable theme override. SYSTEM defers to
|
||||
* `isSystemInDarkTheme()`; LIGHT and DARK pin the app to that mode
|
||||
* regardless of the device setting.
|
||||
*
|
||||
* Stored as the wire string in `AuthSessionEntity.themeMode`. Null
|
||||
* persistence value means "never set" which we treat as SYSTEM.
|
||||
*/
|
||||
enum class ThemeMode {
|
||||
SYSTEM, LIGHT, DARK;
|
||||
|
||||
/** Stable string identifier used for persistence. */
|
||||
val wire: String get() = name.lowercase()
|
||||
|
||||
/**
|
||||
* Maps to MinstrelTheme's `darkOverride: Boolean?` parameter:
|
||||
* - SYSTEM → null (let isSystemInDarkTheme decide)
|
||||
* - LIGHT → false
|
||||
* - DARK → true
|
||||
*/
|
||||
fun toDarkOverride(): Boolean? = when (this) {
|
||||
SYSTEM -> null
|
||||
LIGHT -> false
|
||||
DARK -> true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromWire(wire: String?): ThemeMode = when (wire) {
|
||||
"light" -> LIGHT
|
||||
"dark" -> DARK
|
||||
else -> SYSTEM
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.fabledsword.minstrel.theme
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fabledsword.minstrel.auth.AuthStore
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val SHARE_STOP_TIMEOUT_MS = 5_000L
|
||||
|
||||
/**
|
||||
* Surfaces AuthStore.themeMode as a typed [ThemeMode] StateFlow and
|
||||
* lets callers write through. Used by:
|
||||
* - MainActivity's App() to pick the darkOverride to wrap the
|
||||
* NavGraph in.
|
||||
* - SettingsScreen's Appearance section as the 3-way picker.
|
||||
*
|
||||
* Lives in the theme package rather than a UI package because it's
|
||||
* tightly coupled to the theme tokens; SettingsScreen just consumes it.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class ThemePreferenceViewModel @Inject constructor(
|
||||
private val authStore: AuthStore,
|
||||
) : ViewModel() {
|
||||
|
||||
val themeMode: StateFlow<ThemeMode> =
|
||||
authStore.themeMode
|
||||
.map { ThemeMode.fromWire(it) }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
|
||||
initialValue = ThemeMode.fromWire(authStore.themeMode.value),
|
||||
)
|
||||
|
||||
fun setThemeMode(mode: ThemeMode) {
|
||||
// Persist null for SYSTEM so a "never set" row reads identically
|
||||
// to an explicit "follow system" pick — keeps the wire/storage
|
||||
// round-trip clean.
|
||||
authStore.setThemeMode(if (mode == ThemeMode.SYSTEM) null else mode.wire)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user