Separate Preferences

This commit is contained in:
Sad Ellie 2023-09-13 15:43:20 +03:00
parent 6eaae86b12
commit c81bcf1438
23 changed files with 429 additions and 477 deletions

View File

@ -41,10 +41,10 @@ internal class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContent {
val uiPrefs = userPrefsRepository.uiPreferencesFlow
val prefs = userPrefsRepository.appPrefs
.collectAsStateWithLifecycle(null).value
if (uiPrefs != null) UnittoApp(uiPrefs)
if (prefs != null) UnittoApp(prefs)
}
}

View File

@ -46,28 +46,28 @@ import com.sadellie.unitto.core.ui.common.isOpen
import com.sadellie.unitto.core.ui.common.open
import com.sadellie.unitto.core.ui.common.rememberUnittoDrawerState
import com.sadellie.unitto.core.ui.model.DrawerItems
import com.sadellie.unitto.core.ui.theme.AppTypographyUnitto
import com.sadellie.unitto.core.ui.theme.AppTypographySystem
import com.sadellie.unitto.core.ui.theme.AppTypographyUnitto
import com.sadellie.unitto.core.ui.theme.DarkThemeColors
import com.sadellie.unitto.core.ui.theme.LightThemeColors
import com.sadellie.unitto.data.userprefs.UIPreferences
import com.sadellie.unitto.data.userprefs.AppPreferences
import io.github.sadellie.themmo.Themmo
import io.github.sadellie.themmo.rememberThemmoController
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun UnittoApp(uiPrefs: UIPreferences) {
internal fun UnittoApp(prefs: AppPreferences) {
val mContext = LocalContext.current
val themmoController = rememberThemmoController(
lightColorScheme = LightThemeColors,
darkColorScheme = DarkThemeColors,
themingMode = uiPrefs.themingMode,
dynamicThemeEnabled = uiPrefs.enableDynamicTheme,
amoledThemeEnabled = uiPrefs.enableAmoledTheme,
customColor = uiPrefs.customColor,
monetMode = uiPrefs.monetMode
themingMode = prefs.themingMode,
dynamicThemeEnabled = prefs.enableDynamicTheme,
amoledThemeEnabled = prefs.enableAmoledTheme,
customColor = prefs.customColor,
monetMode = prefs.monetMode
)
val navController = rememberNavController()
val sysUiController = rememberSystemUiController()
@ -78,9 +78,9 @@ internal fun UnittoApp(uiPrefs: UIPreferences) {
val shortcutsScope = rememberCoroutineScope()
val tabs by remember(uiPrefs.enableToolsExperiment) {
val tabs by remember(prefs.enableToolsExperiment) {
derivedStateOf {
if (uiPrefs.enableToolsExperiment) {
if (prefs.enableToolsExperiment) {
listOf(
DrawerItems.Calculator,
DrawerItems.Converter,
@ -106,7 +106,7 @@ internal fun UnittoApp(uiPrefs: UIPreferences) {
Themmo(
themmoController = themmoController,
typography = if (uiPrefs.systemFont) AppTypographySystem else AppTypographyUnitto,
typography = if (prefs.systemFont) AppTypographySystem else AppTypographyUnitto,
animationSpec = tween(250)
) {
val backgroundColor = MaterialTheme.colorScheme.background
@ -151,7 +151,7 @@ internal fun UnittoApp(uiPrefs: UIPreferences) {
UnittoNavigation(
navController = navController,
themmoController = it,
startDestination = uiPrefs.startingScreen,
startDestination = prefs.startingScreen,
openDrawer = { drawerScope.launch { drawerState.open() } }
)
}

View File

@ -25,6 +25,8 @@ android {
}
dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(project(mapOf("path" to ":core:base")))
testImplementation(libs.junit)
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.units
package com.sadellie.unitto.data.common
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow

View File

@ -39,255 +39,222 @@ import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import java.io.IOException
import javax.inject.Inject
/**
* Represents user preferences that are user across the app
*
* @property themingMode [ThemingMode] from Themmo.
* @property enableDynamicTheme Use dynamic color scheme
* @property enableAmoledTheme Use amoled color scheme
* @property customColor Generate custom color scheme from this color.
* @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)
* @property latestLeftSideUnit Latest [AbstractUnit] that was on the left side
* @property latestRightSideUnit Latest [AbstractUnit] that was on the right side
* @property shownUnitGroups [UnitGroup]s that user wants to see. Excludes other [UnitGroup]s,
* @property enableVibrations When true will use haptic feedback in app.
* @property enableToolsExperiment When true will enable experimental Tools screen.
* @property radianMode AngleMode in mxParser. When true - Radian, when False - Degree.
* @property unitConverterFavoritesOnly If true will show only units that are marked as favorite.
* @property unitConverterFormatTime If true will format time to be more human readable.
* @property unitConverterSorting Units list sorting mode.
*/
data class UserPreferences(
val themingMode: ThemingMode = ThemingMode.AUTO,
val enableDynamicTheme: Boolean = true,
val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified,
val monetMode: MonetMode = MonetMode.TONAL_SPOT,
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACE,
val outputFormat: Int = OutputFormat.PLAIN,
val latestLeftSideUnit: String = MyUnitIDS.kilometer,
val latestRightSideUnit: String = MyUnitIDS.mile,
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
val enableVibrations: Boolean = true,
val enableToolsExperiment: Boolean = false,
val startingScreen: String = TopLevelDestinations.Calculator.graph,
val radianMode: Boolean = true,
val unitConverterFavoritesOnly: Boolean = false,
val unitConverterFormatTime: Boolean = false,
val unitConverterSorting: UnitsListSorting = UnitsListSorting.USAGE,
val middleZero: Boolean = false,
val systemFont: Boolean = false,
val partialHistoryView: Boolean = true,
)
private object PrefsKeys {
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 CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
val MONET_MODE = stringPreferencesKey("MONET_MODE_PREF_KEY")
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
val LATEST_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY")
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
val ENABLE_TOOLS_EXPERIMENT = booleanPreferencesKey("ENABLE_TOOLS_EXPERIMENT_PREF_KEY")
val STARTING_SCREEN = stringPreferencesKey("STARTING_SCREEN_PREF_KEY")
val RADIAN_MODE = booleanPreferencesKey("RADIAN_MODE_PREF_KEY")
val UNIT_CONVERTER_FAVORITES_ONLY =
booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_PREF_KEY")
val UNIT_CONVERTER_FORMAT_TIME = booleanPreferencesKey("UNIT_CONVERTER_FORMAT_TIME_PREF_KEY")
val UNIT_CONVERTER_SORTING = stringPreferencesKey("UNIT_CONVERTER_SORTING_PREF_KEY")
val MIDDLE_ZERO = booleanPreferencesKey("MIDDLE_ZERO_PREF_KEY")
val SYSTEM_FONT = booleanPreferencesKey("SYSTEM_FONT_PREF_KEY")
val PARTIAL_HISTORY_VIEW = booleanPreferencesKey("PARTIAL_HISTORY_VIEW_PREF_KEY")
}
data class UIPreferences(
data class AppPreferences(
val themingMode: ThemingMode = ThemingMode.AUTO,
val enableDynamicTheme: Boolean = true,
val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified,
val monetMode: MonetMode = MonetMode.TONAL_SPOT,
val monetMode: MonetMode = MonetMode.values().first(),
val startingScreen: String = TopLevelDestinations.Calculator.graph,
val enableToolsExperiment: Boolean = false,
val systemFont: Boolean = false,
)
data class MainPreferences(
data class GeneralPreferences(
val startingScreen: String = TopLevelDestinations.Calculator.graph,
val enableVibrations: Boolean = true,
val middleZero: Boolean = false,
)
data class CalculatorPreferences(
val radianMode: Boolean = true,
val enableVibrations: Boolean = true,
val separator: Int = Separator.SPACE,
val middleZero: Boolean = false,
val partialHistoryView: Boolean = true,
val precision: Int = 3,
val outputFormat: Int = OutputFormat.PLAIN,
)
data class ConverterPreferences(
val enableVibrations: Boolean = true,
val separator: Int = Separator.SPACE,
val middleZero: Boolean = false,
val precision: Int = 3,
val outputFormat: Int = OutputFormat.PLAIN,
val unitConverterFormatTime: Boolean = false,
val unitConverterSorting: UnitsListSorting = UnitsListSorting.USAGE,
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
val unitConverterFavoritesOnly: Boolean = false,
val enableToolsExperiment: Boolean = false,
val latestLeftSideUnit: String = MyUnitIDS.kilometer,
val latestRightSideUnit: String = MyUnitIDS.mile,
)
data class ThemePreferences(
val systemFont: Boolean = false,
)
data class FormattingPreferences(
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACE,
val outputFormat: Int = OutputFormat.PLAIN,
val latestLeftSideUnit: String = MyUnitIDS.kilometer,
val latestRightSideUnit: String = MyUnitIDS.mile,
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
val enableVibrations: Boolean = true,
val radianMode: Boolean = true,
val unitConverterFavoritesOnly: Boolean = false,
val unitConverterFormatTime: Boolean = false,
val unitConverterSorting: UnitsListSorting = UnitsListSorting.USAGE,
val middleZero: Boolean = false,
val enableToolsExperiment: Boolean = false,
val partialHistoryView: Boolean = true,
)
/**
* Repository that works with DataStore
*/
data class UnitGroupsPreferences(
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
)
data class AddSubtractPreferences(
val separator: Int = Separator.SPACE,
)
data class AboutPreferences(
val enableToolsExperiment: Boolean = false,
)
class UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<Preferences>) {
/**
* Keys for DataStore
*/
private object PrefsKeys {
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 CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
val MONET_MODE = stringPreferencesKey("MONET_MODE_PREF_KEY")
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
val LATEST_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY")
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
val ENABLE_TOOLS_EXPERIMENT = booleanPreferencesKey("ENABLE_TOOLS_EXPERIMENT_PREF_KEY")
val STARTING_SCREEN = stringPreferencesKey("STARTING_SCREEN_PREF_KEY")
val RADIAN_MODE = booleanPreferencesKey("RADIAN_MODE_PREF_KEY")
val UNIT_CONVERTER_FAVORITES_ONLY = booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_PREF_KEY")
val UNIT_CONVERTER_FORMAT_TIME = booleanPreferencesKey("UNIT_CONVERTER_FORMAT_TIME_PREF_KEY")
val UNIT_CONVERTER_SORTING = stringPreferencesKey("UNIT_CONVERTER_SORTING_PREF_KEY")
val MIDDLE_ZERO = booleanPreferencesKey("MIDDLE_ZERO_PREF_KEY")
val SYSTEM_FONT = booleanPreferencesKey("SYSTEM_FONT_PREF_KEY")
val PARTIAL_HISTORY_VIEW = booleanPreferencesKey("PARTIAL_HISTORY_VIEW_PREF_KEY")
}
private val data = dataStore.data
.catch { if (it is IOException) emit(emptyPreferences()) else throw it }
val uiPreferencesFlow: Flow<UIPreferences> = dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
val appPrefs: Flow<AppPreferences> = data
.map { preferences ->
val themingMode: ThemingMode = preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
?: ThemingMode.AUTO
val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true
val enableAmoledTheme: Boolean = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
val customColor: Color = preferences[PrefsKeys.CUSTOM_COLOR]?.let { Color(it.toULong()) } ?: Color.Unspecified
val monetMode: MonetMode = preferences[PrefsKeys.MONET_MODE]?.let { MonetMode.valueOf(it) }
?: MonetMode.TONAL_SPOT
val startingScreen: String = preferences[PrefsKeys.STARTING_SCREEN] ?: TopLevelDestinations.Calculator.graph
val enableToolsExperiment: Boolean = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
val systemFont: Boolean = preferences[PrefsKeys.SYSTEM_FONT] ?: false
UIPreferences(
themingMode = themingMode,
enableDynamicTheme = enableDynamicTheme,
enableAmoledTheme = enableAmoledTheme,
customColor = customColor,
monetMode = monetMode,
startingScreen = startingScreen,
enableToolsExperiment = enableToolsExperiment,
systemFont = systemFont
AppPreferences(
themingMode = preferences[PrefsKeys.THEMING_MODE]?.letTryOrNull {
ThemingMode.valueOf(it)
}
?: ThemingMode.AUTO,
enableDynamicTheme = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true,
enableAmoledTheme = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false,
customColor = preferences[PrefsKeys.CUSTOM_COLOR]?.letTryOrNull { Color(it.toULong()) }
?: Color.Unspecified,
monetMode = preferences[PrefsKeys.MONET_MODE]?.letTryOrNull { MonetMode.valueOf(it) }
?: MonetMode.values().first(),
startingScreen = preferences[PrefsKeys.STARTING_SCREEN]
?: TopLevelDestinations.Calculator.graph,
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false,
systemFont = preferences[PrefsKeys.SYSTEM_FONT] ?: false
)
}
val mainPrefsFlow: Flow<MainPreferences> = dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
val generalPrefs: Flow<GeneralPreferences> = data
.map { preferences ->
val digitsPrecision: Int = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE
val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
val latestLeftSideUnit: String = preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
val latestRightSideUnit: String = preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
val shownUnitGroups: List<UnitGroup> =
preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.let { list ->
// Everything is in hidden (nothing in shown)
list.ifEmpty { return@let listOf() }
try {
list.split(",").map { UnitGroup.valueOf(it) }
} catch (e: Exception) {
// Bad thing happened, return null so all units will be shown
null
}
} ?: ALL_UNIT_GROUPS
val enableVibrations: Boolean = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true
val radianMode: Boolean = preferences[PrefsKeys.RADIAN_MODE] ?: true
val unitConverterFavoritesOnly: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] ?: false
val unitConverterFormatTime: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
val unitConverterSorting: UnitsListSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING]?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE
val middleZero: Boolean = preferences[PrefsKeys.MIDDLE_ZERO] ?: false
val enableToolsExperiment: Boolean = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
val partialHistoryView: Boolean = preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true
MainPreferences(
digitsPrecision = digitsPrecision,
separator = separator,
outputFormat = outputFormat,
latestLeftSideUnit = latestLeftSideUnit,
latestRightSideUnit = latestRightSideUnit,
shownUnitGroups = shownUnitGroups,
enableVibrations = enableVibrations,
radianMode = radianMode,
unitConverterFavoritesOnly = unitConverterFavoritesOnly,
unitConverterFormatTime = unitConverterFormatTime,
unitConverterSorting = unitConverterSorting,
middleZero = middleZero,
enableToolsExperiment = enableToolsExperiment,
partialHistoryView = partialHistoryView
GeneralPreferences(
startingScreen = preferences[PrefsKeys.STARTING_SCREEN]
?: TopLevelDestinations.Calculator.graph,
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false,
)
}
val allPreferencesFlow = combine(
mainPrefsFlow, uiPreferencesFlow
) { main, ui ->
return@combine UserPreferences(
themingMode = ui.themingMode,
enableDynamicTheme = ui.enableDynamicTheme,
enableAmoledTheme = ui.enableAmoledTheme,
customColor = ui.customColor,
monetMode = ui.monetMode,
digitsPrecision = main.digitsPrecision,
separator = main.separator,
outputFormat = main.outputFormat,
latestLeftSideUnit = main.latestLeftSideUnit,
latestRightSideUnit = main.latestRightSideUnit,
shownUnitGroups = main.shownUnitGroups,
enableVibrations = main.enableVibrations,
enableToolsExperiment = ui.enableToolsExperiment,
startingScreen = ui.startingScreen,
radianMode = main.radianMode,
unitConverterFavoritesOnly = main.unitConverterFavoritesOnly,
unitConverterFormatTime = main.unitConverterFormatTime,
unitConverterSorting = main.unitConverterSorting,
middleZero = main.middleZero,
systemFont = ui.systemFont,
partialHistoryView = main.partialHistoryView
)
}
val calculatorPrefs: Flow<CalculatorPreferences> = data
.map { preferences ->
CalculatorPreferences(
radianMode = preferences[PrefsKeys.RADIAN_MODE] ?: true,
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE,
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false,
partialHistoryView = preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true,
precision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3,
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
)
}
val converterPrefs: Flow<ConverterPreferences> = data
.map { preferences ->
ConverterPreferences(
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE,
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false,
precision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3,
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN,
unitConverterFormatTime = preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME]
?: false,
unitConverterSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING]
?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE,
shownUnitGroups = preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list ->
list.ifEmpty { return@letTryOrNull listOf() }.split(",")
.map { UnitGroup.valueOf(it) }
} ?: ALL_UNIT_GROUPS,
unitConverterFavoritesOnly = preferences[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY]
?: false,
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false,
latestLeftSideUnit = preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer,
latestRightSideUnit = preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile,
)
}
val themePrefs: Flow<ThemePreferences> = data
.map { preferences ->
ThemePreferences(
systemFont = preferences[PrefsKeys.SYSTEM_FONT] ?: false
)
}
val formattingPrefs: Flow<FormattingPreferences> = data
.map { preferences ->
FormattingPreferences(
digitsPrecision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3,
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE,
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN,
)
}
val unitGroupsPrefs: Flow<UnitGroupsPreferences> = data
.map { preferences ->
UnitGroupsPreferences(
shownUnitGroups = preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list ->
list.ifEmpty { return@letTryOrNull listOf() }.split(",")
.map { UnitGroup.valueOf(it) }
} ?: ALL_UNIT_GROUPS,
)
}
val addSubtractPrefs: Flow<AddSubtractPreferences> = data
.map { preferences ->
AddSubtractPreferences(
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE
)
}
val aboutPrefs: Flow<AboutPreferences> = data
.map { preferences ->
AboutPreferences(
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
)
}
/**
* Update precision preference in DataStore
*
* @param precision One of the [PRECISIONS] to change to
*/
suspend fun updateDigitsPrecision(precision: Int) {
dataStore.edit { preferences ->
preferences[PrefsKeys.DIGITS_PRECISION] = precision
}
}
/**
* Update separator preference in DataStore
*
* @param separator One of the [Separator] to change to
*/
suspend fun updateSeparator(separator: Int) {
dataStore.edit { preferences ->
preferences[PrefsKeys.SEPARATOR] = separator
}
}
/**
* Update outputFormat preference in DataStore
*
* @param outputFormat One of the [OutputFormat] to change to
*/
suspend fun updateOutputFormat(outputFormat: Int) {
dataStore.edit { preferences ->
preferences[PrefsKeys.OUTPUT_FORMAT] = outputFormat
@ -301,66 +268,36 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
}
/**
* 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
}
}
/**
* Update preference on custom color scheme.
*
* @param color New custom color value.
*/
suspend fun updateCustomColor(color: Color) {
dataStore.edit { preferences ->
preferences[PrefsKeys.CUSTOM_COLOR] = color.value.toLong()
}
}
/**
* Update [MonetMode]. Saves value as a string.
*
* @param monetMode [MonetMode] to save.
*/
suspend fun updateMonetMode(monetMode: MonetMode) {
dataStore.edit { preferences ->
preferences[PrefsKeys.MONET_MODE] = monetMode.name
}
}
/**
* Update preference on starting screen route.
*
* @param startingScreen Route from [TopLevelDestinations].
*/
suspend fun updateStartingScreen(startingScreen: String) {
dataStore.edit { preferences ->
preferences[PrefsKeys.STARTING_SCREEN] = startingScreen
@ -373,102 +310,63 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
}
/**
* Update preference on whether or not use haptic feedback.
*
* @param enabled True if user wants to enable this feature.
*/
suspend fun updateVibrations(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.ENABLE_VIBRATIONS] = enabled
}
}
/**
* Update preference on where zero should be.
*
* @param enabled True if user wants zero button to be in the middle.
*/
suspend fun updateMiddleZero(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.MIDDLE_ZERO] = enabled
}
}
/**
* Update preference on whether or not show tools screen.
*
* @param enabled True if user wants to enable this feature.
*/
suspend fun updateToolsExperiment(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] = enabled
}
}
/**
* Update angle mode for calculator.
*
* @param radianMode When true - Radian, when False - Degree.
*/
suspend fun updateRadianMode(radianMode: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.RADIAN_MODE] = radianMode
}
}
/**
* Update units list favorite filter state.
*
* @param enabled When true will show only favorite units.
*/
suspend fun updateUnitConverterFavoritesOnly(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] = enabled
}
}
/**
* Update [UserPreferences.unitConverterFormatTime].
*
* @see UserPreferences.unitConverterFormatTime
*/
suspend fun updateUnitConverterFormatTime(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = enabled
}
}
/**
* Update [UserPreferences.unitConverterSorting].
*
* @see UserPreferences.unitConverterSorting
*/
suspend fun updateUnitConverterSorting(sorting: UnitsListSorting) {
dataStore.edit { preferences ->
preferences[PrefsKeys.UNIT_CONVERTER_SORTING] = sorting.name
}
}
/**
* Update system font preference.
*
* @param enabled When true will use system font.
*/
suspend fun updateSystemFont(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.SYSTEM_FONT] = enabled
}
}
/**
* Update partial history view preference.
*
* @param enabled When true will enable partial history view.
*/
suspend fun updatePartialHistoryView(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] = enabled
}
}
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
this?.let(block)
} catch (e: Exception) {
null
}
}

