ability to change theme

This commit is contained in:
2024-08-14 23:28:00 +03:00
parent 638d3868de
commit 602db20f12
9 changed files with 74 additions and 7 deletions
@@ -4,7 +4,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.FadeTransition import cafe.adriel.voyager.transitions.FadeTransition
import dev.meloda.overseerr.screens.main.MainScreen import dev.meloda.overseerr.screens.main.MainScreen
@@ -24,12 +26,13 @@ internal fun App() = KoinContext {
} }
val settingsController: SettingsController = koinInject() val settingsController: SettingsController = koinInject()
val settings by settingsController.settings.collectAsStateWithLifecycle()
LaunchedEffect(true) { LaunchedEffect(true) {
settingsController.loadAppSettings() settingsController.loadAppSettings()
} }
AppTheme { AppTheme(themeMode = settings.themeMode) {
Surface(modifier = Modifier.fillMaxSize()) { Surface(modifier = Modifier.fillMaxSize()) {
Navigator(MainScreen()) { navigator -> Navigator(MainScreen()) { navigator ->
FadeTransition(navigator) FadeTransition(navigator)
@@ -17,6 +17,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@@ -32,7 +33,7 @@ class LoginScreen : Screen {
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>() val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>()
val screenState: LoginScreenState by viewModel.screenState.collectAsState() val screenState: LoginScreenState by viewModel.screenState.collectAsStateWithLifecycle()
var loginValue by rememberSaveable { var loginValue by rememberSaveable {
mutableStateOf(screenState.login) mutableStateOf(screenState.login)
@@ -5,25 +5,61 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.presentation.LoginScreen import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen
import dev.meloda.overseerr.screens.url.presentation.UrlScreen import dev.meloda.overseerr.screens.url.presentation.UrlScreen
import dev.meloda.overseerr.settings.SettingsController
import dev.meloda.overseerr.settings.model.ThemeMode
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
class MainScreen : Screen { class MainScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val coroutineScope = rememberCoroutineScope()
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val settingsController: SettingsController = koinInject()
val settings by settingsController.settings.collectAsStateWithLifecycle()
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar(title = { Text(text = "Main screen") }) TopAppBar(
title = { Text(text = "Main screen") },
actions = {
TextButton(
onClick = {
val newThemeMode = ThemeMode.entries.getOrElse(
ThemeMode.entries.indexOf(settings.themeMode) + 1
) { ThemeMode.System }
settingsController.updateThemeMode(newThemeMode)
coroutineScope.launch {
settingsController.saveAppSettings()
}
}
) {
Text(
text = when (settings.themeMode) {
ThemeMode.System -> "System"
ThemeMode.Dark -> "Dark"
ThemeMode.Light -> "Light"
}
)
}
}
)
} }
) { padding -> ) { padding ->
Row( Row(
@@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@@ -43,7 +44,7 @@ class RequestsScreen : Screen {
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel: RequestsViewModel = koinViewModel<RequestsViewModelImpl>() val viewModel: RequestsViewModel = koinViewModel<RequestsViewModelImpl>()
val screenState: RequestsScreenState by viewModel.screenState.collectAsState() val screenState: RequestsScreenState by viewModel.screenState.collectAsStateWithLifecycle()
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
val hazeStyle = HazeMaterials.ultraThin() val hazeStyle = HazeMaterials.ultraThin()
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@@ -27,7 +28,7 @@ class UrlScreen : Screen {
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>() val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>()
val screenState by viewModel.screenState.collectAsState() val screenState by viewModel.screenState.collectAsStateWithLifecycle()
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -2,6 +2,7 @@ package dev.meloda.overseerr.settings
import dev.meloda.overseerr.ext.setValue import dev.meloda.overseerr.ext.setValue
import dev.meloda.overseerr.settings.model.AppSettings import dev.meloda.overseerr.settings.model.AppSettings
import dev.meloda.overseerr.settings.model.ThemeMode
import io.github.xxfast.kstore.KStore import io.github.xxfast.kstore.KStore
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -9,8 +10,11 @@ import kotlinx.coroutines.flow.StateFlow
interface SettingsController { interface SettingsController {
val settings: StateFlow<AppSettings> val settings: StateFlow<AppSettings>
suspend fun saveAppSettings()
suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) suspend fun updateAppSettings(update: (AppSettings) -> AppSettings)
suspend fun loadAppSettings(): AppSettings suspend fun loadAppSettings(): AppSettings
fun updateThemeMode(newThemeMode: ThemeMode)
} }
class SettingsControllerImpl( class SettingsControllerImpl(
@@ -19,6 +23,10 @@ class SettingsControllerImpl(
override val settings = MutableStateFlow(AppSettings.EMPTY) override val settings = MutableStateFlow(AppSettings.EMPTY)
override suspend fun saveAppSettings() {
store.set(settings.value)
}
override suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) { override suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) {
store.set(update(settings.value)) store.set(update(settings.value))
} }
@@ -28,4 +36,8 @@ class SettingsControllerImpl(
settings.setValue { loadedSettings } settings.setValue { loadedSettings }
return loadedSettings return loadedSettings
} }
override fun updateThemeMode(newThemeMode: ThemeMode) {
settings.setValue { old -> old.copy(themeMode = newThemeMode) }
}
} }
@@ -5,7 +5,8 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class AppSettings( data class AppSettings(
val url: String = "", val url: String = "",
val plexToken: String = "" val plexToken: String = "",
val themeMode: ThemeMode = ThemeMode.System
) { ) {
companion object { companion object {
val EMPTY: AppSettings = AppSettings() val EMPTY: AppSettings = AppSettings()
@@ -0,0 +1,5 @@
package dev.meloda.overseerr.settings.model
enum class ThemeMode {
System, Dark, Light
}
@@ -6,15 +6,22 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.meloda.overseerr.settings.model.ThemeMode
internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) } internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) }
@Composable @Composable
internal fun AppTheme( internal fun AppTheme(
themeMode: ThemeMode = ThemeMode.System,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val systemIsDark = isSystemInDarkTheme() val systemIsDark = isSystemInDarkTheme()
val isDarkState = remember { mutableStateOf(systemIsDark) } val isDarkState = remember(themeMode, systemIsDark) {
mutableStateOf(
if (themeMode == ThemeMode.System) systemIsDark
else themeMode == ThemeMode.Dark
)
}
CompositionLocalProvider( CompositionLocalProvider(
LocalThemeIsDark provides isDarkState LocalThemeIsDark provides isDarkState
) { ) {