Skip to content

Commit 156afba

Browse files
committed
Add option to change the apps theme
1 parent 56b5329 commit 156afba

File tree

10 files changed

+275
-6
lines changed

10 files changed

+275
-6
lines changed

app/src/main/java/com/OxGames/Pluvia/PrefManager.kt

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.datastore.preferences.core.intPreferencesKey
1111
import androidx.datastore.preferences.core.longPreferencesKey
1212
import androidx.datastore.preferences.core.stringPreferencesKey
1313
import androidx.datastore.preferences.preferencesDataStore
14+
import com.OxGames.Pluvia.enums.AppTheme
1415
import com.OxGames.Pluvia.service.SteamService
1516
import com.OxGames.Pluvia.ui.enums.Orientation
1617
import com.winlator.box86_64.Box86_64Preset
@@ -349,4 +350,14 @@ object PrefManager {
349350
set(value) {
350351
setPref(TIPPED, value)
351352
}
353+
354+
private val APP_THEME = intPreferencesKey("app_theme")
355+
var appTheme: AppTheme
356+
get() {
357+
val value = getPref(APP_THEME, AppTheme.NIGHT.code)
358+
return AppTheme.from(value)
359+
}
360+
set(value) {
361+
setPref(APP_THEME, value.code)
362+
}
352363
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.OxGames.Pluvia.di
2+
3+
import com.OxGames.Pluvia.PrefManager
4+
import com.OxGames.Pluvia.enums.AppTheme
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import javax.inject.Singleton
10+
import kotlin.properties.ReadWriteProperty
11+
import kotlin.reflect.KProperty
12+
import kotlinx.coroutines.flow.MutableStateFlow
13+
import kotlinx.coroutines.flow.StateFlow
14+
15+
/**
16+
* Referenced from https://github.com/fvilarino/App-Theme-Compose-Sample
17+
*/
18+
19+
interface IAppTheme {
20+
val themeFlow: StateFlow<AppTheme>
21+
var currentTheme: AppTheme
22+
}
23+
24+
class AppThemeImpl : IAppTheme {
25+
26+
override val themeFlow: MutableStateFlow<AppTheme> = MutableStateFlow(PrefManager.appTheme)
27+
28+
override var currentTheme: AppTheme by AppThemePreferenceDelegate()
29+
30+
inner class AppThemePreferenceDelegate : ReadWriteProperty<Any, AppTheme> {
31+
32+
override fun getValue(thisRef: Any, property: KProperty<*>): AppTheme = PrefManager.appTheme
33+
34+
override fun setValue(thisRef: Any, property: KProperty<*>, value: AppTheme) {
35+
themeFlow.value = value
36+
PrefManager.appTheme = value
37+
}
38+
}
39+
}
40+
41+
@InstallIn(SingletonComponent::class)
42+
@Module
43+
class AppThemeModule {
44+
@Provides
45+
@Singleton
46+
fun provideAppTheme(): IAppTheme = AppThemeImpl()
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.OxGames.Pluvia.enums
2+
3+
enum class AppTheme(val code: Int) {
4+
AUTO(0),
5+
DAY(1),
6+
NIGHT(2),
7+
;
8+
9+
companion object {
10+
fun from(value: Int): AppTheme = entries.firstOrNull { it.code == value } ?: NIGHT
11+
}
12+
}

app/src/main/java/com/OxGames/Pluvia/ui/PluviaMain.kt

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.OxGames.Pluvia.ui
22

33
import android.content.Context
44
import android.content.Intent
5+
import androidx.compose.foundation.isSystemInDarkTheme
56
import androidx.compose.foundation.layout.fillMaxSize
67
import androidx.compose.runtime.Composable
78
import androidx.compose.runtime.LaunchedEffect
@@ -12,10 +13,10 @@ import androidx.compose.runtime.setValue
1213
import androidx.compose.ui.Modifier
1314
import androidx.compose.ui.platform.LocalContext
1415
import androidx.compose.ui.platform.LocalUriHandler
16+
import androidx.hilt.navigation.compose.hiltViewModel
1517
import androidx.lifecycle.LifecycleOwner
1618
import androidx.lifecycle.compose.LocalLifecycleOwner
1719
import androidx.lifecycle.compose.collectAsStateWithLifecycle
18-
import androidx.lifecycle.viewmodel.compose.viewModel
1920
import androidx.navigation.NavController
2021
import androidx.navigation.NavHostController
2122
import androidx.navigation.compose.NavHost
@@ -26,6 +27,7 @@ import com.OxGames.Pluvia.BuildConfig
2627
import com.OxGames.Pluvia.Constants
2728
import com.OxGames.Pluvia.PluviaApp
2829
import com.OxGames.Pluvia.PrefManager
30+
import com.OxGames.Pluvia.enums.AppTheme
2931
import com.OxGames.Pluvia.enums.LoginResult
3032
import com.OxGames.Pluvia.enums.PathType
3133
import com.OxGames.Pluvia.enums.SaveLocation
@@ -58,7 +60,7 @@ import timber.log.Timber
5860

5961
@Composable
6062
fun PluviaMain(
61-
viewModel: MainViewModel = viewModel(),
63+
viewModel: MainViewModel = hiltViewModel(),
6264
navController: NavHostController = rememberNavController(),
6365
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
6466
) {
@@ -352,7 +354,13 @@ fun PluviaMain(
352354
}
353355
}
354356

355-
PluviaTheme {
357+
PluviaTheme(
358+
darkTheme = when (state.appTheme) {
359+
AppTheme.AUTO -> isSystemInDarkTheme()
360+
AppTheme.DAY -> false
361+
AppTheme.NIGHT -> true
362+
},
363+
) {
356364
LoadingDialog(
357365
visible = state.loadingDialogVisible,
358366
progress = state.loadingDialogProgress,
@@ -431,7 +439,11 @@ fun PluviaMain(
431439

432440
/** Settings **/
433441
composable(route = PluviaScreen.Settings.name) {
434-
SettingsScreen(onBack = { navController.navigateUp() })
442+
SettingsScreen(
443+
appTheme = state.appTheme,
444+
onAppTheme = viewModel::setTheme,
445+
onBack = { navController.navigateUp() },
446+
)
435447
}
436448
}
437449
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.OxGames.Pluvia.ui.component.dialog
2+
3+
import android.content.res.Configuration
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.height
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.selection.selectable
10+
import androidx.compose.foundation.selection.selectableGroup
11+
import androidx.compose.material.icons.Icons
12+
import androidx.compose.material.icons.filled.BrightnessMedium
13+
import androidx.compose.material3.AlertDialog
14+
import androidx.compose.material3.Icon
15+
import androidx.compose.material3.MaterialTheme
16+
import androidx.compose.material3.RadioButton
17+
import androidx.compose.material3.Text
18+
import androidx.compose.material3.TextButton
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableStateOf
22+
import androidx.compose.runtime.remember
23+
import androidx.compose.runtime.setValue
24+
import androidx.compose.ui.Alignment
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.platform.LocalContext
27+
import androidx.compose.ui.semantics.Role
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.dp
30+
import com.OxGames.Pluvia.PrefManager
31+
import com.OxGames.Pluvia.enums.AppTheme
32+
import com.OxGames.Pluvia.ui.theme.PluviaTheme
33+
34+
@Composable
35+
fun AppThemeDialog(
36+
openDialog: Boolean,
37+
appTheme: AppTheme,
38+
onSelected: (AppTheme) -> Unit,
39+
onDismiss: () -> Unit,
40+
) {
41+
if (!openDialog) {
42+
return
43+
}
44+
45+
AlertDialog(
46+
onDismissRequest = onDismiss,
47+
icon = {
48+
Icon(
49+
imageVector = Icons.Default.BrightnessMedium,
50+
contentDescription = null,
51+
)
52+
},
53+
title = { Text(text = "App Theme") },
54+
text = {
55+
Column(modifier = Modifier.selectableGroup()) {
56+
AppTheme.entries.forEach { entry ->
57+
Row(
58+
Modifier
59+
.fillMaxWidth()
60+
.height(56.dp)
61+
.selectable(
62+
selected = entry == appTheme,
63+
onClick = { onSelected(entry) },
64+
role = Role.RadioButton,
65+
)
66+
.padding(horizontal = 16.dp),
67+
verticalAlignment = Alignment.CenterVertically,
68+
) {
69+
RadioButton(
70+
selected = entry == appTheme,
71+
onClick = null,
72+
)
73+
Text(
74+
text = entry.name,
75+
style = MaterialTheme.typography.bodyLarge,
76+
modifier = Modifier.padding(start = 16.dp),
77+
)
78+
}
79+
}
80+
}
81+
},
82+
confirmButton = {
83+
TextButton(onClick = onDismiss) {
84+
Text(text = "Close")
85+
}
86+
},
87+
)
88+
}
89+
90+
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL)
91+
@Composable
92+
private fun Preview_AppThemeDialog() {
93+
val content = LocalContext.current
94+
PrefManager.init(content)
95+
96+
var theme by remember { mutableStateOf(AppTheme.DAY) }
97+
98+
PluviaTheme {
99+
AppThemeDialog(
100+
openDialog = true,
101+
appTheme = theme,
102+
onSelected = { theme = it },
103+
onDismiss = { },
104+
)
105+
}
106+
}

app/src/main/java/com/OxGames/Pluvia/ui/data/MainState.kt

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.OxGames.Pluvia.ui.data
22

3+
import com.OxGames.Pluvia.enums.AppTheme
34
import com.OxGames.Pluvia.ui.enums.PluviaScreen
45

56
data class MainState(
7+
val appTheme: AppTheme = AppTheme.NIGHT,
68
val resettedScreen: PluviaScreen? = null,
79
val currentScreen: PluviaScreen = PluviaScreen.LoginUser,
810
val hasLaunched: Boolean = false,

app/src/main/java/com/OxGames/Pluvia/ui/model/MainViewModel.kt

+18-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import androidx.lifecycle.viewModelScope
77
import com.OxGames.Pluvia.PluviaApp
88
import com.OxGames.Pluvia.PrefManager
99
import com.OxGames.Pluvia.data.GameProcessInfo
10+
import com.OxGames.Pluvia.di.IAppTheme
11+
import com.OxGames.Pluvia.enums.AppTheme
1012
import com.OxGames.Pluvia.enums.LoginResult
1113
import com.OxGames.Pluvia.enums.PathType
1214
import com.OxGames.Pluvia.events.AndroidEvent
@@ -15,8 +17,10 @@ import com.OxGames.Pluvia.service.SteamService
1517
import com.OxGames.Pluvia.ui.data.MainState
1618
import com.OxGames.Pluvia.ui.enums.PluviaScreen
1719
import com.winlator.xserver.Window
20+
import dagger.hilt.android.lifecycle.HiltViewModel
1821
import `in`.dragonbra.javasteam.steam.handlers.steamapps.AppProcessInfo
1922
import java.nio.file.Paths
23+
import javax.inject.Inject
2024
import kotlin.io.path.name
2125
import kotlinx.coroutines.channels.Channel
2226
import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,7 +31,10 @@ import kotlinx.coroutines.flow.update
2731
import kotlinx.coroutines.launch
2832
import timber.log.Timber
2933

30-
class MainViewModel : ViewModel() {
34+
@HiltViewModel
35+
class MainViewModel @Inject constructor(
36+
private val appTheme: IAppTheme,
37+
) : ViewModel() {
3138

3239
sealed class MainUiEvent {
3340
data object OnBackPressed : MainUiEvent()
@@ -83,6 +90,12 @@ class MainViewModel : ViewModel() {
8390
PluviaApp.events.on<SteamEvent.LogonStarted, Unit>(onLoggingIn)
8491
PluviaApp.events.on<SteamEvent.LogonEnded, Unit>(onLogonEnded)
8592
PluviaApp.events.on<SteamEvent.LoggedOut, Unit>(onLoggedOut)
93+
94+
viewModelScope.launch {
95+
appTheme.themeFlow.collect { value ->
96+
_state.update { it.copy(appTheme = value) }
97+
}
98+
}
8699
}
87100

88101
override fun onCleared() {
@@ -103,6 +116,10 @@ class MainViewModel : ViewModel() {
103116
}
104117
}
105118

119+
fun setTheme(value: AppTheme) {
120+
appTheme.currentTheme = value
121+
}
122+
106123
fun setAnnoyingDialogShown(value: Boolean) {
107124
_state.update { it.copy(annoyingDialogShown = value) }
108125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.OxGames.Pluvia.ui.screen.settings
2+
3+
import androidx.compose.material3.Text
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.mutableStateOf
7+
import androidx.compose.runtime.saveable.rememberSaveable
8+
import androidx.compose.runtime.setValue
9+
import com.OxGames.Pluvia.enums.AppTheme
10+
import com.OxGames.Pluvia.ui.component.dialog.AppThemeDialog
11+
import com.alorma.compose.settings.ui.SettingsGroup
12+
import com.alorma.compose.settings.ui.SettingsMenuLink
13+
14+
@Composable
15+
fun SettingsGroupInterface(
16+
appTheme: AppTheme,
17+
onAppTheme: (AppTheme) -> Unit,
18+
) {
19+
var openAppThemeDialog by rememberSaveable { mutableStateOf(false) }
20+
21+
AppThemeDialog(
22+
openDialog = openAppThemeDialog,
23+
appTheme = appTheme,
24+
onSelected = onAppTheme,
25+
onDismiss = {
26+
openAppThemeDialog = false
27+
},
28+
)
29+
30+
SettingsGroup(title = { Text(text = "Interface") }) {
31+
SettingsMenuLink(
32+
title = { Text(text = "App Theme") },
33+
subtitle = { Text(text = "Choose between Day, Night, or Auto") },
34+
onClick = {
35+
openAppThemeDialog = true
36+
},
37+
)
38+
}
39+
}

0 commit comments

Comments
 (0)