View File

@ -31,7 +31,7 @@ import com.sadellie.unitto.data.common.isExpression
import com.sadellie.unitto.data.common.setMinimumRequiredScale
import com.sadellie.unitto.data.common.toStringWith
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.userprefs.MainPreferences
import com.sadellie.unitto.data.userprefs.CalculatorPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.evaluatto.Expression
@ -54,11 +54,11 @@ internal class CalculatorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val calculatorHistoryRepository: CalculatorHistoryRepository,
) : ViewModel() {
private val _userPrefs: StateFlow<MainPreferences> =
userPrefsRepository.mainPrefsFlow.stateIn(
private val _prefs: StateFlow<CalculatorPreferences> =
userPrefsRepository.calculatorPrefs.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000L),
MainPreferences()
CalculatorPreferences()
)
private val _input: MutableStateFlow<TextFieldValue> = MutableStateFlow(TextFieldValue())
@ -67,7 +67,7 @@ internal class CalculatorViewModel @Inject constructor(
private val _history = calculatorHistoryRepository.historyFlow
val uiState = combine(
_input, _output, _history, _userPrefs
_input, _output, _history, _prefs
) { input, output, history, userPrefs ->
return@combine CalculatorUIState.Ready(
input = input,
@ -112,7 +112,7 @@ internal class CalculatorViewModel @Inject constructor(
}
fun toggleCalculatorMode() = viewModelScope.launch {
userPrefsRepository.updateRadianMode(!_userPrefs.value.radianMode)
userPrefsRepository.updateRadianMode(!_prefs.value.radianMode)
}
fun clearHistory() = viewModelScope.launch(Dispatchers.IO) {
@ -126,14 +126,14 @@ internal class CalculatorViewModel @Inject constructor(
return try {
CalculationResult.Default(
Expression(currentInput, radianMode = _userPrefs.value.radianMode)
Expression(currentInput, radianMode = _prefs.value.radianMode)
.calculate()
.also {
if (it > BigDecimal.valueOf(Double.MAX_VALUE)) throw ExpressionException.TooBig()
}
.setMinimumRequiredScale(_userPrefs.value.digitsPrecision)
.setMinimumRequiredScale(_prefs.value.precision)
.trimZeros()
.toStringWith(_userPrefs.value.outputFormat)
.toStringWith(_prefs.value.outputFormat)
)
} catch (e: ExpressionException.DivideByZero) {
CalculationResult.DivideByZeroError
@ -145,7 +145,7 @@ internal class CalculatorViewModel @Inject constructor(
init {
// Observe and invoke calculation without UI lag.
viewModelScope.launch(Dispatchers.Default) {
merge(_userPrefs, _input).collectLatest {
merge(_prefs, _input).collectLatest {
val calculated = calculateInput()
_output.update {
// Don't show error when simply entering stuff

View File

@ -33,8 +33,8 @@ import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import com.sadellie.unitto.data.units.UnitsRepository
import com.sadellie.unitto.data.units.combine
import com.sadellie.unitto.data.units.stateIn
import com.sadellie.unitto.data.common.combine
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.evaluatto.Expression
@ -79,7 +79,7 @@ internal class ConverterViewModel @Inject constructor(
_result,
_unitFrom,
_unitTo,
userPrefsRepository.mainPrefsFlow,
userPrefsRepository.converterPrefs,
_loadingCurrencies
) { input, calculation, result, unitFrom, unitTo, prefs, _ ->
return@combine when {
@ -93,7 +93,7 @@ internal class ConverterViewModel @Inject constructor(
enableHaptic = prefs.enableVibrations,
middleZero = prefs.middleZero,
formatterSymbols = AllFormatterSymbols.getById(prefs.separator),
scale = prefs.digitsPrecision,
scale = prefs.precision,
outputFormat = prefs.outputFormat,
formatTime = prefs.unitConverterFormatTime,
)
@ -147,7 +147,7 @@ internal class ConverterViewModel @Inject constructor(
val leftSideUIState = combine(
_unitFrom,
_leftSideUIState,
userPrefsRepository.mainPrefsFlow,
userPrefsRepository.converterPrefs,
unitsRepo.allUnits
) { unitFrom, ui, prefs, _ ->
return@combine ui.copy(
@ -175,7 +175,7 @@ internal class ConverterViewModel @Inject constructor(
_input,
_calculation,
_rightSideUIState,
userPrefsRepository.mainPrefsFlow,
userPrefsRepository.converterPrefs,
unitsRepo.allUnits
) { unitFrom, unitTo, input, calculation, ui, prefs, _ ->
return@combine ui.copy(
@ -184,7 +184,7 @@ internal class ConverterViewModel @Inject constructor(
sorting = prefs.unitConverterSorting,
favorites = prefs.unitConverterFavoritesOnly,
input = calculation?.toPlainString() ?: input.text,
scale = prefs.digitsPrecision,
scale = prefs.precision,
outputFormat = prefs.outputFormat,
formatterSymbols = AllFormatterSymbols.getById(prefs.separator)
)
@ -387,10 +387,9 @@ internal class ConverterViewModel @Inject constructor(
_result.update { ConverterResult.NumberBase(conversion) }
}
init {
viewModelScope.launch(Dispatchers.Default) {
val userPrefs = userPrefsRepository.mainPrefsFlow.first()
val userPrefs = userPrefsRepository.converterPrefs.first()
val unitFrom = unitsRepo.getById(userPrefs.latestLeftSideUnit)
val unitTo = unitsRepo.getById(userPrefs.latestRightSideUnit)

View File

@ -42,7 +42,7 @@ internal class AddSubtractViewModel @Inject constructor(
private val _uiState = MutableStateFlow(AddSubtractState())
val uiState: StateFlow<AddSubtractState> = _uiState
.combine(userPreferencesRepository.allPreferencesFlow) { uiState, userPrefs ->
.combine(userPreferencesRepository.addSubtractPrefs) { uiState, userPrefs ->
return@combine uiState.copy(
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator)
)

View File

@ -20,86 +20,38 @@ package com.sadellie.unitto.feature.settings
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.userprefs.UserPreferences
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.GeneralPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
internal class SettingsViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val userPrefs = userPrefsRepository.allPreferencesFlow
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
UserPreferences()
)
val userPrefs = userPrefsRepository.generalPrefs
.stateIn(viewModelScope, GeneralPreferences())
/**
* @see UserPreferencesRepository.updateVibrations
*/
fun updateVibrations(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateVibrations(enabled)
}
fun updateVibrations(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateVibrations(enabled)
}
/**
* @see UserPreferencesRepository.updateMiddleZero
*/
fun updateMiddleZero(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateMiddleZero(enabled)
}
fun updateMiddleZero(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateMiddleZero(enabled)
}
/**
* @see UserPreferencesRepository.updateStartingScreen
*/
fun updateStartingScreen(startingScreen: String) {
viewModelScope.launch {
userPrefsRepository.updateStartingScreen(startingScreen)
}
}
/**
* @see UserPreferencesRepository.updateToolsExperiment
*/
fun enableToolsExperiment() {
viewModelScope.launch {
userPrefsRepository.updateToolsExperiment(true)
}
}
/**
* @see UserPreferencesRepository.updateUnitConverterFormatTime
*/
fun updateUnitConverterFormatTime(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateUnitConverterFormatTime(enabled)
}
}
/**
* @see UserPreferencesRepository.updateUnitConverterSorting
*/
fun updateUnitConverterSorting(sorting: UnitsListSorting) {
viewModelScope.launch {
userPrefsRepository.updateUnitConverterSorting(sorting)
}
}
/**
* @see UserPreferencesRepository.updatePartialHistoryView
*/
fun updatePartialHistoryView(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updatePartialHistoryView(enabled)
}
fun updateStartingScreen(startingScreen: String) = viewModelScope.launch {
userPrefsRepository.updateStartingScreen(startingScreen)
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings
package com.sadellie.unitto.feature.settings.about
import android.widget.Toast
import androidx.compose.foundation.clickable
@ -54,12 +54,12 @@ import com.sadellie.unitto.core.ui.openLink
@Composable
internal fun AboutScreen(
viewModel: SettingsViewModel = hiltViewModel(),
viewModel: AboutViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
navigateToThirdParty: () -> Unit,
) {
val mContext = LocalContext.current
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
var aboutItemClick: Int by rememberSaveable { mutableIntStateOf(0) }
var showDialog: Boolean by rememberSaveable { mutableStateOf(false) }
@ -185,7 +185,7 @@ internal fun AboutScreen(
headlineContent = { Text(stringResource(R.string.app_version_name_setting)) },
supportingContent = { Text("${BuildConfig.APP_NAME} (${BuildConfig.APP_CODE})") },
modifier = Modifier.combinedClickable {
if (userPrefs.value.enableToolsExperiment) {
if (prefs.value.enableToolsExperiment) {
Toast.makeText(mContext, "Experiments features are already enabled!", Toast.LENGTH_LONG).show()
return@combinedClickable
}

View File

@ -0,0 +1,40 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings.about
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.AboutPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class AboutViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.aboutPrefs
.stateIn(viewModelScope, AboutPreferences())
fun enableToolsExperiment() = viewModelScope.launch {
userPrefsRepository.updateToolsExperiment(true)
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings
package com.sadellie.unitto.feature.settings.calculator
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
@ -24,6 +24,7 @@ import androidx.compose.material.icons.filled.Timer
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.NavigateUpButton
@ -32,10 +33,10 @@ import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
@Composable
internal fun CalculatorSettingsScreen(
viewModel: SettingsViewModel,
viewModel: CalculatorViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
) {
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
UnittoScreenWithLargeTopBar(
title = stringResource(R.string.calculator),
@ -52,7 +53,7 @@ internal fun CalculatorSettingsScreen(
)
},
supportContent = stringResource(R.string.partial_history_view_setting_support),
switchState = userPrefs.value.partialHistoryView,
switchState = prefs.value.partialHistoryView,
onSwitchChange = viewModel::updatePartialHistoryView
)
}

View File

@ -0,0 +1,40 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings.calculator
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.CalculatorPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class CalculatorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.calculatorPrefs
.stateIn(viewModelScope, CalculatorPreferences())
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updatePartialHistoryView(enabled)
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings
package com.sadellie.unitto.feature.settings.converter
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
@ -34,6 +34,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.NavigateUpButton
@ -44,11 +45,11 @@ import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
@Composable
internal fun ConverterSettingsScreen(
viewModel: SettingsViewModel,
viewModel: ConverterViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
navigateToUnitsGroup: () -> Unit
navigateToUnitsGroup: () -> Unit,
) {
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
var showDialog: Boolean by rememberSaveable { mutableStateOf(false) }
UnittoScreenWithLargeTopBar(
@ -97,7 +98,7 @@ internal fun ConverterSettingsScreen(
)
},
supportContent = stringResource(R.string.format_time_support),
switchState = userPrefs.value.unitConverterFormatTime,
switchState = prefs.value.unitConverterFormatTime,
onSwitchChange = viewModel::updateUnitConverterFormatTime
)
}
@ -113,7 +114,7 @@ internal fun ConverterSettingsScreen(
UnitsListSorting.SCALE_DESC to R.string.sort_by_scale_desc,
UnitsListSorting.SCALE_ASC to R.string.sort_by_scale_asc,
),
selectedItemIndex = userPrefs.value.unitConverterSorting,
selectedItemIndex = prefs.value.unitConverterSorting,
selectAction = viewModel::updateUnitConverterSorting,
dismissAction = { showDialog = false }
)

View File

@ -0,0 +1,45 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings.converter
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.userprefs.ConverterPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class ConverterViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.converterPrefs
.stateIn(viewModelScope, ConverterPreferences())
fun updateUnitConverterFormatTime(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterFormatTime(enabled)
}
fun updateUnitConverterSorting(sorting: UnitsListSorting) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterSorting(sorting)
}
}

View File

@ -49,6 +49,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.core.base.OutputFormat
@ -66,7 +67,7 @@ import kotlin.math.roundToInt
@Composable
fun FormattingRoute(
viewModel: FormattingViewModel,
viewModel: FormattingViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()

View File

@ -25,14 +25,13 @@ import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.setMinimumRequiredScale
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.common.toStringWith
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.math.BigDecimal
@ -43,10 +42,10 @@ import kotlin.math.ceil
class FormattingViewModel @Inject constructor(
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
private val _mainPreferences = userPreferencesRepository.mainPrefsFlow
private val _prefs = userPreferencesRepository.formattingPrefs
private val _fractional = MutableStateFlow(false)
val uiState = combine(_mainPreferences, _fractional) { mainPrefs, fractional ->
val uiState = combine(_prefs, _fractional) { mainPrefs, fractional ->
val formatterSymbols = AllFormatterSymbols.getById(mainPrefs.separator)
return@combine FormattingUIState(
@ -62,7 +61,7 @@ class FormattingViewModel @Inject constructor(
formatterSymbols = formatterSymbols
)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), FormattingUIState())
.stateIn(viewModelScope, FormattingUIState())
fun togglePreview() = _fractional.update { !it }
@ -88,29 +87,23 @@ class FormattingViewModel @Inject constructor(
/**
* @see UserPreferencesRepository.updateDigitsPrecision
*/
fun updatePrecision(precision: Int) {
viewModelScope.launch {
// In UI the slider for precision goes from 0 to 16, where 16 is treated as 1000 (MAX)
val newPrecision = if (precision > 15) MAX_PRECISION else precision
userPreferencesRepository.updateDigitsPrecision(newPrecision)
}
fun updatePrecision(precision: Int) = viewModelScope.launch {
// In UI the slider for precision goes from 0 to 16, where 16 is treated as 1000 (MAX)
val newPrecision = if (precision > 15) MAX_PRECISION else precision
userPreferencesRepository.updateDigitsPrecision(newPrecision)
}
/**
* @see UserPreferencesRepository.updateSeparator
*/
fun updateSeparator(separator: Int) {
viewModelScope.launch {
userPreferencesRepository.updateSeparator(separator)
}
fun updateSeparator(separator: Int) = viewModelScope.launch {
userPreferencesRepository.updateSeparator(separator)
}
/**
* @see UserPreferencesRepository.updateOutputFormat
*/
fun updateOutputFormat(outputFormat: Int) {
viewModelScope.launch {
userPreferencesRepository.updateOutputFormat(outputFormat)
}
fun updateOutputFormat(outputFormat: Int) = viewModelScope.launch {
userPreferencesRepository.updateOutputFormat(outputFormat)
}
}

View File

@ -18,8 +18,6 @@
package com.sadellie.unitto.feature.settings.navigation
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
@ -27,13 +25,13 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.navDeepLink
import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.feature.settings.AboutScreen
import com.sadellie.unitto.feature.settings.CalculatorSettingsScreen
import com.sadellie.unitto.feature.settings.ConverterSettingsScreen
import com.sadellie.unitto.feature.settings.SettingsScreen
import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen
import com.sadellie.unitto.feature.settings.about.AboutScreen
import com.sadellie.unitto.feature.settings.calculator.CalculatorSettingsScreen
import com.sadellie.unitto.feature.settings.converter.ConverterSettingsScreen
import com.sadellie.unitto.feature.settings.formatting.FormattingRoute
import com.sadellie.unitto.feature.settings.themes.ThemesRoute
import com.sadellie.unitto.feature.settings.thirdparty.ThirdPartyLicensesScreen
import com.sadellie.unitto.feature.settings.unitgroups.UnitGroupsScreen
import io.github.sadellie.themmo.ThemmoController
@ -57,7 +55,7 @@ fun NavController.navigateToUnitGroups() {
fun NavGraphBuilder.settingGraph(
themmoController: ThemmoController,
navController: NavHostController
navController: NavHostController,
) {
navigation(
startDestination = start,
@ -67,10 +65,7 @@ fun NavGraphBuilder.settingGraph(
)
) {
composable(start) {
val parent = remember(it) { navController.getBackStackEntry(graph) }
SettingsScreen(
viewModel = hiltViewModel(parent),
menuButtonClick = navController::navigateUp,
navControllerAction = navController::navigate
)
@ -78,59 +73,47 @@ fun NavGraphBuilder.settingGraph(
composable(themesRoute) {
ThemesRoute(
viewModel = hiltViewModel(),
navigateUpAction = navController::navigateUp,
themmoController = themmoController,
)
}
composable(formattingRoute) {
FormattingRoute(
navigateUpAction = navController::navigateUp
)
}
composable(calculatorSettingsRoute) {
CalculatorSettingsScreen(
navigateUpAction = navController::navigateUp,
)
}
composable(converterSettingsRoute) {
ConverterSettingsScreen(
navigateUpAction = navController::navigateUp,
navigateToUnitsGroup = { navController.navigate(unitsGroupRoute) }
)
}
composable(unitsGroupRoute) {
UnitGroupsScreen(
navigateUpAction = navController::navigateUp,
)
}
composable(aboutRoute) {
AboutScreen(
navigateUpAction = navController::navigateUp,
navigateToThirdParty = { navController.navigate(thirdPartyRoute) }
)
}
composable(thirdPartyRoute) {
ThirdPartyLicensesScreen(
navigateUpAction = navController::navigateUp,
)
}
composable(aboutRoute) {
val parent = remember(it) { navController.getBackStackEntry(graph) }
AboutScreen(
viewModel = hiltViewModel(parent),
navigateUpAction = navController::navigateUp,
navigateToThirdParty = { navController.navigate(thirdPartyRoute) }
)
}
composable(unitsGroupRoute) {
UnitGroupsScreen(
viewModel = hiltViewModel(),
navigateUpAction = navController::navigateUp,
)
}
composable(formattingRoute) {
FormattingRoute(
viewModel = hiltViewModel(),
navigateUpAction = navController::navigateUp
)
}
composable(calculatorSettingsRoute) {
val parent = remember(it) { navController.getBackStackEntry(graph) }
CalculatorSettingsScreen(
viewModel = hiltViewModel(parent),
navigateUpAction = navController::navigateUp,
)
}
composable(converterSettingsRoute) {
val parent = remember(it) { navController.getBackStackEntry(graph) }
ConverterSettingsScreen(
viewModel = hiltViewModel(parent),
navigateUpAction = navController::navigateUp,
navigateToUnitsGroup = { navController.navigate(unitsGroupRoute) }
)
}
}
}

View File

@ -48,6 +48,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.Header
@ -80,7 +81,7 @@ private val colorSchemes: List<Color> by lazy {
@Composable
internal fun ThemesRoute(
viewModel: ThemesViewModel,
viewModel: ThemesViewModel = hiltViewModel(),
navigateUpAction: () -> Unit = {},
themmoController: ThemmoController,
) {

View File

@ -21,13 +21,12 @@ package com.sadellie.unitto.feature.settings.themes
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -36,13 +35,9 @@ class ThemesViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository
) : ViewModel() {
val systemFont = userPrefsRepository.uiPreferencesFlow
val systemFont = userPrefsRepository.themePrefs
.map { it.systemFont }
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000L),
false
)
.stateIn(viewModelScope, false)
/**
* @see UserPreferencesRepository.updateThemingMode

View File

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

View File

@ -47,6 +47,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton
@ -59,8 +60,8 @@ import org.burnoutcrew.reorderable.reorderable
@Composable
internal fun UnitGroupsScreen(
viewModel: UnitGroupsViewModel,
navigateUpAction: () -> Unit
viewModel: UnitGroupsViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
) {
UnittoScreenWithLargeTopBar(
title = stringResource(R.string.unit_groups_setting),

View File

@ -87,7 +87,7 @@ class UnitGroupsViewModel @Inject constructor(
init {
viewModelScope.launch {
unitGroupsRepository.updateShownGroups(
userPrefsRepository.mainPrefsFlow.first().shownUnitGroups
userPrefsRepository.unitGroupsPrefs.first().shownUnitGroups
)
}
}