ability to change theme
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
+2
-1
@@ -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(
|
||||||
|
|||||||
+2
-1
@@ -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()
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user