From 9b46b10f932802e5c6db27541bdc44da24873652 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 29 Nov 2022 10:48:39 +0100 Subject: [PATCH 1/8] [#335] Move change theme feature in the top app bar --- .../java/com/orange/ods/demo/ui/MainScreen.kt | 50 +++++++++++++++- .../com/orange/ods/demo/ui/MainTopAppBar.kt | 60 ++++++++++++------- .../orange/ods/demo/ui/MainTopAppBarState.kt | 2 +- .../orange/ods/demo/ui/about/AboutScreen.kt | 34 ----------- demo/src/main/res/drawable/ic_palette.xml | 4 ++ demo/src/main/res/values/strings.xml | 8 +-- 6 files changed, 97 insertions(+), 61 deletions(-) create mode 100644 demo/src/main/res/drawable/ic_palette.xml diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt index 6c03f1dac..05bb62701 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt @@ -14,6 +14,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -24,23 +25,32 @@ import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.Dialog import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost import androidx.navigation.navigation import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.orange.ods.compose.text.OdsTextH6 import com.orange.ods.compose.theme.OdsTheme +import com.orange.ods.demo.R import com.orange.ods.demo.ui.about.addAboutGraph import com.orange.ods.demo.ui.components.addComponentsGraph import com.orange.ods.demo.ui.components.tabs.FixedTabRow import com.orange.ods.demo.ui.components.tabs.ScrollableTabRow import com.orange.ods.demo.ui.guidelines.addGuidelinesGraph +import com.orange.ods.demo.ui.utilities.composable.RadioButtonListItem import com.orange.ods.demo.ui.utilities.extension.isDarkModeEnabled import com.orange.ods.theme.OdsThemeConfigurationContract import com.orange.ods.theme.orange.OrangeThemeConfiguration @@ -71,6 +81,8 @@ fun MainScreen(themeConfigurations: Set) { LocalMainThemeManager provides mainState.themeState, LocalOdsDemoGuideline provides mainState.themeState.currentThemeConfiguration.demoGuideline, ) { + var changeThemeDialogVisible by remember { mutableStateOf(false) } + OdsTheme( themeConfiguration = mainState.themeState.currentThemeConfiguration, darkThemeEnabled = configuration.isDarkModeEnabled @@ -85,7 +97,8 @@ fun MainScreen(themeConfigurations: Set) { titleRes = mainState.topAppBarState.titleRes.value, shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar, state = mainState.topAppBarState, - upPress = mainState::upPress + upPress = mainState::upPress, + onChangeThemeActionClick = { changeThemeDialogVisible = true } ) // Display tabs in the top bar if needed MainTabs(mainTabsState = mainState.tabsState) @@ -111,6 +124,12 @@ fun MainScreen(themeConfigurations: Set) { mainNavGraph(navigateToElement = mainState::navigateToElement) } } + + if (changeThemeDialogVisible) { + ChangeThemeDialog(themeState = mainState.themeState, onDismiss = { + changeThemeDialogVisible = false + }) + } } } } @@ -121,6 +140,35 @@ private fun getCurrentThemeConfiguration(themeConfigurations: Set Unit) { + val selectedRadio = rememberSaveable { mutableStateOf(themeState.currentThemeConfiguration.name) } + + Box(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m))) { + Dialog(onDismissRequest = onDismiss) { + Column(modifier = Modifier.background(OdsTheme.colors.surface)) { + OdsTextH6( + text = stringResource(R.string.top_app_bar_action_change_theme_desc), + modifier = Modifier + .padding(top = dimensionResource(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s)) + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + ) + themeState.themeConfigurations.forEach { themeConfiguration -> + RadioButtonListItem( + label = themeConfiguration.name, + selectedRadio = selectedRadio, + currentRadio = themeConfiguration.name, + onClick = { + themeState.currentThemeConfiguration = themeConfiguration + onDismiss() + } + ) + } + } + } + } +} + @Composable private fun SystemBarsColorSideEffect() { val systemUiController = rememberSystemUiController() diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBar.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBar.kt index a714a444c..b8a981183 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBar.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBar.kt @@ -41,7 +41,8 @@ fun MainTopAppBar( titleRes: Int, shouldShowUpNavigationIcon: Boolean, state: MainTopAppBarState, - upPress: () -> Unit + upPress: () -> Unit, + onChangeThemeActionClick: () -> Unit ) { OdsTopAppBar( title = stringResource(id = titleRes), @@ -49,7 +50,7 @@ fun MainTopAppBar( { Icon( imageVector = Icons.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.back_icon_content_description) + contentDescription = stringResource(id = R.string.top_app_bar_back_icon_desc) ) } } else null, @@ -57,25 +58,17 @@ fun MainTopAppBar( actions = { val context = LocalContext.current repeat(state.actionCount.value) { index -> - if (index == 0) { - val configuration = LocalConfiguration.current - val mainThemeManager = LocalMainThemeManager.current - - val painterRes = if (configuration.isDarkModeEnabled) R.drawable.ic_ui_light_mode else R.drawable.ic_ui_dark_mode - val contentDescriptionRes = - if (configuration.isDarkModeEnabled) R.string.theme_changer_icon_content_description_light else R.string.theme_changer_icon_content_description_dark - OdsTopAppBarActionButton( - onClick = { mainThemeManager.darkModeEnabled = !configuration.isDarkModeEnabled }, - painter = painterResource(id = painterRes), - contentDescription = stringResource(id = contentDescriptionRes) - ) - } else { - val action = topAppBarDemoActions[index - 1] - OdsTopAppBarActionButton( - onClick = { clickOnElement(context, context.getString(action.titleRes)) }, - painter = painterResource(id = action.iconRes), - contentDescription = stringResource(id = action.titleRes) - ) + when (index) { + 0 -> TopAppBarChangeThemeActionButton(onClick = onChangeThemeActionClick) + 1 -> TopAppBarChangeModeActionButton() + else -> { + val action = topAppBarDemoActions[index - 1] + OdsTopAppBarActionButton( + onClick = { clickOnElement(context, context.getString(action.titleRes)) }, + painter = painterResource(id = action.iconRes), + contentDescription = stringResource(id = action.titleRes) + ) + } } } if (state.isOverflowMenuEnabled) { @@ -86,6 +79,31 @@ fun MainTopAppBar( ) } +@Composable +private fun TopAppBarChangeThemeActionButton(onClick: () -> Unit) { + OdsTopAppBarActionButton( + onClick = { onClick() }, + painter = painterResource(id = R.drawable.ic_palette), + contentDescription = stringResource(id = R.string.top_app_bar_action_change_theme_desc) + ) +} + +@Composable +private fun TopAppBarChangeModeActionButton() { + val configuration = LocalConfiguration.current + val mainThemeManager = LocalMainThemeManager.current + + val painterRes = if (configuration.isDarkModeEnabled) R.drawable.ic_ui_light_mode else R.drawable.ic_ui_dark_mode + val iconDesc = + if (configuration.isDarkModeEnabled) R.string.top_app_bar_action_change_mode_to_light_desc else R.string.top_app_bar_action_change_mode_to_dark_desc + + OdsTopAppBarActionButton( + onClick = { mainThemeManager.darkModeEnabled = !configuration.isDarkModeEnabled }, + painter = painterResource(id = painterRes), + contentDescription = stringResource(id = iconDesc) + ) +} + @Composable private fun OverflowMenu() { var showMenu by remember { mutableStateOf(false) } diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBarState.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBarState.kt index 241eca5c0..a3a9c969e 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBarState.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainTopAppBarState.kt @@ -48,7 +48,7 @@ class MainTopAppBarState( companion object { val DefaultConfiguration = TopAppBarConfiguration( isNavigationIconEnabled = true, - actionCount = 1, + actionCount = 2, isOverflowMenuEnabled = false ) } diff --git a/demo/src/main/java/com/orange/ods/demo/ui/about/AboutScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/about/AboutScreen.kt index 1e0b5c7e7..7c99f901b 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/about/AboutScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/about/AboutScreen.kt @@ -12,7 +12,6 @@ package com.orange.ods.demo.ui.about import android.content.Context import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -22,26 +21,18 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll 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.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.window.Dialog import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.text.OdsTextCaption import com.orange.ods.compose.text.OdsTextH4 -import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.demo.R -import com.orange.ods.demo.ui.LocalMainThemeManager import com.orange.ods.demo.ui.LocalMainTopAppBarManager import com.orange.ods.demo.ui.utilities.compat.PackageManagerCompat -import com.orange.ods.demo.ui.utilities.composable.RadioButtonListItem import com.orange.ods.demo.ui.utilities.extension.versionCode import com.orange.ods.utilities.extension.ifNotNull import com.orange.ods.utilities.extension.orElse @@ -50,9 +41,6 @@ import com.orange.ods.utilities.extension.orElse fun AboutScreen(onAboutItemClick: (Long) -> Unit) { LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_about) - val mainThemeManager = LocalMainThemeManager.current - var dialogVisible by remember { mutableStateOf(false) } - Column( modifier = Modifier .verticalScroll(rememberScrollState()) @@ -80,34 +68,12 @@ fun AboutScreen(onAboutItemClick: (Long) -> Unit) { Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.spacing_m))) - OdsListItem(text = stringResource(id = R.string.about_menu_theme), modifier = Modifier.clickable { - dialogVisible = true - }) for (aboutItem in aboutItems) { OdsListItem(text = stringResource(id = aboutItem.titleRes), modifier = Modifier.clickable { onAboutItemClick(aboutItem.id) }) } } - - - if (dialogVisible) { - val selectedRadio = remember { mutableStateOf(mainThemeManager.currentThemeConfiguration.name) } - - Dialog(onDismissRequest = { dialogVisible = false }) { - Column(modifier = Modifier.background(OdsTheme.colors.surface)) { - mainThemeManager.themeConfigurations.forEach { themeConfiguration -> - RadioButtonListItem( - label = themeConfiguration.name, - selectedRadio = selectedRadio, - currentRadio = themeConfiguration.name, - onClick = { mainThemeManager.currentThemeConfiguration = themeConfiguration } - ) - } - } - } - } - } private fun getVersion(context: Context): String { diff --git a/demo/src/main/res/drawable/ic_palette.xml b/demo/src/main/res/drawable/ic_palette.xml new file mode 100644 index 000000000..2b27f15de --- /dev/null +++ b/demo/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,4 @@ + + + diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index e047d7a8d..c61b1660b 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -18,9 +18,10 @@ About - Change to dark mode - Change to light mode - Back + Back + Change theme + Change to dark mode + Change to light mode Colour @@ -296,6 +297,5 @@ Legal notice Privacy policy Changelog - Theme \ No newline at end of file From d208f3e3031f12984beb927a464abe68c5eafe5d Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 29 Nov 2022 16:07:48 +0100 Subject: [PATCH 2/8] [#335] Save the selected theme in DataStore file for persistance --- .../com/orange/ods/gradle/Dependencies.kt | 1 + .../kotlin/com/orange/ods/gradle/Versions.kt | 3 +- demo/build.gradle.kts | 1 + demo/src/main/AndroidManifest.xml | 2 +- .../ods/demo/{ui => }/OdsDemoApplication.kt | 3 +- .../ods/demo/domain/DataStoreService.kt | 18 ++++++++ .../ods/demo/domain/DataStoreServiceImpl.kt | 44 +++++++++++++++++++ .../orange/ods/demo/domain/DomainModule.kt | 28 ++++++++++++ .../java/com/orange/ods/demo/ui/MainScreen.kt | 21 ++++++--- .../com/orange/ods/demo/ui/MainViewModel.kt | 31 +++++++++++++ 10 files changed, 143 insertions(+), 9 deletions(-) rename demo/src/main/java/com/orange/ods/demo/{ui => }/OdsDemoApplication.kt (90%) create mode 100644 demo/src/main/java/com/orange/ods/demo/domain/DataStoreService.kt create mode 100644 demo/src/main/java/com/orange/ods/demo/domain/DataStoreServiceImpl.kt create mode 100644 demo/src/main/java/com/orange/ods/demo/domain/DomainModule.kt create mode 100644 demo/src/main/java/com/orange/ods/demo/ui/MainViewModel.kt diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt index 8ebe5732c..c4fe93145 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt @@ -26,6 +26,7 @@ object Dependencies { const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose}" const val coreKtx = "androidx.core:core-ktx:${Versions.core}" const val customViewPoolingContainer = "androidx.customview:customview-poolingcontainer:${Versions.customViewPoolingContainer}" + const val datastorePreferences = "androidx.datastore:datastore-preferences:${Versions.datastorePreferences}" const val firebaseAppDistributionGradlePlugin = "com.google.firebase:firebase-appdistribution-gradle:${Versions.firebaseAppDistributionGradlePlugin}" const val firebaseBom = "com.google.firebase:firebase-bom:${Versions.firebaseBom}" const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-ktx" diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt index 639a7767a..9b2e3f4e8 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt @@ -24,10 +24,11 @@ object Versions { const val compose = "1.2.0-rc02" const val core = "1.7.0" const val customViewPoolingContainer = "1.0.0" - const val googleServicesGradlePlugin = "4.3.14" + const val datastorePreferences = "1.0.0" const val firebaseAppDistributionGradlePlugin = "3.0.1" const val firebaseBom = "30.0.0" const val firebaseCrashlyticsGradlePlugin = "2.8.1" + const val googleServicesGradlePlugin = "4.3.14" const val hilt = "2.44" const val jUnit = "4.13.2" const val kotlin = "1.6.21" diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 2b81c97d1..4285c35df 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -129,6 +129,7 @@ dependencies { implementation(Dependencies.browser) implementation(Dependencies.hiltAndroid) kapt(Dependencies.hiltCompiler) + implementation(Dependencies.datastorePreferences) debugImplementation(Dependencies.composeUiTooling) } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index b581eac9f..f3259d00e 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ package="com.orange.ods.demo"> by preferencesDataStore(name = "datastore") + +class DataStoreServiceImpl @Inject constructor(private val context: Context) : DataStoreService { + + override suspend fun putString(key: String, value: String) { + val preferenceKey = stringPreferencesKey(key) + context.dataStore.edit { preferences -> + preferences[preferenceKey] = value + } + } + + override suspend fun getString(key: String): String? { + return try { + val preferenceKey = stringPreferencesKey(key) + val preferences = context.dataStore.data.first() + preferences[preferenceKey] + } catch (e: NoSuchElementException) { + e.printStackTrace() + null + } + } + +} \ No newline at end of file diff --git a/demo/src/main/java/com/orange/ods/demo/domain/DomainModule.kt b/demo/src/main/java/com/orange/ods/demo/domain/DomainModule.kt new file mode 100644 index 000000000..124f60e25 --- /dev/null +++ b/demo/src/main/java/com/orange/ods/demo/domain/DomainModule.kt @@ -0,0 +1,28 @@ +/* + * + * Copyright 2021 Orange + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * / + */ + +package com.orange.ods.demo.domain + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DomainModule { + + @Singleton + @Provides + fun provideDataStoreService(@ApplicationContext context: Context): DataStoreService = DataStoreServiceImpl(context) +} diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt index 05bb62701..9d6df63f9 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.window.Dialog +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost @@ -59,11 +60,18 @@ import com.orange.ods.utilities.extension.orElse @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) @Preview(showBackground = true) @Composable -fun MainScreen(themeConfigurations: Set) { +fun MainScreen(themeConfigurations: Set, mainViewModel: MainViewModel = viewModel()) { val isSystemInDarkTheme = isSystemInDarkTheme() val mainState = rememberMainState( themeState = rememberMainThemeState( - currentThemeConfiguration = rememberSaveable { mutableStateOf(getCurrentThemeConfiguration(themeConfigurations)) }, + currentThemeConfiguration = rememberSaveable { + mutableStateOf( + getCurrentThemeConfiguration( + mainViewModel.getUserThemeName(), + themeConfigurations + ) + ) + }, darkModeEnabled = rememberSaveable { mutableStateOf(isSystemInDarkTheme) }, themeConfigurations = themeConfigurations.toList() ) @@ -127,6 +135,7 @@ fun MainScreen(themeConfigurations: Set) { if (changeThemeDialogVisible) { ChangeThemeDialog(themeState = mainState.themeState, onDismiss = { + mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name) changeThemeDialogVisible = false }) } @@ -135,9 +144,11 @@ fun MainScreen(themeConfigurations: Set) { } } -private fun getCurrentThemeConfiguration(themeConfigurations: Set): OdsThemeConfigurationContract { - // If another theme than Orange is injected, select it otherwise take the Orange theme which is always present - return themeConfigurations.firstOrNull { it.name != OrangeThemeConfiguration.OrangeThemeName }.orElse { themeConfigurations.first() } +private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConfigurations: Set): OdsThemeConfigurationContract { + // Return the stored user theme configuration if it exists. If not, return the Orange theme configuration or the first existing theme configuration + return themeConfigurations.firstOrNull { it.name == storedUserThemeName }.takeIf { it != null }.orElse { + themeConfigurations.firstOrNull { it.name == OrangeThemeConfiguration.OrangeThemeName }.orElse { themeConfigurations.first() } + } } @Composable diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainViewModel.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainViewModel.kt new file mode 100644 index 000000000..14ae774a4 --- /dev/null +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainViewModel.kt @@ -0,0 +1,31 @@ +/* + * + * Copyright 2021 Orange + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * / + */ + +package com.orange.ods.demo.ui + +import androidx.lifecycle.ViewModel +import com.orange.ods.demo.domain.DataStoreService +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.runBlocking +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor(private val dataStoreService: DataStoreService) : ViewModel() { + + companion object { + private const val UserThemeNameKey = "userThemeName" + } + + fun storeUserThemeName(themeName: String) = runBlocking { + dataStoreService.putString(UserThemeNameKey, themeName) + } + + fun getUserThemeName(): String? = runBlocking { dataStoreService.getString(UserThemeNameKey) } +} \ No newline at end of file From 09c2826c7d0513b6b624cca89d259c23a5869f2b Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 29 Nov 2022 16:11:15 +0100 Subject: [PATCH 3/8] [#335] Update changelog --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 4a1cb3196..2fe959d9c 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - \[All\] Add `NOTICE.txt` file ([#356](https://github.com/Orange-OpenSource/ods-android/issues/356)) +- \[Demo\] Save the user theme selection in order to reopen the app with this theme [#335](https://github.com/Orange-OpenSource/ods-android/issues/335) - \[Demo\] Add Snackbar component ([#114](https://github.com/Orange-OpenSource/ods-android/issues/114)) - \[Demo\] Display an error message below text fields if customization error switch is on ([#338](https://github.com/Orange-OpenSource/ods-android/issues/338)) - \[Lib\] Add `OdsSnackbar` and `OdsSnackbarHost` composable to manage snackbars display ([#114](https://github.com/Orange-OpenSource/ods-android/issues/114)) @@ -22,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[All\] Version numbers in changelog now display changes on GitHub when clicked ([#322](https://github.com/Orange-OpenSource/ods-android/issues/322)) - \[All\] Update documentation [#334](https://github.com/Orange-OpenSource/ods-android/issues/334) - \[All\] Upgrade compile and target SDK versions to 33 [#343](https://github.com/Orange-OpenSource/ods-android/issues/343) +- \[Demo\] Move change theme feature in top app bar by clicking on a palette icon [#335](https://github.com/Orange-OpenSource/ods-android/issues/335) - \[Demo\] Add customization bottom sheets for buttons ([#303](https://github.com/Orange-OpenSource/ods-android/issues/303)) - \[Demo\] Replace action buttons switches by a counter in cards customization bottom sheet ([#327](https://github.com/Orange-OpenSource/ods-android/issues/327)) - \[Demo\] Add customization bottom sheets for sliders ([#307](https://github.com/Orange-OpenSource/ods-android/issues/307)) From b0a1969e77c345059f5691629016be6ab8cde7f2 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 5 Dec 2022 11:15:57 +0100 Subject: [PATCH 4/8] [#335] Review: Rename "datastore" to "dataStore" --- buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt | 2 +- buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt | 2 +- demo/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt index c4fe93145..3c0608fb9 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt @@ -26,7 +26,7 @@ object Dependencies { const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose}" const val coreKtx = "androidx.core:core-ktx:${Versions.core}" const val customViewPoolingContainer = "androidx.customview:customview-poolingcontainer:${Versions.customViewPoolingContainer}" - const val datastorePreferences = "androidx.datastore:datastore-preferences:${Versions.datastorePreferences}" + const val dataStorePreferences = "androidx.datastore:datastore-preferences:${Versions.dataStorePreferences}" const val firebaseAppDistributionGradlePlugin = "com.google.firebase:firebase-appdistribution-gradle:${Versions.firebaseAppDistributionGradlePlugin}" const val firebaseBom = "com.google.firebase:firebase-bom:${Versions.firebaseBom}" const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-ktx" diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt index 9b2e3f4e8..9dc4b3ff9 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt @@ -24,7 +24,7 @@ object Versions { const val compose = "1.2.0-rc02" const val core = "1.7.0" const val customViewPoolingContainer = "1.0.0" - const val datastorePreferences = "1.0.0" + const val dataStorePreferences = "1.0.0" const val firebaseAppDistributionGradlePlugin = "3.0.1" const val firebaseBom = "30.0.0" const val firebaseCrashlyticsGradlePlugin = "2.8.1" diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 4285c35df..ff588da2b 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -129,7 +129,7 @@ dependencies { implementation(Dependencies.browser) implementation(Dependencies.hiltAndroid) kapt(Dependencies.hiltCompiler) - implementation(Dependencies.datastorePreferences) + implementation(Dependencies.dataStorePreferences) debugImplementation(Dependencies.composeUiTooling) } From 530fb5d8e800cfadcfa696b8686c83b9e81f1871 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 5 Dec 2022 11:33:06 +0100 Subject: [PATCH 5/8] [#335] Review: Simplify expression --- .../com/orange/ods/demo/domain/DataStoreServiceImpl.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/demo/src/main/java/com/orange/ods/demo/domain/DataStoreServiceImpl.kt b/demo/src/main/java/com/orange/ods/demo/domain/DataStoreServiceImpl.kt index f0d0ef0e4..e400192d6 100644 --- a/demo/src/main/java/com/orange/ods/demo/domain/DataStoreServiceImpl.kt +++ b/demo/src/main/java/com/orange/ods/demo/domain/DataStoreServiceImpl.kt @@ -16,7 +16,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject private val Context.dataStore: DataStore by preferencesDataStore(name = "datastore") @@ -31,13 +31,9 @@ class DataStoreServiceImpl @Inject constructor(private val context: Context) : D } override suspend fun getString(key: String): String? { - return try { - val preferenceKey = stringPreferencesKey(key) - val preferences = context.dataStore.data.first() + val preferenceKey = stringPreferencesKey(key) + return context.dataStore.data.firstOrNull()?.let { preferences -> preferences[preferenceKey] - } catch (e: NoSuchElementException) { - e.printStackTrace() - null } } From e17630a67d22a59aeee27dd7d47f69301ffa289a Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 5 Dec 2022 11:43:02 +0100 Subject: [PATCH 6/8] [#335] Review: Simplify expression --- demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt index 9d6df63f9..d27645c19 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt @@ -146,9 +146,9 @@ fun MainScreen(themeConfigurations: Set, mainView private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConfigurations: Set): OdsThemeConfigurationContract { // Return the stored user theme configuration if it exists. If not, return the Orange theme configuration or the first existing theme configuration - return themeConfigurations.firstOrNull { it.name == storedUserThemeName }.takeIf { it != null }.orElse { - themeConfigurations.firstOrNull { it.name == OrangeThemeConfiguration.OrangeThemeName }.orElse { themeConfigurations.first() } - } + return themeConfigurations.firstOrNull { it.name == storedUserThemeName } + .orElse { themeConfigurations.firstOrNull { it.name == OrangeThemeConfiguration.OrangeThemeName } } + .orElse { themeConfigurations.first() } } @Composable From e29feae09b960e26dc9b1dc5f7eedb87113364a9 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 5 Dec 2022 11:45:41 +0100 Subject: [PATCH 7/8] [#335] Review: Remove useless Box --- .../java/com/orange/ods/demo/ui/MainScreen.kt | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt index d27645c19..1383e35f8 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt @@ -155,26 +155,24 @@ private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConf private fun ChangeThemeDialog(themeState: MainThemeState, onDismiss: () -> Unit) { val selectedRadio = rememberSaveable { mutableStateOf(themeState.currentThemeConfiguration.name) } - Box(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m))) { - Dialog(onDismissRequest = onDismiss) { - Column(modifier = Modifier.background(OdsTheme.colors.surface)) { - OdsTextH6( - text = stringResource(R.string.top_app_bar_action_change_theme_desc), - modifier = Modifier - .padding(top = dimensionResource(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s)) - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + Dialog(onDismissRequest = onDismiss) { + Column(modifier = Modifier.background(OdsTheme.colors.surface)) { + OdsTextH6( + text = stringResource(R.string.top_app_bar_action_change_theme_desc), + modifier = Modifier + .padding(top = dimensionResource(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s)) + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + ) + themeState.themeConfigurations.forEach { themeConfiguration -> + RadioButtonListItem( + label = themeConfiguration.name, + selectedRadio = selectedRadio, + currentRadio = themeConfiguration.name, + onClick = { + themeState.currentThemeConfiguration = themeConfiguration + onDismiss() + } ) - themeState.themeConfigurations.forEach { themeConfiguration -> - RadioButtonListItem( - label = themeConfiguration.name, - selectedRadio = selectedRadio, - currentRadio = themeConfiguration.name, - onClick = { - themeState.currentThemeConfiguration = themeConfiguration - onDismiss() - } - ) - } } } } From d37ca4493d193720d1c8e31cfa693d657e497a95 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 5 Dec 2022 12:00:36 +0100 Subject: [PATCH 8/8] [#335] Review: Improve theme change dialog --- .../java/com/orange/ods/demo/ui/MainScreen.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt index 1383e35f8..7dc4a512a 100644 --- a/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt +++ b/demo/src/main/java/com/orange/ods/demo/ui/MainScreen.kt @@ -134,10 +134,15 @@ fun MainScreen(themeConfigurations: Set, mainView } if (changeThemeDialogVisible) { - ChangeThemeDialog(themeState = mainState.themeState, onDismiss = { - mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name) - changeThemeDialogVisible = false - }) + ChangeThemeDialog( + themeState = mainState.themeState, + dismissDialog = { + changeThemeDialogVisible = false + }, + onThemeSelected = { + mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name) + } + ) } } } @@ -152,10 +157,10 @@ private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConf } @Composable -private fun ChangeThemeDialog(themeState: MainThemeState, onDismiss: () -> Unit) { +private fun ChangeThemeDialog(themeState: MainThemeState, dismissDialog: () -> Unit, onThemeSelected: () -> Unit) { val selectedRadio = rememberSaveable { mutableStateOf(themeState.currentThemeConfiguration.name) } - Dialog(onDismissRequest = onDismiss) { + Dialog(onDismissRequest = dismissDialog) { Column(modifier = Modifier.background(OdsTheme.colors.surface)) { OdsTextH6( text = stringResource(R.string.top_app_bar_action_change_theme_desc), @@ -169,8 +174,11 @@ private fun ChangeThemeDialog(themeState: MainThemeState, onDismiss: () -> Unit) selectedRadio = selectedRadio, currentRadio = themeConfiguration.name, onClick = { - themeState.currentThemeConfiguration = themeConfiguration - onDismiss() + if (themeConfiguration != themeState.currentThemeConfiguration) { + themeState.currentThemeConfiguration = themeConfiguration + onThemeSelected() + } + dismissDialog() } ) }