diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7628070c..7ccfe25b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -162,4 +162,7 @@ dependencies {
// Retrofit with Moshi Converter
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
+
+ // Themmo
+ implementation("com.github.sadellie:themmo:0.0.2")
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sadellie/unitto/MainActivity.kt b/app/src/main/java/com/sadellie/unitto/MainActivity.kt
index 44254f8f..67e12fad 100644
--- a/app/src/main/java/com/sadellie/unitto/MainActivity.kt
+++ b/app/src/main/java/com/sadellie/unitto/MainActivity.kt
@@ -22,17 +22,21 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
+import androidx.compose.animation.core.tween
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.sadellie.unitto.data.NavRoutes.ABOUT_SCREEN
import com.sadellie.unitto.data.NavRoutes.LEFT_LIST_SCREEN
import com.sadellie.unitto.data.NavRoutes.MAIN_SCREEN
import com.sadellie.unitto.data.NavRoutes.RIGHT_LIST_SCREEN
import com.sadellie.unitto.data.NavRoutes.SETTINGS_SCREEN
-import com.sadellie.unitto.data.preferences.AppTheme
+import com.sadellie.unitto.data.NavRoutes.THEMES_SCREEN
import com.sadellie.unitto.screens.MainViewModel
import com.sadellie.unitto.screens.about.AboutScreen
import com.sadellie.unitto.screens.main.MainScreen
@@ -40,34 +44,58 @@ import com.sadellie.unitto.screens.second.LeftSideScreen
import com.sadellie.unitto.screens.second.RightSideScreen
import com.sadellie.unitto.screens.second.SecondViewModel
import com.sadellie.unitto.screens.setttings.SettingsScreen
-import com.sadellie.unitto.ui.theme.UnittoTheme
+import com.sadellie.unitto.screens.theming.ThemesScreen
+import com.sadellie.unitto.screens.theming.ThemesViewModel
+import com.sadellie.unitto.ui.theme.AppTypography
+import com.sadellie.unitto.ui.theme.DarkThemeColors
+import com.sadellie.unitto.ui.theme.LightThemeColors
import dagger.hilt.android.AndroidEntryPoint
+import io.github.sadellie.themmo.Themmo
+import io.github.sadellie.themmo.ThemmoController
+import io.github.sadellie.themmo.rememberThemmoController
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val mainViewModel: MainViewModel by viewModels()
private val secondViewModel: SecondViewModel by viewModels()
+ private val themesViewModel: ThemesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
- val navController = rememberNavController()
- val currentAppTheme: Int = mainViewModel.currentTheme
+ themesViewModel.collectThemeOptions()
- // We don't draw anything until we know what theme we need to use
- if (currentAppTheme != AppTheme.NOT_SET) {
- UnittoTheme(
- currentAppTheme = currentAppTheme
- ) {
- UnittoApp(
- navController = navController,
- mainViewModel = mainViewModel,
- secondViewModel = secondViewModel
- )
- }
+ val themmoController = rememberThemmoController(
+ lightColorScheme = LightThemeColors,
+ darkColorScheme = DarkThemeColors,
+ // Anything below will not called if theming mode is still loading from DataStore
+ themingMode = themesViewModel.themingMode ?: return@setContent,
+ dynamicThemeEnabled = themesViewModel.enableDynamic,
+ amoledThemeEnabled = themesViewModel.enableAmoled
+ )
+ val navController = rememberNavController()
+ val sysUiController = rememberSystemUiController()
+
+ Themmo(
+ themmoController = themmoController,
+ typography = AppTypography,
+ animationSpec = tween(150)
+ ) {
+ val backgroundColor = MaterialTheme.colorScheme.background
+
+ UnittoApp(
+ navController = navController,
+ mainViewModel = mainViewModel,
+ secondViewModel = secondViewModel,
+ themesViewModel = themesViewModel,
+ themmoController = it
+ )
+
+ SideEffect { sysUiController.setSystemBarsColor(backgroundColor) }
}
+
}
}
@@ -81,7 +109,9 @@ class MainActivity : ComponentActivity() {
fun UnittoApp(
navController: NavHostController,
mainViewModel: MainViewModel,
- secondViewModel: SecondViewModel
+ secondViewModel: SecondViewModel,
+ themesViewModel: ThemesViewModel,
+ themmoController: ThemmoController
) {
NavHost(
navController = navController,
@@ -123,6 +153,14 @@ fun UnittoApp(
)
}
+ composable(THEMES_SCREEN) {
+ ThemesScreen(
+ navigateUpAction = { navController.navigateUp() },
+ themmoController = themmoController,
+ viewModel = themesViewModel
+ )
+ }
+
composable(ABOUT_SCREEN) {
AboutScreen(navigateUpAction = { navController.navigateUp() })
}
diff --git a/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt b/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt
index b62e2fe7..a3ecbd7f 100644
--- a/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt
+++ b/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt
@@ -28,5 +28,6 @@ object NavRoutes {
const val RIGHT_LIST_SCREEN = "RightScreen"
const val SETTINGS_SCREEN = "SettingsScreen"
+ const val THEMES_SCREEN = "ThemesScreen"
const val ABOUT_SCREEN = "AboutScreen"
}
diff --git a/app/src/main/java/com/sadellie/unitto/data/preferences/AppTheme.kt b/app/src/main/java/com/sadellie/unitto/data/preferences/AppTheme.kt
deleted file mode 100644
index d5e6db66..00000000
--- a/app/src/main/java/com/sadellie/unitto/data/preferences/AppTheme.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Unitto is a unit converter for Android
- * Copyright (c) 2022-2022 Elshan Agaev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.sadellie.unitto.data.preferences
-
-import android.os.Build
-import com.sadellie.unitto.R
-
-
-/**
- * All possible state of theme in the app
- */
-object AppTheme {
- // Used on app launch when we don't know which theme to use
- const val NOT_SET = 0
-
- const val AUTO = 1
- const val LIGHT = 2
- const val DARK = 3
- const val LIGHT_DYNAMIC = 4
- const val DARK_DYNAMIC = 5
- const val AMOLED = 6
-}
-
-/**
- * Device specific map of available themes. Used in settings
- */
-val APP_THEMES: Map by lazy {
- // Dynamic themes are only for Android 8.1 and later
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
- mapOf(
- AppTheme.AUTO to R.string.force_auto_mode,
- AppTheme.LIGHT to R.string.force_light_mode,
- AppTheme.DARK to R.string.force_dark_mode,
- AppTheme.AMOLED to R.string.force_amoled_mode,
- AppTheme.LIGHT_DYNAMIC to R.string.force_light_dynamic_mode,
- AppTheme.DARK_DYNAMIC to R.string.force_dark_dynamic_mode,
- )
- } else {
- mapOf(
- AppTheme.AUTO to R.string.force_auto_mode,
- AppTheme.LIGHT to R.string.force_light_mode,
- AppTheme.DARK to R.string.force_dark_mode,
- AppTheme.AMOLED to R.string.force_amoled_mode,
- )
- }
-}
diff --git a/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt b/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt
index d9a5e670..1cc4b570 100644
--- a/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt
+++ b/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt
@@ -22,19 +22,24 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.sadellie.unitto.data.units.AbstractUnit
import com.sadellie.unitto.data.units.MyUnitIDS
+import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
+import okio.IOException
import javax.inject.Inject
-
/**
* Represents user preferences that are user across the app
*
- * @property currentAppTheme Current [AppTheme] to be used
+ * @property themingMode [ThemingMode] from Themmo
+ * @property enableDynamicTheme Use dynamic color scheme
+ * @property enableAmoledTheme Use amoled color scheme
* @property digitsPrecision Current [PRECISIONS]. Number of digits in fractional part
* @property separator Current [Separator] that used to separate thousands
* @property outputFormat Current [OutputFormat] that is applied to converted value (not input)
@@ -43,7 +48,9 @@ import javax.inject.Inject
* @property enableAnalytics Whether or not user wants to share application usage data
*/
data class UserPreferences(
- val currentAppTheme: Int,
+ val themingMode: ThemingMode,
+ val enableDynamicTheme: Boolean,
+ val enableAmoledTheme: Boolean,
val digitsPrecision: Int,
val separator: Int,
val outputFormat: Int,
@@ -61,7 +68,9 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
* Keys for DataStore
*/
private object PrefsKeys {
- val CURRENT_APP_THEME = intPreferencesKey("CURRENT_APP_THEME")
+ val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
+ val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
+ val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
@@ -71,9 +80,21 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
val userPreferencesFlow: Flow = dataStore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
.map { preferences ->
- val currentAppTheme: Int =
- preferences[PrefsKeys.CURRENT_APP_THEME] ?: AppTheme.AUTO
+ val themingMode: ThemingMode =
+ preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
+ ?: ThemingMode.AUTO
+ val enableDynamicTheme: Boolean =
+ preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
+ val enableAmoledTheme: Boolean =
+ preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
val digitsPrecision: Int =
preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
val separator: Int =
@@ -88,7 +109,9 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
preferences[PrefsKeys.ENABLE_ANALYTICS] ?: true
UserPreferences(
- currentAppTheme = currentAppTheme,
+ themingMode = themingMode,
+ enableDynamicTheme = enableDynamicTheme,
+ enableAmoledTheme = enableAmoledTheme,
digitsPrecision = digitsPrecision,
separator = separator,
outputFormat = outputFormat,
@@ -98,17 +121,6 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
)
}
- /**
- * Update current theme preference in DataStore
- *
- * @param appTheme [AppTheme] to change to
- */
- suspend fun updateCurrentAppTheme(appTheme: Int) {
- dataStore.edit { preferences ->
- preferences[PrefsKeys.CURRENT_APP_THEME] = appTheme
- }
- }
-
/**
* Update precision preference in DataStore
*
@@ -166,4 +178,37 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
preferences[PrefsKeys.LATEST_RIGHT_SIDE] = rightSideUnit.unitId
}
}
+
+ /**
+ * Update [ThemingMode]. Saves value as a string.
+ *
+ * @param themingMode [ThemingMode] to save.
+ */
+ suspend fun updateThemingMode(themingMode: ThemingMode) {
+ dataStore.edit { preferences ->
+ preferences[PrefsKeys.THEMING_MODE] = themingMode.name
+ }
+ }
+
+ /**
+ * Update preference on whether or not generate color scheme from device wallpaper.
+ *
+ * @param enabled True if user wants to enable this feature.
+ */
+ suspend fun updateDynamicTheme(enabled: Boolean) {
+ dataStore.edit { preferences ->
+ preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] = enabled
+ }
+ }
+
+ /**
+ * Update preference on whether or not use true black colors.
+ *
+ * @param enabled True if user wants to enable this feature.
+ */
+ suspend fun updateAmoledTheme(enabled: Boolean) {
+ dataStore.edit { preferences ->
+ preferences[PrefsKeys.ENABLE_AMOLED_THEME] = enabled
+ }
+ }
}
diff --git a/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt b/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt
index b4fe3637..fb4ccfd1 100644
--- a/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt
+++ b/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt
@@ -30,7 +30,6 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.sadellie.unitto.data.KEY_0
import com.sadellie.unitto.data.KEY_DOT
import com.sadellie.unitto.data.KEY_MINUS
-import com.sadellie.unitto.data.preferences.AppTheme
import com.sadellie.unitto.data.preferences.MAX_PRECISION
import com.sadellie.unitto.data.preferences.OutputFormat
import com.sadellie.unitto.data.preferences.Separator
@@ -59,8 +58,6 @@ class MainViewModel @Inject constructor(
private val allUnitsRepository: AllUnitsRepository
) : ViewModel() {
- var currentTheme: Int by mutableStateOf(AppTheme.NOT_SET)
- private set
var precision: Int by mutableStateOf(3)
private set
var separator: Int by mutableStateOf(Separator.SPACES)
@@ -70,15 +67,6 @@ class MainViewModel @Inject constructor(
var enableAnalytics: Boolean by mutableStateOf(false)
private set
- /**
- * See [UserPreferencesRepository.updateCurrentAppTheme]
- */
- fun updateCurrentAppTheme(appTheme: Int) {
- viewModelScope.launch {
- userPrefsRepository.updateCurrentAppTheme(appTheme)
- }
- }
-
/**
* See [UserPreferencesRepository.updateDigitsPrecision]
*/
@@ -407,13 +395,11 @@ class MainViewModel @Inject constructor(
*
* Any change in user preferences will update mutableStateOf so composables, that rely on this
* values recompose when actually needed. For example, when you change output format, composable
- * in MainActivity will not be recomposed even though it needs currentTheme which is also in
- * user preferences/
+ * in MainActivity will not be recomposed.
*/
private fun observePreferenceChanges() {
viewModelScope.launch {
userPrefsRepository.userPreferencesFlow.collect { userPref ->
- currentTheme = userPref.currentAppTheme
precision = userPref.digitsPrecision
separator = userPref.separator.also { Formatter.setSeparator(it) }
outputFormat = userPref.outputFormat
diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt
index 33cbaec3..3575bb06 100644
--- a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt
+++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt
@@ -18,7 +18,6 @@
package com.sadellie.unitto.screens.setttings
-import android.os.Build
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -31,7 +30,6 @@ import com.sadellie.unitto.BuildConfig
import com.sadellie.unitto.R
import com.sadellie.unitto.data.NavRoutes.ABOUT_SCREEN
import com.sadellie.unitto.data.NavRoutes.THEMES_SCREEN
-import com.sadellie.unitto.data.preferences.APP_THEMES
import com.sadellie.unitto.data.preferences.OUTPUT_FORMAT
import com.sadellie.unitto.data.preferences.PRECISIONS
import com.sadellie.unitto.data.preferences.SEPARATORS
@@ -91,7 +89,7 @@ fun SettingsScreen(
SettingsListItem(
stringResource(R.string.theme_setting),
stringResource(R.string.theme_setting_support)
- ) { dialogState = DialogState.THEME }
+ ) { navControllerAction(THEMES_SCREEN) }
}
// CURRENCY RATE NOTE
@@ -192,19 +190,6 @@ fun SettingsScreen(
supportText = stringResource(id = R.string.output_format_setting_info)
)
}
- DialogState.THEME -> {
- AlertDialogWithList(
- title = stringResource(id = R.string.theme_setting),
- listItems = APP_THEMES,
- selectedItemIndex = mainViewModel.currentTheme,
- selectAction = { mainViewModel.updateCurrentAppTheme(it) },
- dismissAction = { resetDialog() },
- // Show note for users with devices that support custom Dynamic theming
- supportText = if (Build.VERSION.SDK_INT in (Build.VERSION_CODES.O_MR1..Build.VERSION_CODES.R)) stringResource(
- id = R.string.theme_setting_info
- ) else null
- )
- }
DialogState.CURRENCY_RATE -> {
AlertDialogWithList(
title = stringResource(id = R.string.currency_rates_note_title),
@@ -222,5 +207,5 @@ fun SettingsScreen(
* All possible states for alert dialog that opens when user clicks on settings.
*/
private enum class DialogState {
- NONE, PRECISION, SEPARATOR, OUTPUT_FORMAT, THEME, CURRENCY_RATE,
+ NONE, PRECISION, SEPARATOR, OUTPUT_FORMAT, CURRENCY_RATE,
}
diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt
index e1dc941c..cfbf9421 100644
--- a/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt
+++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt
@@ -25,12 +25,21 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
+import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
+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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
@@ -121,3 +130,59 @@ fun SettingsListItem(
) = BasicSettingsListItem(label, supportText, { onSwitchChange(switchState) }) {
Switch(checked = switchState, onCheckedChange = { onSwitchChange(!it) })
}
+
+/**
+ * Represents one item in list on Settings screen with drop-down menu.
+ *
+ * @param label Main text.
+ * @param supportText Text that is located below label.
+ * @param allOptions Options in drop-down menu.
+ * @param selected Selected option.
+ * @param onSelectedChange Action to perform when drop-down menu item is selected.
+ */
+@Composable
+fun SettingsListItem(
+ label: String,
+ supportText: String? = null,
+ allOptions: Collection,
+ selected: T,
+ onSelectedChange: (T) -> Unit
+) = BasicSettingsListItem(label, supportText, {}) {
+ var dropDownExpanded by rememberSaveable { mutableStateOf(false) }
+ var currentOption by rememberSaveable { mutableStateOf(selected) }
+
+ ExposedDropdownMenuBox(
+ modifier = Modifier,
+ expanded = dropDownExpanded,
+ onExpandedChange = { dropDownExpanded = it }
+ ) {
+ OutlinedTextField(
+ modifier = Modifier.widthIn(1.dp),
+ value = currentOption.toString(),
+ onValueChange = {},
+ readOnly = true,
+ singleLine = true,
+ enabled = false,
+ colors = TextFieldDefaults.outlinedTextFieldColors(
+ disabledBorderColor = MaterialTheme.colorScheme.outline,
+ disabledTextColor = MaterialTheme.colorScheme.onSurface,
+ )
+ )
+ ExposedDropdownMenu(
+ modifier = Modifier.exposedDropdownSize(),
+ expanded = dropDownExpanded,
+ onDismissRequest = { dropDownExpanded = false }
+ ) {
+ allOptions.forEach {
+ DropdownMenuItem(
+ text = { Text(it.toString()) },
+ onClick = {
+ currentOption = it
+ onSelectedChange(it)
+ dropDownExpanded = false
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt
new file mode 100644
index 00000000..e12c7eb4
--- /dev/null
+++ b/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt
@@ -0,0 +1,89 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2022 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.screens.theming
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.sadellie.unitto.R
+import com.sadellie.unitto.screens.common.UnittoLargeTopAppBar
+import com.sadellie.unitto.screens.setttings.components.SettingsListItem
+import io.github.sadellie.themmo.ThemingMode
+import io.github.sadellie.themmo.ThemmoController
+
+@Composable
+fun ThemesScreen(
+ navigateUpAction: () -> Unit = {},
+ themmoController: ThemmoController,
+ viewModel: ThemesViewModel
+) {
+ UnittoLargeTopAppBar(
+ title = stringResource(id = R.string.theme_setting),
+ navigateUpAction = navigateUpAction
+ ) { paddingValues ->
+ LazyColumn(contentPadding = paddingValues) {
+ item {
+ SettingsListItem(
+ label = stringResource(R.string.theme_setting),
+ allOptions = ThemingMode.values().toList(),
+ selected = themmoController.currentThemingMode,
+ onSelectedChange = {
+ themmoController.setThemingMode(it)
+ viewModel.updateThemingMode(it)
+ }
+ )
+ }
+
+ item {
+ SettingsListItem(
+ label = stringResource(R.string.enable_dynamic_colors),
+ supportText = stringResource(R.string.enable_dynamic_colors_support),
+ switchState = themmoController.isDynamicThemeEnabled,
+ onSwitchChange = {
+ themmoController.enableDynamicTheme(!it)
+ viewModel.updateDynamicTheme(!it)
+ }
+ )
+ }
+
+ item {
+ AnimatedVisibility(
+ visible = (themmoController.currentThemingMode != ThemingMode.FORCE_LIGHT),
+ enter = expandVertically() + fadeIn(),
+ exit = shrinkVertically() + fadeOut(),
+ ) {
+ SettingsListItem(
+ label = stringResource(R.string.force_amoled_mode),
+ supportText = stringResource(R.string.force_amoled_mode_support),
+ switchState = themmoController.isAmoledThemeEnabled,
+ onSwitchChange = {
+ themmoController.enableAmoledTheme(!it)
+ viewModel.updateAmoledTheme(!it)
+ }
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt b/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt
new file mode 100644
index 00000000..c0d911c6
--- /dev/null
+++ b/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt
@@ -0,0 +1,80 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2022 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.screens.theming
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.sadellie.unitto.data.preferences.UserPreferencesRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sadellie.themmo.ThemingMode
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ThemesViewModel @Inject constructor(
+ private val userPrefsRepository: UserPreferencesRepository
+) : ViewModel() {
+
+ /**
+ * @see [UserPreferencesRepository.updateThemingMode]
+ */
+ fun updateThemingMode(themingMode: ThemingMode) {
+ viewModelScope.launch {
+ userPrefsRepository.updateThemingMode(themingMode)
+ }
+ }
+
+ /**
+ * @see [UserPreferencesRepository.updateDynamicTheme]
+ */
+ fun updateDynamicTheme(enabled: Boolean) {
+ viewModelScope.launch {
+ userPrefsRepository.updateDynamicTheme(enabled)
+ }
+ }
+
+ /**
+ * @see [UserPreferencesRepository.updateAmoledTheme]
+ */
+ fun updateAmoledTheme(enabled: Boolean) {
+ viewModelScope.launch {
+ userPrefsRepository.updateAmoledTheme(enabled)
+ }
+ }
+
+ var themingMode: ThemingMode? by mutableStateOf(null)
+ var enableDynamic by mutableStateOf(false)
+ var enableAmoled by mutableStateOf(false)
+
+ /**
+ * Collect saved theming options. Used on app launch.
+ */
+ fun collectThemeOptions() {
+ viewModelScope.launch {
+ val userPref = userPrefsRepository.userPreferencesFlow.first()
+ themingMode = userPref.themingMode
+ enableDynamic = userPref.enableDynamicTheme
+ enableAmoled = userPref.enableAmoledTheme
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sadellie/unitto/ui/theme/Color.kt b/app/src/main/java/com/sadellie/unitto/ui/theme/Color.kt
index 83956851..ed289108 100644
--- a/app/src/main/java/com/sadellie/unitto/ui/theme/Color.kt
+++ b/app/src/main/java/com/sadellie/unitto/ui/theme/Color.kt
@@ -73,5 +73,3 @@ val md_theme_dark_onSurfaceVariant = Color(0xFFc1c9be)
val md_theme_dark_outline = Color(0xFF8b9389)
val md_theme_dark_inverseOnSurface = Color(0xFF1a1c19)
val md_theme_dark_inverseSurface = Color(0xFFe1e3dd)
-
-val md_theme_amoled_black = Color(0xFF000000)
diff --git a/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemeUtils.kt b/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemeUtils.kt
deleted file mode 100644
index f72918d3..00000000
--- a/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemeUtils.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Unitto is a unit converter for Android
- * Copyright (c) 2022-2022 Elshan Agaev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.sadellie.unitto.ui.theme
-
-import android.app.WallpaperManager
-import android.content.Context
-import android.os.Build
-import androidx.compose.material3.ColorScheme
-import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.luminance
-
-
-/**
- * Shifts colors to make them brighter
- */
-@Stable
-private fun Color.shiftTo255(ratio: Float): Color {
- return this
- .copy(
- alpha,
- red + (1.0f - red) * ratio,
- green + (1.0f - green) * ratio,
- blue + (1.0f - blue) * ratio,
- )
-}
-
-/**
- * Shifts colors to make them darker
- */
-@Stable
-private fun Color.shiftTo0(ratio: Float): Color {
- return this
- .copy(
- alpha,
- red * (1.0f - ratio),
- green * (1.0f - ratio),
- blue * (1.0f - ratio),
- )
-}
-
-/**
- * Decides which colors fits the best for the color that is used as a background
- */
-@Stable
-private fun Color.getAppropriateTextColor(): Color {
- return if (luminance() > 0.5) Color.Black else Color.White
-}
-
-/**
- * Function to return dynamic light theme. Determines which API to use based on Android version
- */
-fun dynamicLightTheme(context: Context): ColorScheme {
- return when {
- // Android 12+ devices use new api
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> dynamicLightColorScheme(context)
- // Dynamic theming for devices with Android 8.1 up to Android 11
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> {
- // Wallpaper colors can be null. We return default theme for such cases
- val wallpaperColors = WallpaperManager.getInstance(context)
- .getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
- ?: return LightThemeColors
-
- val primary = Color(
- red = wallpaperColors.primaryColor.red(),
- green = wallpaperColors.primaryColor.green(),
- blue = wallpaperColors.primaryColor.blue()
- )
- // Secondary color can be null. For such cases we just generate it from primary
- val secondary: Color = primary.shiftTo255(0.5f)
- val background = primary.shiftTo255(0.9f)
- val onBackground = background.getAppropriateTextColor()
-
- return lightColorScheme(
- // Settings screen group text, units screen units group text
- primary = primary,
- // Switch thumb color
- onPrimary = primary.getAppropriateTextColor(),
- onPrimaryContainer = primary.shiftTo0(0.7f),
- // Selected unit, group, keyboard buttons
- secondaryContainer = secondary,
- onSecondaryContainer = secondary.getAppropriateTextColor(),
- // Background color for all screens
- background = background,
- // Main screen input/output text color
- onBackground = onBackground,
- // Collapsable top bar background
- surface = background,
- // Main screen Unitto text, disabled buttons
- // Settings screen title and back icon, dialog window title, not selected radio button color
- // Third party licenses screen title and back icon
- onSurface = onBackground,
- surfaceVariant = primary.shiftTo255(0.5f),
- // Main Screen settings icon, Not selected chips text, short unit names
- // Settings items secondary text
- onSurfaceVariant = primary.shiftTo0(0.80f),
- // Chips outline and cards outline on Third party licenses screen
- outline = primary.shiftTo0(0.8f),
- )
- }
- // Just in case
- else -> LightThemeColors
- }
-}
-
-/**
- * Function to return dynamic light theme. Determines which API to use based on Android version
- */
-fun dynamicDarkTheme(context: Context): ColorScheme {
- return when {
- // Android 12+ devices use new api
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> dynamicDarkColorScheme(context)
- // Dynamic theming for devices with Android 8.1 up to Android 11
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> {
- // Wallpaper colors can be null. We return default theme for such cases
- val wallpaperColors = WallpaperManager.getInstance(context)
- .getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
- ?: return DarkThemeColors
-
- val primary = Color(
- red = wallpaperColors.primaryColor.red(),
- green = wallpaperColors.primaryColor.green(),
- blue = wallpaperColors.primaryColor.blue()
- )
- // Secondary color can be null. For such cases we just generate it from primary
- val secondary: Color = primary.shiftTo0(0.7f)
- val background = primary.shiftTo0(0.9f)
- val onBackground = background.getAppropriateTextColor()
-
- return darkColorScheme(
- // Settings screen group text, units screen units group text
- primary = primary,
- // Switch thumb color
- onPrimary = primary.getAppropriateTextColor(),
- onPrimaryContainer = primary.shiftTo255(0.7f),
- // Selected unit, group, keyboard buttons
- secondaryContainer = secondary,
- onSecondaryContainer = secondary.getAppropriateTextColor(),
- // Background color for all screens
- background = background,
- // Main screen input/output text color
- onBackground = onBackground,
- // Collapsable top bar background
- surface = background,
- // Main screen Unitto text, disabled buttons
- // Settings screen title and back icon, dialog window title, not selected radio button color
- // Third party licenses screen title and back icon
- onSurface = onBackground,
- surfaceVariant = primary.shiftTo0(0.5f),
- // Main Screen settings icon, Not selected chips text, short unit names
- // Settings items secondary text
- onSurfaceVariant = primary.shiftTo255(0.80f),
- // Chips outline and cards outline on Third party licenses screen
- outline = primary.shiftTo255(0.8f),
- )
- }
- // Just in case
- else -> DarkThemeColors
- }
-}
diff --git a/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemes.kt b/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemes.kt
index a5e4ad87..a92b86da 100644
--- a/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemes.kt
+++ b/app/src/main/java/com/sadellie/unitto/ui/theme/ColorSchemes.kt
@@ -80,10 +80,3 @@ val DarkThemeColors by lazy {
inverseSurface = md_theme_dark_inverseSurface,
)
}
-
-val AmoledThemeColors by lazy {
- DarkThemeColors.copy(
- background = md_theme_amoled_black,
- surface = md_theme_amoled_black,
- )
-}
diff --git a/app/src/main/java/com/sadellie/unitto/ui/theme/Theme.kt b/app/src/main/java/com/sadellie/unitto/ui/theme/Theme.kt
deleted file mode 100644
index 237d5022..00000000
--- a/app/src/main/java/com/sadellie/unitto/ui/theme/Theme.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Unitto is a unit converter for Android
- * Copyright (c) 2022-2022 Elshan Agaev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.sadellie.unitto.ui.theme
-
-import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.platform.LocalContext
-import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import com.sadellie.unitto.data.preferences.AppTheme
-
-
-@Composable
-fun UnittoTheme(
- currentAppTheme: Int,
- content: @Composable () -> Unit
-) {
- // Dynamic color is only for Android 12 and higher
- val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
- val sysUiController = rememberSystemUiController()
-
- val colors = when (currentAppTheme) {
- AppTheme.DARK -> DarkThemeColors
- AppTheme.LIGHT -> LightThemeColors
- AppTheme.AMOLED -> AmoledThemeColors
- AppTheme.LIGHT_DYNAMIC -> dynamicLightTheme(LocalContext.current)
- AppTheme.DARK_DYNAMIC -> dynamicDarkTheme(LocalContext.current)
- // When it is set to Auto
- else -> {
- when {
- dynamicColor and !isSystemInDarkTheme() -> dynamicLightTheme(LocalContext.current)
- dynamicColor and isSystemInDarkTheme() -> dynamicDarkTheme(LocalContext.current)
- !dynamicColor and !isSystemInDarkTheme() -> LightThemeColors
- !dynamicColor and isSystemInDarkTheme() -> DarkThemeColors
- // This case is here just in case, not actually used
- else -> LightThemeColors
- }
- }
- }
-
- MaterialTheme(
- colorScheme = colors,
- typography = AppTypography,
- content = content
- )
-
- SideEffect {
- sysUiController.setSystemBarsColor(
- color = colors.background
- )
- }
-}
diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
index 7df1b4ab..001fa879 100644
--- a/app/src/main/res/values-en-rGB/strings.xml
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -675,7 +675,7 @@
Settings
- Theme
+ Themes
Precision
Separator
Output format
@@ -717,8 +717,6 @@
Light
Dark
AMOLED Dark
- Dynamic light
- Dynamic dark
Loading…
@@ -738,5 +736,8 @@
Clear input
Add or remove unit from favorites
Empty search result
+ Dynamic colors
+ Generate theme from your current wallpaper
+ Use black background for dark themes
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 5b2bc248..44984314 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -492,7 +492,7 @@
Перевести из
Перевести в
Настройки
- Тема
+ Темы
Точность
Разделитель
Формат вывода
@@ -523,8 +523,6 @@
Светлая
Темная
Темная AMOLED
- Динамическая светлая
- Динамическая темная
Загрузка…
Ошибка
Скопировано %1$s!
@@ -673,5 +671,8 @@
Отправлять статистику использования
Все данные анонимны и зашифрованы
Название версии
+ Динамичные цвета
+ Использовать цвета обоев рабочего стола
+ Использовать черный фон в темных темах
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cebae7f1..41c6add4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -936,7 +936,7 @@
Settings
- Theme
+ Themes
Precision
Separator
Output format
@@ -991,8 +991,9 @@
Light
Dark
AMOLED Dark
- Dynamic light
- Dynamic dark
+ Use black background for dark themes
+ Dynamic colors
+ Generate theme from your current wallpaper
Loading…
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1e49e70b..110e6142 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,6 +3,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven(url = "https://jitpack.io")
}
}
rootProject.name = "Unitto"