release v2026.05.11.1: Flutter caching, navigation, and player polish #39

Merged
bvandeusen merged 262 commits from dev into main 2026-05-11 13:47:43 -04:00
2 changed files with 81 additions and 0 deletions
Showing only changes of commit 45e2248970 - Show all commits
@@ -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)
}
}