diff --git a/app/src/main/java/com/sadellie/unitto/MainActivity.kt b/app/src/main/java/com/sadellie/unitto/MainActivity.kt index 1bd3d827..ddd494e8 100644 --- a/app/src/main/java/com/sadellie/unitto/MainActivity.kt +++ b/app/src/main/java/com/sadellie/unitto/MainActivity.kt @@ -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 ) } 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 a3ecbd7f..46858f29 100644 --- a/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt +++ b/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt @@ -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" diff --git a/app/src/main/java/com/sadellie/unitto/data/preferences/Separator.kt b/app/src/main/java/com/sadellie/unitto/data/preferences/Separator.kt index bf582481..b1a96a8d 100644 --- a/app/src/main/java/com/sadellie/unitto/data/preferences/Separator.kt +++ b/app/src/main/java/com/sadellie/unitto/data/preferences/Separator.kt @@ -34,8 +34,8 @@ object Separator { */ val SEPARATORS: Map 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 ) } 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 a0fb3ebe..7daaaefc 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 @@ -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 ) /** 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 8b506be9..8a4fb1a9 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/MainViewModel.kt @@ -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() } } } diff --git a/app/src/main/java/com/sadellie/unitto/screens/about/AboutScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/AboutScreen.kt similarity index 98% rename from app/src/main/java/com/sadellie/unitto/screens/about/AboutScreen.kt rename to app/src/main/java/com/sadellie/unitto/screens/setttings/AboutScreen.kt index f2231d86..dd41e44a 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/about/AboutScreen.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/AboutScreen.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package com.sadellie.unitto.screens.about +package com.sadellie.unitto.screens.setttings import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement 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 d5e98146..5d2967de 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 @@ -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) ) diff --git a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt similarity index 54% rename from app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt rename to app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt index 6f053ed3..e51c662c 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesViewModel.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt @@ -16,27 +16,29 @@ * along with this program. If not, see . */ -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) + } + } } } diff --git a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/ThemesScreen.kt similarity index 97% rename from app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt rename to app/src/main/java/com/sadellie/unitto/screens/setttings/ThemesScreen.kt index 606c92c5..9e848e51 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/theming/ThemesScreen.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/ThemesScreen.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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),