Little refactor for settings screen

This commit is contained in:
Sad Ellie 2022-07-23 22:23:40 +03:00
parent 685a3dfc56
commit 956a04bec6
9 changed files with 134 additions and 138 deletions

View File

@ -21,32 +21,34 @@ package com.sadellie.unitto
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.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
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_GRAPH
import com.sadellie.unitto.data.NavRoutes.SETTINGS_SCREEN
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.setttings.SettingsViewModel
import com.sadellie.unitto.screens.setttings.AboutScreen
import com.sadellie.unitto.screens.main.MainScreen
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.screens.theming.ThemesScreen
import com.sadellie.unitto.screens.theming.ThemesViewModel
import com.sadellie.unitto.screens.setttings.ThemesScreen
import com.sadellie.unitto.ui.theme.AppTypography
import com.sadellie.unitto.ui.theme.DarkThemeColors
import com.sadellie.unitto.ui.theme.LightThemeColors
@ -57,20 +59,21 @@ import io.github.sadellie.themmo.rememberThemmoController
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val mainViewModel: MainViewModel by viewModels()
private val themesViewModel: ThemesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val userPrefs = settingsViewModel.userPrefs
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
// Anything below will not called if theming mode is still loading from DataStore
themingMode = userPrefs.themingMode ?: return@setContent,
dynamicThemeEnabled = userPrefs.enableDynamicTheme,
amoledThemeEnabled = userPrefs.enableAmoledTheme
)
val navController = rememberNavController()
val sysUiController = rememberSystemUiController()
@ -84,8 +87,7 @@ class MainActivity : ComponentActivity() {
UnittoApp(
navController = navController,
mainViewModel = mainViewModel,
themesViewModel = themesViewModel,
settingsViewModel = settingsViewModel,
themmoController = it
)
@ -93,21 +95,16 @@ class MainActivity : ComponentActivity() {
}
}
}
override fun onPause() {
mainViewModel.saveLatestPairOfUnits()
super.onPause()
}
}
@Composable
fun UnittoApp(
navController: NavHostController,
mainViewModel: MainViewModel,
themesViewModel: ThemesViewModel,
settingsViewModel: SettingsViewModel,
themmoController: ThemmoController
) {
val secondViewModel: SecondViewModel = hiltViewModel()
val mainViewModel: MainViewModel = hiltViewModel()
NavHost(
navController = navController,
startDestination = MAIN_SCREEN
@ -139,19 +136,32 @@ fun UnittoApp(
)
}
settingGraph(
settingsViewModel = settingsViewModel,
themmoController = themmoController,
navController = navController
)
}
}
private fun NavGraphBuilder.settingGraph(
settingsViewModel: SettingsViewModel,
themmoController: ThemmoController,
navController: NavHostController
) {
navigation(SETTINGS_SCREEN, SETTINGS_GRAPH) {
composable(SETTINGS_SCREEN) {
SettingsScreen(
mainViewModel = mainViewModel,
navigateUpAction = { navController.navigateUp() },
navControllerAction = { route -> navController.navigate(route) }
)
viewModel = settingsViewModel,
navigateUpAction = { navController.navigateUp() }
) { route -> navController.navigate(route) }
}
composable(THEMES_SCREEN) {
ThemesScreen(
navigateUpAction = { navController.navigateUp() },
themmoController = themmoController,
viewModel = themesViewModel
viewModel = settingsViewModel
)
}

View File

@ -27,6 +27,7 @@ object NavRoutes {
const val LEFT_LIST_SCREEN = "LeftScreen"
const val RIGHT_LIST_SCREEN = "RightScreen"
const val SETTINGS_GRAPH = "SettingsGraph"
const val SETTINGS_SCREEN = "SettingsScreen"
const val THEMES_SCREEN = "ThemesScreen"
const val ABOUT_SCREEN = "AboutScreen"

View File

@ -34,8 +34,8 @@ object Separator {
*/
val SEPARATORS: Map<Int, Int> by lazy {
mapOf(
Separator.SPACES to R.string.spaces,
Separator.PERIOD to R.string.period,
Separator.COMMA to R.string.comma,
Separator.SPACES to R.string.spaces
Separator.COMMA to R.string.comma
)
}

View File

@ -37,7 +37,8 @@ import javax.inject.Inject
/**
* Represents user preferences that are user across the app
*
* @property themingMode [ThemingMode] from Themmo
* @property themingMode [ThemingMode] from Themmo. Nullable just to indicate that preferences are
* still loading.
* @property enableDynamicTheme Use dynamic color scheme
* @property enableAmoledTheme Use amoled color scheme
* @property digitsPrecision Current [PRECISIONS]. Number of digits in fractional part
@ -48,15 +49,15 @@ import javax.inject.Inject
* @property enableAnalytics Whether or not user wants to share application usage data
*/
data class UserPreferences(
val themingMode: ThemingMode,
val enableDynamicTheme: Boolean,
val enableAmoledTheme: Boolean,
val digitsPrecision: Int,
val separator: Int,
val outputFormat: Int,
val latestLeftSideUnit: String,
val latestRightSideUnit: String,
val enableAnalytics: Boolean
val themingMode: ThemingMode? = null,
val enableDynamicTheme: Boolean = false,
val enableAmoledTheme: Boolean = false,
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACES,
val outputFormat: Int = OutputFormat.PLAIN,
val latestLeftSideUnit: String = MyUnitIDS.kilometer,
val latestRightSideUnit: String = MyUnitIDS.mile,
val enableAnalytics: Boolean = true
)
/**

View File

@ -25,14 +25,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.analytics.FirebaseAnalytics
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.MAX_PRECISION
import com.sadellie.unitto.data.preferences.OutputFormat
import com.sadellie.unitto.data.preferences.Separator
import com.sadellie.unitto.data.preferences.UserPreferences
import com.sadellie.unitto.data.preferences.UserPreferencesRepository
import com.sadellie.unitto.data.units.AbstractUnit
import com.sadellie.unitto.data.units.AllUnitsRepository
@ -56,55 +55,7 @@ class MainViewModel @Inject constructor(
private val application: Application,
private val allUnitsRepository: AllUnitsRepository
) : ViewModel() {
var precision: Int by mutableStateOf(3)
private set
var separator: Int by mutableStateOf(Separator.SPACES)
private set
var outputFormat: Int by mutableStateOf(OutputFormat.PLAIN)
private set
var enableAnalytics: Boolean by mutableStateOf(false)
private set
/**
* See [UserPreferencesRepository.updateDigitsPrecision]
*/
fun updatePrecision(precision: Int) {
viewModelScope.launch {
userPrefsRepository.updateDigitsPrecision(precision)
convertValue()
}
}
/**
* See [UserPreferencesRepository.updateSeparator]
*/
fun updateSeparator(separator: Int) {
viewModelScope.launch {
userPrefsRepository.updateSeparator(separator)
convertValue()
}
}
/**
* See [UserPreferencesRepository.updateOutputFormat]
*/
fun updateOutputFormat(outputFormat: Int) {
viewModelScope.launch {
userPrefsRepository.updateOutputFormat(outputFormat)
convertValue()
}
}
/**
* See [UserPreferencesRepository.updateEnableAnalytics]
*/
fun updateEnableAnalytics(enableAnalytics: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateEnableAnalytics(enableAnalytics)
FirebaseAnalytics.getInstance(application)
.setAnalyticsCollectionEnabled(enableAnalytics)
}
}
private var userPrefs = UserPreferences()
/**
* Unit we converting from (left side)
@ -130,7 +81,11 @@ class MainViewModel @Inject constructor(
private fun convertValue() {
// Converting value using a specified precision
val convertedValue: BigDecimal =
unitFrom.convert(unitTo, mainUIState.inputValue.toBigDecimal(), precision)
unitFrom.convert(
unitTo,
mainUIState.inputValue.toBigDecimal(),
userPrefs.digitsPrecision
)
/**
* There is a very interesting bug when trailing zeros are not stripped when input
@ -138,11 +93,11 @@ class MainViewModel @Inject constructor(
* is zero, than we make sure there are no trailing zeros.
*/
val resultValue =
if (convertedValue == BigDecimal.ZERO.setScale(precision, RoundingMode.HALF_EVEN)) {
if (convertedValue == BigDecimal.ZERO.setScale(userPrefs.digitsPrecision, RoundingMode.HALF_EVEN)) {
KEY_0
} else {
// Setting result value using a specified OutputFormat
when (outputFormat) {
when (userPrefs.outputFormat) {
OutputFormat.ALLOW_ENGINEERING -> convertedValue.toString()
OutputFormat.FORCE_ENGINEERING -> convertedValue.toEngineeringString()
else -> convertedValue.toPlainString()
@ -183,6 +138,8 @@ class MainViewModel @Inject constructor(
updateCurrenciesBasicUnits()
// We can't call outside of this block. It will set precision to 0 in that case
convertValue()
// Saving latest pair
saveLatestPairOfUnits()
}
}
@ -209,6 +166,8 @@ class MainViewModel @Inject constructor(
)
// We also need to increment counter for the selected unit
incrementCounter(clickedUnit)
// Saving latest pair
saveLatestPairOfUnits()
}
// Changed units, now we can convert
@ -278,7 +237,10 @@ class MainViewModel @Inject constructor(
unitFrom = unitTo.also {
unitTo = unitFrom
}
viewModelScope.launch { updateCurrenciesBasicUnits() }
viewModelScope.launch {
updateCurrenciesBasicUnits()
saveLatestPairOfUnits()
}
// Swapped, can convert now
convertValue()
}
@ -382,10 +344,8 @@ class MainViewModel @Inject constructor(
/**
* Saves latest pair of units into datastore
*/
fun saveLatestPairOfUnits() {
viewModelScope.launch {
userPrefsRepository.updateLatestPairOfUnits(unitFrom, unitTo)
}
private suspend fun saveLatestPairOfUnits() {
userPrefsRepository.updateLatestPairOfUnits(unitFrom, unitTo)
}
/**
@ -395,36 +355,27 @@ class MainViewModel @Inject constructor(
* values recompose when actually needed. For example, when you change output format, composable
* in MainActivity will not be recomposed.
*/
private fun observePreferenceChanges() {
viewModelScope.launch {
userPrefsRepository.userPreferencesFlow.collect { userPref ->
precision = userPref.digitsPrecision
separator = userPref.separator.also { Formatter.setSeparator(it) }
outputFormat = userPref.outputFormat
enableAnalytics = userPref.enableAnalytics.also {
// Maybe this is unnecessary
if (it != enableAnalytics) FirebaseAnalytics.getInstance(application)
.setAnalyticsCollectionEnabled(enableAnalytics)
}
}
suspend private fun observePreferenceChanges() {
userPrefsRepository.userPreferencesFlow.collect {
userPrefs = it
convertValue()
}
}
init {
observePreferenceChanges()
viewModelScope.launch {
val snapshot = userPrefsRepository.userPreferencesFlow.first()
userPrefs = userPrefsRepository.userPreferencesFlow.first()
// First we load latest pair of units
unitFrom = try {
allUnitsRepository.getById(snapshot.latestLeftSideUnit)
allUnitsRepository.getById(userPrefs.latestLeftSideUnit)
} catch (e: java.util.NoSuchElementException) {
Log.w("MainViewModel", "No unit with the given unitId")
allUnitsRepository.getById(MyUnitIDS.kilometer)
}
unitTo = try {
allUnitsRepository.getById(snapshot.latestRightSideUnit)
allUnitsRepository.getById(userPrefs.latestRightSideUnit)
} catch (e: java.util.NoSuchElementException) {
Log.w("MainViewModel", "No unit with the given unitId")
allUnitsRepository.getById(MyUnitIDS.mile)
@ -440,6 +391,7 @@ class MainViewModel @Inject constructor(
mainUIState = mainUIState.copy(isLoadingDatabase = false)
updateCurrenciesBasicUnits()
convertValue()
observePreferenceChanges()
}
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.screens.about
package com.sadellie.unitto.screens.setttings
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement

View File

@ -33,7 +33,6 @@ import com.sadellie.unitto.data.NavRoutes.THEMES_SCREEN
import com.sadellie.unitto.data.preferences.OUTPUT_FORMAT
import com.sadellie.unitto.data.preferences.PRECISIONS
import com.sadellie.unitto.data.preferences.SEPARATORS
import com.sadellie.unitto.screens.MainViewModel
import com.sadellie.unitto.screens.common.UnittoLargeTopAppBar
import com.sadellie.unitto.screens.openLink
import com.sadellie.unitto.screens.setttings.components.AlertDialogWithList
@ -42,7 +41,7 @@ import com.sadellie.unitto.screens.setttings.components.SettingsListItem
@Composable
fun SettingsScreen(
mainViewModel: MainViewModel,
viewModel: SettingsViewModel,
navigateUpAction: () -> Unit,
navControllerAction: (String) -> Unit
) {
@ -121,8 +120,8 @@ fun SettingsScreen(
SettingsListItem(
stringResource(R.string.send_usage_statistics),
stringResource(R.string.send_usage_statistics_support),
mainViewModel.enableAnalytics
) { mainViewModel.updateEnableAnalytics(it) }
viewModel.userPrefs.enableAnalytics
) { viewModel.updateEnableAnalytics(it) }
}
// THIRD PARTY
@ -164,8 +163,8 @@ fun SettingsScreen(
AlertDialogWithList(
title = stringResource(id = R.string.precision_setting),
listItems = PRECISIONS,
selectedItemIndex = mainViewModel.precision,
selectAction = { mainViewModel.updatePrecision(it) },
selectedItemIndex = viewModel.userPrefs.digitsPrecision,
selectAction = { viewModel.updatePrecision(it) },
dismissAction = { resetDialog() },
supportText = stringResource(id = R.string.precision_setting_info)
)
@ -174,8 +173,8 @@ fun SettingsScreen(
AlertDialogWithList(
title = stringResource(id = R.string.separator_setting),
listItems = SEPARATORS,
selectedItemIndex = mainViewModel.separator,
selectAction = { mainViewModel.updateSeparator(it) },
selectedItemIndex = viewModel.userPrefs.separator,
selectAction = { viewModel.updateSeparator(it) },
dismissAction = { resetDialog() }
)
}
@ -183,8 +182,8 @@ fun SettingsScreen(
AlertDialogWithList(
title = stringResource(id = R.string.output_format_setting),
listItems = OUTPUT_FORMAT,
selectedItemIndex = mainViewModel.outputFormat,
selectAction = { mainViewModel.updateOutputFormat(it) },
selectedItemIndex = viewModel.userPrefs.outputFormat,
selectAction = { viewModel.updateOutputFormat(it) },
dismissAction = { resetDialog() },
supportText = stringResource(id = R.string.output_format_setting_info)
)

View File

@ -16,27 +16,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.screens.theming
package com.sadellie.unitto.screens.setttings
import android.app.Application
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.google.firebase.analytics.FirebaseAnalytics
import com.sadellie.unitto.data.preferences.UserPreferences
import com.sadellie.unitto.data.preferences.UserPreferencesRepository
import com.sadellie.unitto.screens.Formatter
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
class SettingsViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val application: Application,
) : ViewModel() {
var themingMode: ThemingMode? by mutableStateOf(null)
var enableDynamic by mutableStateOf(false)
var enableAmoled by mutableStateOf(false)
var userPrefs: UserPreferences by mutableStateOf(UserPreferences())
/**
* @see [UserPreferencesRepository.updateThemingMode]
@ -66,18 +68,49 @@ class ThemesViewModel @Inject constructor(
}
/**
* Collect saved theming options. Used on app launch.
* See [UserPreferencesRepository.updateDigitsPrecision]
*/
private fun collectThemeOptions() {
fun updatePrecision(precision: Int) {
viewModelScope.launch {
val userPref = userPrefsRepository.userPreferencesFlow.first()
themingMode = userPref.themingMode
enableDynamic = userPref.enableDynamicTheme
enableAmoled = userPref.enableAmoledTheme
userPrefsRepository.updateDigitsPrecision(precision)
}
}
/**
* See [UserPreferencesRepository.updateSeparator]
*/
fun updateSeparator(separator: Int) {
viewModelScope.launch {
userPrefsRepository.updateSeparator(separator)
}
}
/**
* See [UserPreferencesRepository.updateOutputFormat]
*/
fun updateOutputFormat(outputFormat: Int) {
viewModelScope.launch {
userPrefsRepository.updateOutputFormat(outputFormat)
}
}
/**
* See [UserPreferencesRepository.updateEnableAnalytics]
*/
fun updateEnableAnalytics(enableAnalytics: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateEnableAnalytics(enableAnalytics)
FirebaseAnalytics.getInstance(application)
.setAnalyticsCollectionEnabled(enableAnalytics)
}
}
init {
collectThemeOptions()
viewModelScope.launch {
userPrefsRepository.userPreferencesFlow.collect {
userPrefs = it
Formatter.setSeparator(it.separator)
}
}
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.screens.theming
package com.sadellie.unitto.screens.setttings
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
@ -36,7 +36,7 @@ import io.github.sadellie.themmo.ThemmoController
fun ThemesScreen(
navigateUpAction: () -> Unit = {},
themmoController: ThemmoController,
viewModel: ThemesViewModel
viewModel: SettingsViewModel
) {
UnittoLargeTopAppBar(
title = stringResource(R.string.theme_setting),