From 5f381dbd10557983d847ad7715b0c6571e881f82 Mon Sep 17 00:00:00 2001 From: Leo CACHEUX Date: Sat, 10 Jan 2026 21:37:43 +0100 Subject: [PATCH 1/2] Add dark mode option and improve used colors --- .../net/cacheux/nvp/app/MainActivity.kt | 16 +- .../DatastorePreferencesRepository.kt | 7 + .../app/repository/PreferencesRepository.kt | 8 + .../net/cacheux/nvp/app/ui/ScreenWrapper.kt | 1 + .../app/viewmodel/BaseSettingsViewModel.kt | 1 + app/src/desktopMain/kotlin/main.kt | 57 ++++--- .../repository/PreferencesRepositoryImpl.kt | 2 + ui/build.gradle.kts | 1 + .../composeResources/values-fr/strings.xml | 4 + .../composeResources/values/strings.xml | 4 + .../net/cacheux/nvp/ui/DoseGroupDetails.kt | 15 +- .../kotlin/net/cacheux/nvp/ui/DoseList.kt | 7 +- .../kotlin/net/cacheux/nvp/ui/MainScreen.kt | 36 ++--- .../net/cacheux/nvp/ui/SelectPreference.kt | 145 ++++++++++++++++++ .../net/cacheux/nvp/ui/SettingsScreen.kt | 21 ++- 15 files changed, 267 insertions(+), 58 deletions(-) create mode 100644 ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SelectPreference.kt diff --git a/app/src/androidMain/kotlin/net/cacheux/nvp/app/MainActivity.kt b/app/src/androidMain/kotlin/net/cacheux/nvp/app/MainActivity.kt index 55756d3..acef5f9 100644 --- a/app/src/androidMain/kotlin/net/cacheux/nvp/app/MainActivity.kt +++ b/app/src/androidMain/kotlin/net/cacheux/nvp/app/MainActivity.kt @@ -5,9 +5,12 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.ui.Modifier import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope @@ -17,6 +20,7 @@ import kotlinx.coroutines.launch import net.cacheux.nvp.app.BuildConfig.DEMO_VERSION import net.cacheux.nvp.app.repository.ActivityRequirer import net.cacheux.nvp.app.repository.PenInfoRepository +import net.cacheux.nvp.app.repository.Theme import net.cacheux.nvp.app.ui.ScreenWrapper import net.cacheux.nvp.app.utils.csvFilename import net.cacheux.nvp.app.utils.toCsv @@ -24,6 +28,7 @@ import net.cacheux.nvp.app.viewmodel.MainScreenViewModel import net.cacheux.nvp.app.viewmodel.PenSettingsViewModel import net.cacheux.nvp.app.viewmodel.SettingsViewModel import net.cacheux.nvp.ui.MainDropdownMenuActions +import net.cacheux.nvp.ui.asStateWrapper import javax.inject.Inject @AndroidEntryPoint @@ -40,7 +45,16 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - MaterialTheme { + val isDark = when(settingsViewModel.theme.asStateWrapper().value) { + Theme.THEME_LIGHT -> false + Theme.THEME_DARK -> true + Theme.THEME_SYSTEM -> isSystemInDarkTheme() + else -> false + } + + MaterialTheme( + colorScheme = if (isDark) darkColorScheme() else lightColorScheme() + ) { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background diff --git a/app/src/androidMain/kotlin/net/cacheux/nvp/app/repository/DatastorePreferencesRepository.kt b/app/src/androidMain/kotlin/net/cacheux/nvp/app/repository/DatastorePreferencesRepository.kt index d09a0bb..4023e39 100644 --- a/app/src/androidMain/kotlin/net/cacheux/nvp/app/repository/DatastorePreferencesRepository.kt +++ b/app/src/androidMain/kotlin/net/cacheux/nvp/app/repository/DatastorePreferencesRepository.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import net.cacheux.nvp.app.repository.Theme.THEME_LIGHT import net.cacheux.nvp.app.utils.PreferenceStateFlowWrapper import net.cacheux.nvplib.utils.StateFlowWrapper @@ -13,12 +14,18 @@ class DatastorePreferencesRepository(context: Context): PreferencesRepository { private val dataStore = context.dataStore private object PreferencesKeys { + val THEME = intPreferencesKey("theme") val GROUP_ENABLED = booleanPreferencesKey("group_enabled") val GROUP_DELAY = intPreferencesKey("group_delay") val AUTO_IGNORE_ENABLED = booleanPreferencesKey("auto_ignore_enabled") val AUTO_IGNORE_VALUE = intPreferencesKey("auto_ignore_value") } + override val theme: StateFlowWrapper = + PreferenceStateFlowWrapper( + datastore = dataStore, key = PreferencesKeys.THEME, initialValue = THEME_LIGHT + ) + override val groupEnabled: StateFlowWrapper = PreferenceStateFlowWrapper( dataStore, PreferencesKeys.GROUP_ENABLED, true diff --git a/app/src/commonMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepository.kt b/app/src/commonMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepository.kt index 7c4a24e..5f41fdd 100644 --- a/app/src/commonMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepository.kt +++ b/app/src/commonMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepository.kt @@ -2,7 +2,15 @@ package net.cacheux.nvp.app.repository import net.cacheux.nvplib.utils.StateFlowWrapper +object Theme { + const val THEME_LIGHT = 0 + const val THEME_DARK = 1 + const val THEME_SYSTEM = 2 +} + interface PreferencesRepository { + val theme: StateFlowWrapper + val groupEnabled: StateFlowWrapper val groupDelay: StateFlowWrapper val autoIgnoreEnabled: StateFlowWrapper diff --git a/app/src/commonMain/kotlin/net/cacheux/nvp/app/ui/ScreenWrapper.kt b/app/src/commonMain/kotlin/net/cacheux/nvp/app/ui/ScreenWrapper.kt index 1609569..26cc3f3 100644 --- a/app/src/commonMain/kotlin/net/cacheux/nvp/app/ui/ScreenWrapper.kt +++ b/app/src/commonMain/kotlin/net/cacheux/nvp/app/ui/ScreenWrapper.kt @@ -93,6 +93,7 @@ fun ScreenWrapper( SettingsScreen( params = SettingsScreenParams( onBack = { currentScreen = CurrentScreen.Main }, + theme = settingsViewModel.theme.asStateWrapper(), groupDose = settingsViewModel.groupEnabled.asStateWrapper(), groupDelay = settingsViewModel.groupDelay.asStateWrapper(), autoIgnoreEnabled = settingsViewModel.autoIgnoreEnabled.asStateWrapper(), diff --git a/app/src/commonMain/kotlin/net/cacheux/nvp/app/viewmodel/BaseSettingsViewModel.kt b/app/src/commonMain/kotlin/net/cacheux/nvp/app/viewmodel/BaseSettingsViewModel.kt index 0205686..836497d 100644 --- a/app/src/commonMain/kotlin/net/cacheux/nvp/app/viewmodel/BaseSettingsViewModel.kt +++ b/app/src/commonMain/kotlin/net/cacheux/nvp/app/viewmodel/BaseSettingsViewModel.kt @@ -6,6 +6,7 @@ import net.cacheux.nvp.app.repository.PreferencesRepository open class BaseSettingsViewModel( preferencesRepository: PreferencesRepository ): ViewModel() { + val theme = preferencesRepository.theme val groupEnabled = preferencesRepository.groupEnabled val groupDelay = preferencesRepository.groupDelay val autoIgnoreEnabled = preferencesRepository.autoIgnoreEnabled diff --git a/app/src/desktopMain/kotlin/main.kt b/app/src/desktopMain/kotlin/main.kt index 96f633c..b249046 100644 --- a/app/src/desktopMain/kotlin/main.kt +++ b/app/src/desktopMain/kotlin/main.kt @@ -1,3 +1,7 @@ +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.room.Room @@ -12,6 +16,7 @@ import kotlinx.coroutines.launch import net.cacheux.nvp.app.repository.PreferencesRepositoryImpl import net.cacheux.nvp.app.repository.StorageRepository import net.cacheux.nvp.app.repository.TestPenInfoRepository +import net.cacheux.nvp.app.repository.Theme import net.cacheux.nvp.app.ui.ScreenWrapper import net.cacheux.nvp.app.usecase.DoseListUseCase import net.cacheux.nvp.app.utils.csvFilename @@ -20,6 +25,7 @@ import net.cacheux.nvp.app.viewmodel.BasePenSettingsViewModel import net.cacheux.nvp.app.viewmodel.BaseSettingsViewModel import net.cacheux.nvp.app.viewmodel.MainScreenViewModel import net.cacheux.nvp.ui.MainDropdownMenuActions +import net.cacheux.nvp.ui.asStateWrapper import net.cacheux.nvplib.storage.room.NvpDatabase import net.cacheux.nvplib.storage.room.RoomDoseStorage @@ -71,27 +77,38 @@ fun main() = application { // TODO feedback message } - ScreenWrapper( - mainScreenViewModel = mainScreenViewModel, - penSettingsViewModel = penSettingsViewModel, - settingsViewModel = settingsViewModel, + val isDark = when(settingsViewModel.theme.asStateWrapper().value) { + Theme.THEME_LIGHT -> false + Theme.THEME_DARK -> true + Theme.THEME_SYSTEM -> isSystemInDarkTheme() + else -> false + } + + MaterialTheme( + colorScheme = if (isDark) darkColorScheme() else lightColorScheme() + ) { + ScreenWrapper( + mainScreenViewModel = mainScreenViewModel, + penSettingsViewModel = penSettingsViewModel, + settingsViewModel = settingsViewModel, - dropdownMenuActions = MainDropdownMenuActions( - onLoadingClick = { loadRawDataPicker.launch() }, - onExportCsv = { - ioScope.launch { - saveCsvPicker.launch( - baseName = csvFilename(mainScreenViewModel.getCurrentPen().value), - extension = "csv", - bytes = mainScreenViewModel.flatDoseList.first().toCsv().toByteArray() - ) - } - }, - onImportCsv = { loadCsvPicker.launch() }, - onInitDemo = { mainScreenViewModel.initDemoData() } - ), + dropdownMenuActions = MainDropdownMenuActions( + onLoadingClick = { loadRawDataPicker.launch() }, + onExportCsv = { + ioScope.launch { + saveCsvPicker.launch( + baseName = csvFilename(mainScreenViewModel.getCurrentPen().value), + extension = "csv", + bytes = mainScreenViewModel.flatDoseList.first().toCsv().toByteArray() + ) + } + }, + onImportCsv = { loadCsvPicker.launch() }, + onInitDemo = { mainScreenViewModel.initDemoData() } + ), - demoVersion = true - ) + demoVersion = true + ) + } } } diff --git a/app/src/desktopMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepositoryImpl.kt b/app/src/desktopMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepositoryImpl.kt index 5dd2208..ca8840a 100644 --- a/app/src/desktopMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepositoryImpl.kt +++ b/app/src/desktopMain/kotlin/net/cacheux/nvp/app/repository/PreferencesRepositoryImpl.kt @@ -1,5 +1,6 @@ package net.cacheux.nvp.app.repository +import net.cacheux.nvplib.utils.StateFlowWrapper import net.cacheux.nvplib.utils.stateFlowWrapper import java.io.File import java.util.Properties @@ -14,6 +15,7 @@ class PreferencesRepositoryImpl: PreferencesRepository { } } + override val theme: StateFlowWrapper = stateFlowWrapper(Theme.THEME_LIGHT) override val groupEnabled = booleanStateFlowWrapper("groupEnabled", "true") override val groupDelay = intStateFlowWrapper("groupDelay", "60") override val autoIgnoreEnabled = booleanStateFlowWrapper("autoIgnoreEnabled", "true") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index ca787b2..eefec42 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { commonMain.dependencies { implementation(project(":model")) implementation(project(":utils")) + implementation(libs.kotlinx.coroutines.core) implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) diff --git a/ui/src/commonMain/composeResources/values-fr/strings.xml b/ui/src/commonMain/composeResources/values-fr/strings.xml index e5d37bd..ac68061 100644 --- a/ui/src/commonMain/composeResources/values-fr/strings.xml +++ b/ui/src/commonMain/composeResources/values-fr/strings.xml @@ -29,6 +29,10 @@ Paramètres + Thème + Clair + Sombre + Système Grouper les doses Afficher les doses dans l'intervalle spécifié comme une seule Intervalle de groupe diff --git a/ui/src/commonMain/composeResources/values/strings.xml b/ui/src/commonMain/composeResources/values/strings.xml index 1a1fb13..1fafd70 100644 --- a/ui/src/commonMain/composeResources/values/strings.xml +++ b/ui/src/commonMain/composeResources/values/strings.xml @@ -29,6 +29,10 @@ Settings + Theme + Light + Dark + System Group doses Display all doses within the specified delay as one Group delay diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseGroupDetails.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseGroupDetails.kt index b54afe5..7749bdd 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseGroupDetails.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseGroupDetails.kt @@ -21,7 +21,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontStyle @@ -51,7 +50,7 @@ fun DoseGroupDetails( val selected = remember { mutableStateListOf() } Column( - modifier = modifier.background(Color.White) + modifier = modifier.background(MaterialTheme.colorScheme.background) .testTag("doseGroupDetails") .pointerInput(doseGroup) { detectTapGestures( @@ -118,7 +117,7 @@ fun DoseDetails( ) { Box( modifier - .background(Color.White) + .background(MaterialTheme.colorScheme.surfaceContainer) .padding(4.dp), ) { Row { @@ -126,13 +125,19 @@ fun DoseDetails( modifier = Modifier.weight(1f), text = dose.displayedValue(), fontWeight = FontWeight.Bold, - color = if (dose.ignored) Color.LightGray else Color.Black + color = if (dose.ignored) + MaterialTheme.colorScheme.onSurfaceVariant + else + MaterialTheme.colorScheme.onSurface ) Text( text = format.format(Date(dose.time)), fontStyle = FontStyle.Italic, - color = if (dose.ignored) Color.LightGray else Color.DarkGray + color = if (dose.ignored) + MaterialTheme.colorScheme.onSurfaceVariant + else + MaterialTheme.colorScheme.onSurface ) } } diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt index 1d95b00..9737044 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt @@ -49,7 +49,8 @@ fun DoseList( modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .padding(8.dp), + .padding(8.dp) + .background(MaterialTheme.colorScheme.surfaceBright), contentAlignment = Alignment.Center, ) { Text( @@ -93,7 +94,8 @@ fun DoseListHeader( Text( text = headerDate.format(Date(date)), fontSize = 14.sp, - fontWeight = FontWeight.SemiBold + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSecondaryContainer ) } } @@ -118,6 +120,7 @@ fun DoseListItem( Column( modifier = Modifier .padding(horizontal = 4.dp) + .fillMaxWidth() ) { Text( fontSize = 14.sp, diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/MainScreen.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/MainScreen.kt index 348fb05..1fa6960 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/MainScreen.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/MainScreen.kt @@ -5,8 +5,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MoreVert @@ -102,6 +100,7 @@ fun MainScreen( } ) { Scaffold( + modifier = Modifier.background(MaterialTheme.colorScheme.surfaceBright), topBar = { CustomTopBar( onNavClick = { @@ -147,8 +146,8 @@ fun CustomTopBar( CenterAlignedTopAppBar( colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - titleContentColor = MaterialTheme.colorScheme.primary, + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, ), title = { Text(text = stringResource(Res.string.app_name)) @@ -160,6 +159,7 @@ fun CustomTopBar( ) { Icon( imageVector = Icons.Filled.Menu, + tint = MaterialTheme.colorScheme.onPrimary, contentDescription = stringResource(Res.string.open_drawer) ) } @@ -168,6 +168,7 @@ fun CustomTopBar( IconButton(onClick = { dropdownOpened = true }) { Icon( imageVector = Icons.Filled.MoreVert, + tint = MaterialTheme.colorScheme.onPrimary, contentDescription = stringResource(Res.string.open_menu) ) } @@ -182,15 +183,6 @@ fun CustomTopBar( ) } -@Composable -fun ItemList(items: List, onDoseClick: (DoseGroup) -> Unit = {}) { - LazyColumn { - items(items) { item -> - DoseDisplay(dose = item, onClick = onDoseClick) - } - } -} - @Composable fun DoseDisplay( dose: DoseGroup, @@ -210,12 +202,14 @@ fun DoseDisplay( Text( fontSize = 16.sp, fontStyle = FontStyle.Italic, - text = format.format(Date(dose.getTime())) + text = format.format(Date(dose.getTime())), + color = MaterialTheme.colorScheme.onSurface ) Text( fontSize = 24.sp, fontWeight = FontWeight.SemiBold, - text = dose.getTotal().toString() + text = dose.getTotal().toString(), + color = MaterialTheme.colorScheme.onSurface ) HorizontalDivider() } @@ -233,18 +227,6 @@ fun MainScreenPreview() { MainScreen(items) } - -@Preview -@Composable -fun PreviewItemList() { - val items = listOf( - testDoseGroup(testDateTime(12, 1, 12), 12), - testDoseGroup(testDateTime(12, 1, 13), 13), - testDoseGroup(testDateTime(12, 1, 14), 14), - ) - ItemList(items) -} - @Preview @Composable fun PreviewDoseDisplay() { diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SelectPreference.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SelectPreference.kt new file mode 100644 index 0000000..3f7e4cb --- /dev/null +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SelectPreference.kt @@ -0,0 +1,145 @@ +package net.cacheux.nvp.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +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.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import net.cacheux.nvp.ui.ui.generated.resources.Res +import net.cacheux.nvp.ui.ui.generated.resources.cancel +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun SelectPreference( + label: String, + value: StateWrapper, + options: List>, + testTag: String = "" +) { + var showDialog by remember { mutableStateOf(false) } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { showDialog = true } + .testTag(testTag) + ) { + Column( + modifier = Modifier + .prefPadding() + .fillMaxWidth() + ) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge + ) + val currentLabel = options.firstOrNull { it.first == value.value }?.second + ?: value.value?.toString().orEmpty() + Text( + text = currentLabel, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + + if (showDialog) { + SelectPreferencePopup( + label, value, options, { showDialog = false } + ) + } +} + +@Composable +fun SelectPreferencePopup( + label: String, + value: StateWrapper, + options: List>, + onDismiss: () -> Unit = {}, +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(text = label) }, + text = { + Column { + options.forEach { (optValue, optLabel) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + value.setter(optValue) + onDismiss() + } + .padding(8.dp) + ) { + RadioButton( + modifier = Modifier + .align(CenterVertically), + selected = value.value == optValue, + onClick = { + value.setter(optValue) + onDismiss() + } + ) + Text( + modifier = Modifier + .padding(start = 8.dp) + .align(CenterVertically), + text = optLabel, + fontSize = 16.sp, + ) + } + } + } + }, + confirmButton = { + + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(Res.string.cancel)) + } + } + ) +} + +@Preview +@Composable +fun SelectPreferencePreview() { + MaterialTheme { + SelectPreference( + "Visual mode", + stateWrapper("light"), + listOf("light" to "Light", "dark" to "Dark"), + + ) + } +} + +@Preview +@Composable +fun SelectPreferencePopupPreview() { + MaterialTheme { + SelectPreferencePopup( + "Visual mode", + stateWrapper("light"), + listOf("light" to "Light", "dark" to "Dark"), + ) + } +} \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SettingsScreen.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SettingsScreen.kt index a895d7d..b1ff369 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SettingsScreen.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/SettingsScreen.kt @@ -1,7 +1,6 @@ package net.cacheux.nvp.ui import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -14,7 +13,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import net.cacheux.nvp.ui.ui.generated.resources.Res @@ -27,8 +25,11 @@ import net.cacheux.nvp.ui.ui.generated.resources.group_delay import net.cacheux.nvp.ui.ui.generated.resources.group_delay_suffix import net.cacheux.nvp.ui.ui.generated.resources.group_doses import net.cacheux.nvp.ui.ui.generated.resources.group_doses_details -import net.cacheux.nvp.ui.ui.generated.resources.pen_settings import net.cacheux.nvp.ui.ui.generated.resources.settings +import net.cacheux.nvp.ui.ui.generated.resources.theme +import net.cacheux.nvp.ui.ui.generated.resources.theme_dark +import net.cacheux.nvp.ui.ui.generated.resources.theme_light +import net.cacheux.nvp.ui.ui.generated.resources.theme_system import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -43,6 +44,7 @@ fun Modifier.prefPadding() = padding( data class SettingsScreenParams( val onBack: () -> Unit = {}, + val theme: StateWrapper = stateWrapper(0), val groupDose: StateWrapper = stateWrapper(true), val groupDelay: StateWrapper = stateWrapper(60), val autoIgnoreEnabled: StateWrapper = stateWrapper(true), @@ -73,6 +75,19 @@ fun SettingsScreen( Column( modifier = Modifier.padding(innerPadding) ) { + + SelectPreference( + label = stringResource(Res.string.theme), + value = params.theme, + options = listOf( + 0 to stringResource(Res.string.theme_light), + 1 to stringResource(Res.string.theme_dark), + 2 to stringResource(Res.string.theme_system), + ) + ) + + PrefDivider() + ExpandableSwitch( label = stringResource(Res.string.group_doses), subLabel = stringResource(Res.string.group_doses_details), From 222c76f51795f119b4f03eef7ce406ad0da52dc5 Mon Sep 17 00:00:00 2001 From: Leo CACHEUX Date: Sat, 24 Jan 2026 18:08:47 +0100 Subject: [PATCH 2/2] Adjust color transparency in dark mode --- .../kotlin/net/cacheux/nvp/ui/DoseList.kt | 17 ++++++++++++----- .../kotlin/net/cacheux/nvp/ui/utils.kt | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt index 9737044..490e464 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/DoseList.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -112,7 +113,9 @@ fun DoseListItem( Box( modifier = Modifier .background( - color = dose.doses.first().color.hexToColor().copy(alpha = 0.5f) + color = dose.doses.first().color.hexToColor().copy( + alpha = if (isInDarkMode()) 0.3f else 0.5f + ) ) .fillMaxWidth() .clickable { onClick(dose) } @@ -125,11 +128,13 @@ fun DoseListItem( Text( fontSize = 14.sp, fontStyle = FontStyle.Italic, + color = MaterialTheme.colorScheme.onSurface, text = format.format(Date(dose.getTime())) ) Text( fontSize = 18.sp, fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, text = dose.displayedTotal() ) @@ -156,10 +161,12 @@ fun DoseListPreview() { testDoseGroup(testDateTime(12, 1, 15, date = 2), 14), testDoseGroup(testDateTime(12, 1, 14, date = 3), 14), ) - DoseList( - items, - currentDoseGroup = current - ) + MaterialTheme(colorScheme = darkColorScheme()) { + DoseList( + items, + currentDoseGroup = current + ) + } } @Preview diff --git a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/utils.kt b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/utils.kt index 818df47..dce0502 100644 --- a/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/utils.kt +++ b/ui/src/commonMain/kotlin/net/cacheux/nvp/ui/utils.kt @@ -1,5 +1,7 @@ package net.cacheux.nvp.ui +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import net.cacheux.nvplib.utils.StateFlowWrapper @@ -19,3 +21,7 @@ fun stateWrapper(initValue: T, setter: (T) -> Unit = {}) = StateWrapper( value = initValue, setter = setter ) + +@Composable +fun isInDarkMode() = + MaterialTheme.colorScheme.background == darkColorScheme().background