Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Light Dark mode and color themes. #99

Merged
merged 9 commits into from
Jan 26, 2025
26 changes: 17 additions & 9 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ android {
buildConfigField("boolean", "GOLD", "false")
val iconValue = "@mipmap/ic_launcher"
val iconRoundValue = "@mipmap/ic_launcher_round"
manifestPlaceholders.putAll(mapOf(
"icon" to iconValue,
"roundIcon" to iconRoundValue
))
manifestPlaceholders.putAll(
mapOf(
"icon" to iconValue,
"roundIcon" to iconRoundValue,
),
)

ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a"))
Expand All @@ -65,7 +67,7 @@ android {
proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
"proguard-rules.pro",
)
}

Expand All @@ -91,10 +93,12 @@ android {
buildConfigField("boolean", "GOLD", "true")
val iconValue = "@mipmap/ic_launcher_gold"
val iconRoundValue = "@mipmap/ic_launcher_gold_round"
manifestPlaceholders.putAll(mapOf(
"icon" to iconValue,
"roundIcon" to iconRoundValue
))
manifestPlaceholders.putAll(
mapOf(
"icon" to iconValue,
"roundIcon" to iconRoundValue,
),
)
}
}

Expand Down Expand Up @@ -131,6 +135,10 @@ android {
}
dynamicFeatures += setOf(":ubuntufs")

kotlinter {
ignoreFormatFailures = false
}

// cmake on release builds a proot that fails to process ld-2.31.so
// externalNativeBuild {
// cmake {
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/com/OxGames/Pluvia/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package com.OxGames.Pluvia
import android.Manifest
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.graphics.Color.TRANSPARENT
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.OrientationEventListener
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand Down Expand Up @@ -72,6 +74,7 @@ class MainActivity : ComponentActivity() {
private var index = totalIndex++

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.light(TRANSPARENT, TRANSPARENT))
super.onCreate(savedInstanceState)

// startOrientator() // causes memory leak since activity restarted every orientation change
Expand All @@ -80,7 +83,6 @@ class MainActivity : ComponentActivity() {
PluviaApp.events.on<AndroidEvent.SetAllowedOrientation, Unit>(onSetAllowedOrientation)
PluviaApp.events.on<AndroidEvent.EndProcess, Unit>(onEndProcess)

enableEdgeToEdge()
setContent {
var hasNotificationPermission by remember { mutableStateOf(false) }
val permissionLauncher = rememberLauncherForActivityResult(
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/OxGames/Pluvia/PrefManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.OxGames.Pluvia.enums.AppTheme
import com.OxGames.Pluvia.service.SteamService
import com.OxGames.Pluvia.ui.enums.Orientation
import com.materialkolor.PaletteStyle
import com.winlator.box86_64.Box86_64Preset
import com.winlator.container.Container
import com.winlator.core.DefaultVersion
Expand Down Expand Up @@ -349,4 +351,24 @@ object PrefManager {
set(value) {
setPref(TIPPED, value)
}

private val APP_THEME = intPreferencesKey("app_theme")
var appTheme: AppTheme
get() {
val value = getPref(APP_THEME, AppTheme.AUTO.ordinal)
return AppTheme.entries.getOrNull(value) ?: AppTheme.AUTO
}
set(value) {
setPref(APP_THEME, value.ordinal)
}

private val APP_THEME_PALETTE = intPreferencesKey("app_theme_palette")
var appThemePalette: PaletteStyle
get() {
val value = getPref(APP_THEME_PALETTE, PaletteStyle.TonalSpot.ordinal)
return PaletteStyle.entries.getOrNull(value) ?: PaletteStyle.TonalSpot
}
set(value) {
setPref(APP_THEME_PALETTE, value.ordinal)
}
}
64 changes: 64 additions & 0 deletions app/src/main/java/com/OxGames/Pluvia/di/AppThemeModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.OxGames.Pluvia.di

import com.OxGames.Pluvia.PrefManager
import com.OxGames.Pluvia.enums.AppTheme
import com.materialkolor.PaletteStyle
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
* Referenced from https://github.com/fvilarino/App-Theme-Compose-Sample
*/

interface IAppTheme {
val themeFlow: StateFlow<AppTheme>
var currentTheme: AppTheme
val paletteFlow: StateFlow<PaletteStyle>
var currentPalette: PaletteStyle
}

class AppThemeImpl : IAppTheme {

override val themeFlow: MutableStateFlow<AppTheme> = MutableStateFlow(PrefManager.appTheme)

override var currentTheme: AppTheme by AppThemeDelegate()

override val paletteFlow: MutableStateFlow<PaletteStyle> = MutableStateFlow(PrefManager.appThemePalette)

override var currentPalette: PaletteStyle by AppPaletteDelegate()

inner class AppThemeDelegate : ReadWriteProperty<Any, AppTheme> {

override fun getValue(thisRef: Any, property: KProperty<*>): AppTheme = PrefManager.appTheme

override fun setValue(thisRef: Any, property: KProperty<*>, value: AppTheme) {
themeFlow.value = value
PrefManager.appTheme = value
}
}

inner class AppPaletteDelegate : ReadWriteProperty<Any, PaletteStyle> {

override fun getValue(thisRef: Any, property: KProperty<*>): PaletteStyle = PrefManager.appThemePalette

override fun setValue(thisRef: Any, property: KProperty<*>, value: PaletteStyle) {
paletteFlow.value = value
PrefManager.appThemePalette = value
}
}
}

@InstallIn(SingletonComponent::class)
@Module
class AppThemeModule {
@Provides
@Singleton
fun provideAppTheme(): IAppTheme = AppThemeImpl()
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/OxGames/Pluvia/enums/AppTheme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.OxGames.Pluvia.enums

enum class AppTheme(val text: String) {
AUTO("System Default"),
DAY("Light"),
NIGHT("Dark"),
AMOLED("Dark + AMOLED"),
}
30 changes: 24 additions & 6 deletions app/src/main/java/com/OxGames/Pluvia/ui/PluviaMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.OxGames.Pluvia.ui

import android.content.Context
import android.content.Intent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -12,10 +13,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
Expand All @@ -26,6 +27,7 @@ import com.OxGames.Pluvia.BuildConfig
import com.OxGames.Pluvia.Constants
import com.OxGames.Pluvia.PluviaApp
import com.OxGames.Pluvia.PrefManager
import com.OxGames.Pluvia.enums.AppTheme
import com.OxGames.Pluvia.enums.LoginResult
import com.OxGames.Pluvia.enums.PathType
import com.OxGames.Pluvia.enums.SaveLocation
Expand Down Expand Up @@ -58,7 +60,7 @@ import timber.log.Timber

@Composable
fun PluviaMain(
viewModel: MainViewModel = viewModel(),
viewModel: MainViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(),
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
) {
Expand Down Expand Up @@ -352,11 +354,21 @@ fun PluviaMain(
}
}

PluviaTheme {
PluviaTheme(
isDark = when (state.appTheme) {
AppTheme.AUTO -> isSystemInDarkTheme()
AppTheme.DAY -> false
AppTheme.NIGHT -> true
AppTheme.AMOLED -> true
},
isAmoled = (state.appTheme == AppTheme.AMOLED),
style = state.paletteStyle,
) {
LoadingDialog(
visible = state.loadingDialogVisible,
progress = state.loadingDialogProgress,
)

MessageDialog(
visible = msgDialogState.visible,
onDismissRequest = onDismissRequest,
Expand All @@ -368,11 +380,11 @@ fun PluviaMain(
title = msgDialogState.title,
message = msgDialogState.message,
)

NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,
startDestination = PluviaScreen.LoginUser.name,
modifier = Modifier
.fillMaxSize(),
) {
/** Login **/
composable(route = PluviaScreen.LoginUser.name) {
Expand Down Expand Up @@ -431,7 +443,13 @@ fun PluviaMain(

/** Settings **/
composable(route = PluviaScreen.Settings.name) {
SettingsScreen(onBack = { navController.navigateUp() })
SettingsScreen(
appTheme = state.appTheme,
paletteStyle = state.paletteStyle,
onAppTheme = viewModel::setTheme,
onPaletteStyle = viewModel::setPalette,
onBack = { navController.navigateUp() },
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.OxGames.Pluvia.ui.component.dialog

import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BrightnessMedium
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.OxGames.Pluvia.PrefManager
import com.OxGames.Pluvia.ui.theme.PluviaTheme
import com.materialkolor.PaletteStyle

@Composable
fun AppPaletteDialog(
openDialog: Boolean,
paletteStyle: PaletteStyle,
onSelected: (PaletteStyle) -> Unit,
onDismiss: () -> Unit,
) {
if (!openDialog) {
return
}

val scrollState = rememberScrollState()

AlertDialog(
onDismissRequest = onDismiss,
icon = {
Icon(
imageVector = Icons.Default.BrightnessMedium,
contentDescription = null,
)
},
title = { Text(text = "Palette Style") },
text = {
Column(
modifier = Modifier
.selectableGroup()
.verticalScroll(scrollState),
) {
PaletteStyle.entries.forEach { entry ->
Row(
Modifier
.fillMaxWidth()
.height(56.dp)
.selectable(
selected = entry == paletteStyle,
onClick = { onSelected(entry) },
role = Role.RadioButton,
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = entry == paletteStyle,
onClick = null,
)
Text(
text = entry.name,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 16.dp),
)
}
}
}
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text(text = "Close")
}
},
)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL)
@Composable
private fun Preview_AppPaletteDialog() {
val content = LocalContext.current
PrefManager.init(content)

var style by remember { mutableStateOf(PaletteStyle.TonalSpot) }

PluviaTheme {
AppPaletteDialog(
openDialog = true,
paletteStyle = style,
onSelected = { style = it },
onDismiss = { },
)
}
}
Loading
Loading