Fix initial states, navigation, loading logic and transitions

closes #92
This commit is contained in:
Sad Ellie 2023-10-04 13:02:11 +03:00
parent 1279a8acad
commit f3ba0bd06a
28 changed files with 681 additions and 640 deletions

View File

@ -44,7 +44,7 @@ internal class MainActivity : AppCompatActivity() {
val prefs = userPrefsRepository.appPrefs
.collectAsStateWithLifecycle(null).value
if (prefs != null) UnittoApp(prefs)
UnittoApp(prefs)
}
}

View File

@ -53,23 +53,14 @@ import com.sadellie.unitto.core.ui.theme.TypographySystem
import com.sadellie.unitto.core.ui.theme.TypographyUnitto
import com.sadellie.unitto.data.userprefs.AppPreferences
import io.github.sadellie.themmo.Themmo
import io.github.sadellie.themmo.rememberThemmoController
import io.github.sadellie.themmo.ThemmoController
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun UnittoApp(prefs: AppPreferences) {
internal fun UnittoApp(prefs: AppPreferences?) {
val mContext = LocalContext.current
val themmoController = rememberThemmoController(
lightColorScheme = LightThemeColors,
darkColorScheme = DarkThemeColors,
themingMode = prefs.themingMode,
dynamicThemeEnabled = prefs.enableDynamicTheme,
amoledThemeEnabled = prefs.enableAmoledTheme,
customColor = prefs.customColor,
monetMode = prefs.monetMode
)
val navController = rememberNavController()
val sysUiController = rememberSystemUiController()
@ -97,6 +88,19 @@ internal fun UnittoApp(prefs: AppPreferences) {
}
}
if (prefs != null) {
val themmoController = remember(prefs) {
ThemmoController(
lightColorScheme = LightThemeColors,
darkColorScheme = DarkThemeColors,
themingMode = prefs.themingMode,
dynamicThemeEnabled = prefs.enableDynamicTheme,
amoledThemeEnabled = prefs.enableAmoledTheme,
customColor = prefs.customColor,
monetMode = prefs.monetMode
)
}
Themmo(
themmoController = themmoController,
typography = if (prefs.systemFont) TypographySystem else TypographyUnitto,
@ -150,13 +154,14 @@ internal fun UnittoApp(prefs: AppPreferences) {
}
)
BackHandler(drawerState.isOpen) {
drawerScope.launch { drawerState.close() }
}
LaunchedEffect(useDarkIcons) {
sysUiController.setNavigationBarColor(Color.Transparent, useDarkIcons)
sysUiController.setStatusBarColor(Color.Transparent, useDarkIcons)
}
}
}
BackHandler(drawerState.isOpen) {
drawerScope.launch { drawerState.close() }
}
}

View File

@ -22,9 +22,14 @@ import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.IntOffset
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
@ -40,16 +45,16 @@ fun NavGraphBuilder.unittoComposable(
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
enterTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = { fadeIn() },
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = { unittoFadeIn() },
exitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = { fadeOut() },
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = { unittoFadeOut() },
popEnterTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
enterTransition,
popExitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
exitTransition,
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
): Unit = composable(
route = route,
arguments = arguments,
@ -61,6 +66,40 @@ fun NavGraphBuilder.unittoComposable(
content = content,
)
fun NavGraphBuilder.unittoStackedComposable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) {
composable(
route = route,
arguments = arguments,
deepLinks = deepLinks,
enterTransition = {
slideInHorizontally(
animationSpec = unittoEnterTween(),
initialOffsetX = { (it * 0.2f).toInt() }) + unittoFadeIn()
},
exitTransition = {
slideOutHorizontally(
animationSpec = unittoExitTween(),
targetOffsetX = { -(it * 0.2f).toInt() }) + unittoFadeOut()
},
popEnterTransition = {
slideInHorizontally(
animationSpec = unittoEnterTween(),
initialOffsetX = { -(it * 0.2f).toInt() }) + unittoFadeIn()
},
popExitTransition = {
slideOutHorizontally(
animationSpec = unittoExitTween(),
targetOffsetX = { (it * 0.2f).toInt() }) + unittoFadeOut()
},
content = content,
)
}
/**
* @see NavGraphBuilder.navigation
*/
@ -79,7 +118,7 @@ fun NavGraphBuilder.unittoNavigation(
popExitTransition: (
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = exitTransition,
builder: NavGraphBuilder.() -> Unit
builder: NavGraphBuilder.() -> Unit,
): Unit = navigation(
startDestination = startDestination,
route = route,
@ -91,3 +130,11 @@ fun NavGraphBuilder.unittoNavigation(
popExitTransition = popExitTransition,
builder = builder
)
private const val ENTER_DURATION = 350
private const val EXIT_DURATION = 200
private fun unittoFadeIn(): EnterTransition = fadeIn(tween(ENTER_DURATION))
private fun unittoFadeOut(): ExitTransition = fadeOut(tween(EXIT_DURATION))
private fun unittoEnterTween(): FiniteAnimationSpec<IntOffset> = tween(ENTER_DURATION)
private fun unittoExitTween(): FiniteAnimationSpec<IntOffset> = tween(EXIT_DURATION)

View File

@ -0,0 +1,29 @@
/*
* 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.core.ui.common
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun UnittoEmptyScreen() {
Spacer(modifier = Modifier.fillMaxSize())
}

View File

@ -0,0 +1,94 @@
/*
* 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.data.userprefs
import androidx.compose.ui.graphics.Color
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
data class AppPreferences(
val themingMode: ThemingMode,
val enableDynamicTheme: Boolean,
val enableAmoledTheme: Boolean,
val customColor: Color,
val monetMode: MonetMode,
val startingScreen: String,
val enableToolsExperiment: Boolean,
val systemFont: Boolean,
)
data class GeneralPreferences(
val enableVibrations: Boolean,
)
data class CalculatorPreferences(
val radianMode: Boolean,
val enableVibrations: Boolean,
val separator: Int,
val middleZero: Boolean,
val partialHistoryView: Boolean,
val precision: Int,
val outputFormat: Int,
)
data class ConverterPreferences(
val enableVibrations: Boolean,
val separator: Int,
val middleZero: Boolean,
val precision: Int,
val outputFormat: Int,
val unitConverterFormatTime: Boolean,
val unitConverterSorting: UnitsListSorting,
val shownUnitGroups: List<UnitGroup>,
val unitConverterFavoritesOnly: Boolean,
val enableToolsExperiment: Boolean,
val latestLeftSideUnit: String,
val latestRightSideUnit: String,
)
data class DisplayPreferences(
val systemFont: Boolean,
val middleZero: Boolean,
)
data class FormattingPreferences(
val digitsPrecision: Int,
val separator: Int,
val outputFormat: Int,
)
data class UnitGroupsPreferences(
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
)
data class AddSubtractPreferences(
val separator: Int,
val enableVibrations: Boolean,
)
data class AboutPreferences(
val enableToolsExperiment: Boolean,
)
data class StartingScreenPreferences(
val startingScreen: String,
)

View File

@ -0,0 +1,49 @@
/*
* 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.data.userprefs
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
internal 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")
}

View File

@ -21,12 +21,8 @@ package com.sadellie.unitto.data.userprefs
import androidx.compose.ui.graphics.Color
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.base.TopLevelDestinations
@ -43,214 +39,107 @@ import kotlinx.coroutines.flow.map
import java.io.IOException
import javax.inject.Inject
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 AppPreferences(
val themingMode: ThemingMode = ThemingMode.AUTO,
val enableDynamicTheme: Boolean = true,
val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified,
val monetMode: MonetMode = MonetMode.TonalSpot,
val startingScreen: String = TopLevelDestinations.Calculator.graph,
val enableToolsExperiment: Boolean = false,
val systemFont: Boolean = false,
)
data class GeneralPreferences(
val enableVibrations: Boolean = true,
)
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 DisplayPreferences(
val systemFont: Boolean = false,
val middleZero: Boolean = false,
)
data class FormattingPreferences(
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACE,
val outputFormat: Int = OutputFormat.PLAIN,
)
data class UnitGroupsPreferences(
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
)
data class AddSubtractPreferences(
val separator: Int = Separator.SPACE,
val enableVibrations: Boolean = true,
)
data class AboutPreferences(
val enableToolsExperiment: Boolean = false,
)
data class StartingScreenPreferences(
val startingScreen: String = TopLevelDestinations.Calculator.graph,
)
class UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<Preferences>) {
class UserPreferencesRepository @Inject constructor(
private val dataStore: DataStore<Preferences>,
) {
private val data = dataStore.data
.catch { if (it is IOException) emit(emptyPreferences()) else throw it }
val appPrefs: Flow<AppPreferences> = data
.map { preferences ->
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.TonalSpot,
startingScreen = preferences[PrefsKeys.STARTING_SCREEN]
?: TopLevelDestinations.Calculator.graph,
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false,
systemFont = preferences[PrefsKeys.SYSTEM_FONT] ?: false
themingMode = preferences.getThemingMode(),
enableDynamicTheme = preferences.getEnableDynamicTheme(),
enableAmoledTheme = preferences.getEnableAmoledTheme(),
customColor = preferences.getCustomColor(),
monetMode = preferences.getMonetMode(),
startingScreen = preferences.getStartingScreen(),
enableToolsExperiment = preferences.getEnableToolsExperiment(),
systemFont = preferences.getSystemFont()
)
}
val generalPrefs: Flow<GeneralPreferences> = data
.map { preferences ->
GeneralPreferences(
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
enableVibrations = preferences.getEnableVibrations(),
)
}
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
radianMode = preferences.getRadianMode(),
enableVibrations = preferences.getEnableVibrations(),
separator = preferences.getSeparator(),
middleZero = preferences.getMiddleZero(),
partialHistoryView = preferences.getPartialHistoryView(),
precision = preferences.getDigitsPrecision(),
outputFormat = preferences.getOutputFormat()
)
}
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,
enableVibrations = preferences.getEnableVibrations(),
separator = preferences.getSeparator(),
middleZero = preferences.getMiddleZero(),
precision = preferences.getDigitsPrecision(),
outputFormat = preferences.getOutputFormat(),
unitConverterFormatTime = preferences.getUnitConverterFormatTime(),
unitConverterSorting = preferences.getUnitConverterSorting(),
shownUnitGroups = preferences.getShownUnitGroups(),
unitConverterFavoritesOnly = preferences.getUnitConverterFavoritesOnly(),
enableToolsExperiment = preferences.getEnableToolsExperiment(),
latestLeftSideUnit = preferences.getLatestLeftSide(),
latestRightSideUnit = preferences.getLatestRightSide(),
)
}
val displayPrefs: Flow<DisplayPreferences> = data
.map { preferences ->
DisplayPreferences(
systemFont = preferences[PrefsKeys.SYSTEM_FONT] ?: false,
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false,
systemFont = preferences.getSystemFont(),
middleZero = preferences.getMiddleZero(),
)
}
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,
digitsPrecision = preferences.getDigitsPrecision(),
separator = preferences.getSeparator(),
outputFormat = preferences.getOutputFormat(),
)
}
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,
shownUnitGroups = preferences.getShownUnitGroups(),
)
}
val addSubtractPrefs: Flow<AddSubtractPreferences> = data
.map { preferences ->
AddSubtractPreferences(
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE,
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
separator = preferences.getSeparator(),
enableVibrations = preferences.getEnableVibrations(),
)
}
val aboutPrefs: Flow<AboutPreferences> = data
.map { preferences ->
AboutPreferences(
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
enableToolsExperiment = preferences.getEnableToolsExperiment()
)
}
val startingScreenPrefs: Flow<StartingScreenPreferences> = data
.map { preferences ->
StartingScreenPreferences(
startingScreen = preferences[PrefsKeys.STARTING_SCREEN]
?: TopLevelDestinations.Calculator.graph,
startingScreen = preferences.getStartingScreen(),
)
}
@ -374,10 +263,104 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
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
}
}
private fun Preferences.getEnableDynamicTheme(): Boolean {
return this[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true
}
private fun Preferences.getThemingMode(): ThemingMode {
return this[PrefsKeys.THEMING_MODE]
?.letTryOrNull { ThemingMode.valueOf(it) }
?: ThemingMode.AUTO
}
private fun Preferences.getEnableAmoledTheme(): Boolean {
return this[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
}
private fun Preferences.getCustomColor(): Color {
return this[PrefsKeys.CUSTOM_COLOR]?.letTryOrNull { Color(it.toULong()) }
?: Color.Unspecified
}
private fun Preferences.getMonetMode(): MonetMode {
return this[PrefsKeys.MONET_MODE]?.letTryOrNull { MonetMode.valueOf(it) }
?: MonetMode.TonalSpot
}
private fun Preferences.getStartingScreen(): String {
return this[PrefsKeys.STARTING_SCREEN]
?: TopLevelDestinations.Calculator.graph
}
private fun Preferences.getEnableToolsExperiment(): Boolean {
return this[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
}
private fun Preferences.getSystemFont(): Boolean {
return this[PrefsKeys.SYSTEM_FONT] ?: false
}
private fun Preferences.getEnableVibrations(): Boolean {
return this[PrefsKeys.ENABLE_VIBRATIONS] ?: true
}
private fun Preferences.getRadianMode(): Boolean {
return this[PrefsKeys.RADIAN_MODE] ?: true
}
private fun Preferences.getSeparator(): Int {
return this[PrefsKeys.SEPARATOR] ?: Separator.SPACE
}
private fun Preferences.getMiddleZero(): Boolean {
return this[PrefsKeys.MIDDLE_ZERO] ?: false
}
private fun Preferences.getPartialHistoryView(): Boolean {
return this[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true
}
private fun Preferences.getDigitsPrecision(): Int {
return this[PrefsKeys.DIGITS_PRECISION] ?: 3
}
private fun Preferences.getOutputFormat(): Int {
return this[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
}
private fun Preferences.getUnitConverterFormatTime(): Boolean {
return this[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
}
private fun Preferences.getUnitConverterSorting(): UnitsListSorting {
return this[PrefsKeys.UNIT_CONVERTER_SORTING]
?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE
}
private fun Preferences.getShownUnitGroups(): List<UnitGroup> {
return this[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list ->
list.ifEmpty { return@letTryOrNull listOf() }.split(",")
.map { UnitGroup.valueOf(it) }
} ?: ALL_UNIT_GROUPS
}
private fun Preferences.getUnitConverterFavoritesOnly(): Boolean {
return this[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY]
?: false
}
private fun Preferences.getLatestLeftSide(): String {
return this[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
}
private fun Preferences.getLatestRightSide(): String {
return this[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
}
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
this?.let(block)
} catch (e: Exception) {
null
}

View File

@ -18,24 +18,17 @@
package com.sadellie.unitto.feature.calculator
import android.content.res.Configuration
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
@ -53,9 +46,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
@ -70,11 +61,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.SettingsButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
import com.sadellie.unitto.core.ui.common.textfield.UnformattedTextField
import com.sadellie.unitto.data.model.HistoryItem
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboardLoading
import com.sadellie.unitto.feature.calculator.components.HistoryItemHeight
import com.sadellie.unitto.feature.calculator.components.HistoryList
import com.sadellie.unitto.feature.calculator.components.TextBox
@ -117,10 +107,7 @@ internal fun CalculatorScreen(
clearHistory: () -> Unit
) {
when (uiState) {
is CalculatorUIState.Loading -> Loading(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings,
)
is CalculatorUIState.Loading -> UnittoEmptyScreen()
is CalculatorUIState.Ready -> Ready(
uiState = uiState,
navigateToMenu = navigateToMenu,
@ -314,81 +301,6 @@ private fun Ready(
}
}
@Composable
internal fun Loading(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
) {
UnittoScreenWithTopBar(
title = { Text(stringResource(R.string.calculator_title)) },
navigationIcon = { MenuButton { navigateToMenu() } },
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceVariant),
actions = { SettingsButton(navigateToSettings) }
) { paddingValues ->
BoxWithConstraints(
modifier = Modifier.padding(paddingValues),
) {
// Input
Column(
Modifier
.height(maxHeight * 0.25f)
.background(
MaterialTheme.colorScheme.surfaceVariant,
RoundedCornerShape(
topStartPercent = 0, topEndPercent = 0,
bottomStartPercent = 20, bottomEndPercent = 20
)
)
.padding(top = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
UnformattedTextField(
modifier = Modifier
.weight(2f)
.fillMaxWidth()
.padding(horizontal = 8.dp),
value = TextFieldValue(),
minRatio = 0.5f,
onCursorChange = {},
)
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
UnformattedTextField(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.padding(horizontal = 8.dp),
value = TextFieldValue(),
minRatio = 1f,
onCursorChange = {},
readOnly = true,
)
}
// Handle
Box(
Modifier
.padding(8.dp)
.background(
MaterialTheme.colorScheme.onSurfaceVariant,
RoundedCornerShape(100)
)
.sizeIn(24.dp, 4.dp)
)
}
// Keyboard
CalculatorKeyboardLoading(
modifier = Modifier
.semantics { testTag = "loading" }
.offset(y = maxHeight * 0.25f)
.height(maxHeight * 0.75f)
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp),
)
}
}
}
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")

View File

@ -22,6 +22,8 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.addTokens
@ -58,7 +60,15 @@ internal class CalculatorViewModel @Inject constructor(
userPrefsRepository.calculatorPrefs.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000L),
CalculatorPreferences()
CalculatorPreferences(
radianMode = false,
enableVibrations = false,
separator = Separator.SPACE,
middleZero = false,
partialHistoryView = true,
precision = 3,
outputFormat = OutputFormat.PLAIN
)
)
private val _input: MutableStateFlow<TextFieldValue> = MutableStateFlow(TextFieldValue())
@ -79,7 +89,8 @@ internal class CalculatorViewModel @Inject constructor(
middleZero = userPrefs.middleZero,
partialHistoryView = userPrefs.partialHistoryView,
)
}.stateIn(
}
.stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000L), CalculatorUIState.Loading
)

View File

@ -24,7 +24,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -35,7 +34,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Icon
@ -50,11 +48,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional
@ -145,118 +141,6 @@ internal fun CalculatorKeyboard(
}
}
@Composable
internal fun CalculatorKeyboardLoading(
modifier: Modifier
) {
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
PortraitKeyboardLoading(modifier)
} else {
LandscapeKeyboardLoading(modifier)
}
}
@Composable
private fun PortraitKeyboardLoading(
modifier: Modifier
) {
ColumnWithConstraints(
modifier = modifier
) { constraints ->
val additionalButtonHeight by remember {
mutableStateOf(constraints.maxHeight * 0.09f)
}
val spacerHeight by remember {
mutableStateOf(constraints.maxHeight * 0.025f)
}
val additionalRowSpacedBy by remember {
mutableStateOf(constraints.maxWidth * 0.03f)
}
val weightModifier = Modifier.weight(1f)
val additionalButtonModifier = Modifier
.weight(1f)
.height(additionalButtonHeight)
Spacer(modifier = Modifier.height(spacerHeight))
Row(
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(additionalRowSpacedBy)
) {
// Additional buttons
Box(weightModifier) {
AdditionalButtonsPortrait(
modifier = additionalButtonModifier,
allowVibration = false,
addSymbol = {},
showAdditional = false,
radianMode = false,
toggleAngleMode = {},
toggleInvMode = {}
)
}
Box(
modifier = Modifier.size(additionalButtonHeight),
contentAlignment = Alignment.Center
) {
// Expand/Collapse
IconButton(
onClick = { },
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
) {
Icon(Icons.Default.ExpandMore, null)
}
}
}
Spacer(modifier = Modifier.height(spacerHeight))
Box(
modifier = weightModifier
.clip(RoundedCornerShape(32.dp))
.background(MaterialTheme.colorScheme.inverseOnSurface)
.fillMaxSize()
)
Spacer(modifier = Modifier.height(spacerHeight))
}
}
@Composable
private fun LandscapeKeyboardLoading(
modifier: Modifier
) {
RowWithConstraints(modifier) { constraints ->
val buttonModifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(constraints.maxWidth * 0.005f, constraints.maxHeight * 0.02f)
AdditionalButtonsLandscape(
modifier = Modifier.weight(1f),
buttonModifier = buttonModifier,
allowVibration = false,
radianMode = false,
addSymbol = {},
toggleAngleMode = {},
toggleInvMode = {}
)
Box(
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(MaterialTheme.colorScheme.inverseOnSurface)
.weight(5f)
.fillMaxSize()
)
}
}
@Composable
private fun PortraitKeyboard(
modifier: Modifier,

View File

@ -31,7 +31,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -64,7 +64,6 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
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.OutputFormat
@ -74,6 +73,7 @@ import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.PortraitLandscape
import com.sadellie.unitto.core.ui.common.SettingsButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTextField
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
@ -125,23 +125,16 @@ private fun ConverterScreen(
onCursorChange: (TextRange) -> Unit,
onErrorClick: (AbstractUnit) -> Unit,
) {
UnittoScreenWithTopBar(
title = { Text(stringResource(R.string.unit_converter_title)) },
navigationIcon = { MenuButton { navigateToMenu() } },
actions = {
SettingsButton(navigateToSettings)
},
colors = TopAppBarDefaults
.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
content = { padding ->
when (uiState) {
is UnitConverterUIState.Loading -> {
ConverterLoading(modifier = Modifier.padding(padding))
}
UnitConverterUIState.Loading -> UnittoEmptyScreen()
is UnitConverterUIState.NumberBase -> {
UnitConverterTopBar(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings
) {
NumberBase(
modifier = Modifier.padding(padding),
modifier = Modifier.padding(it),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
@ -152,10 +145,15 @@ private fun ConverterScreen(
clearInput = clearInput
)
}
}
is UnitConverterUIState.Default -> {
UnitConverterTopBar(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings
) {
Default(
modifier = Modifier.padding(padding),
modifier = Modifier.padding(it),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
@ -169,45 +167,22 @@ private fun ConverterScreen(
}
}
}
)
}
@Composable
private fun ConverterLoading(modifier: Modifier) {
PortraitLandscape(
modifier = modifier.fillMaxSize(),
content1 = { contentModifier ->
ColumnWithConstraints(modifier = contentModifier) {
val textFieldModifier = Modifier.weight(2f)
UnformattedTextField(
modifier = textFieldModifier,
value = TextFieldValue(stringResource(R.string.loading_label)),
onCursorChange = {},
minRatio = 0.7f,
readOnly = true
)
AnimatedUnitShortName()
ConverterResultTextField(
modifier = textFieldModifier,
result = ConverterResult.Loading
)
AnimatedUnitShortName()
Spacer(modifier = Modifier.height(it.maxHeight * 0.03f))
UnitSelectionButtons()
}
private fun UnitConverterTopBar(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
content: @Composable (PaddingValues) -> Unit
) {
UnittoScreenWithTopBar(
title = { Text(stringResource(R.string.unit_converter_title)) },
navigationIcon = { MenuButton { navigateToMenu() } },
actions = {
SettingsButton(navigateToSettings)
},
content2 = {
Box(
modifier = it
.clip(RoundedCornerShape(32.dp))
.background(MaterialTheme.colorScheme.inverseOnSurface)
.fillMaxSize()
)
}
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
content = { content(it) }
)
}

View File

@ -23,7 +23,6 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -48,6 +47,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoSearchBar
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
@ -71,9 +71,7 @@ internal fun LeftSideRoute(
when (
val uiState = viewModel.leftSideUIState.collectAsStateWithLifecycle().value
) {
is LeftSideUIState.Loading -> {
Box(modifier = Modifier.fillMaxSize())
}
is LeftSideUIState.Loading -> UnittoEmptyScreen()
is LeftSideUIState.Ready -> LeftSideScreen(
uiState = uiState,
onQueryChange = viewModel::queryChangeLeft,

View File

@ -19,7 +19,6 @@
package com.sadellie.unitto.feature.converter
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
@ -36,6 +35,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoSearchBar
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
@ -62,7 +62,7 @@ internal fun RightSideRoute(
when (
val uiState = viewModel.rightSideUIState.collectAsStateWithLifecycle().value
) {
is RightSideUIState.Loading -> Box(Modifier.fillMaxSize())
is RightSideUIState.Loading -> UnittoEmptyScreen()
is RightSideUIState.Ready ->
RightSideScreen(
uiState = uiState,

View File

@ -54,6 +54,7 @@ import com.sadellie.unitto.core.base.BuildConfig
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink
@ -72,17 +73,22 @@ internal fun SettingsRoute(
navigateUp: () -> Unit,
navControllerAction: (String) -> Unit,
) {
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle().value
val cachePercentage = viewModel.cachePercentage.collectAsStateWithLifecycle()
when (userPrefs) {
null -> UnittoEmptyScreen()
else -> {
SettingsScreen(
userPrefs = userPrefs.value,
userPrefs = userPrefs,
navigateUp = navigateUp,
navControllerAction = navControllerAction,
updateVibrations = viewModel::updateVibrations,
cachePercentage = cachePercentage.value,
clearCache = viewModel::clearCache,
)
}
}
}
@Composable
@ -197,7 +203,9 @@ private fun PreviewSettingsScreen() {
var cacheSize by remember { mutableFloatStateOf(0.9f) }
SettingsScreen(
userPrefs = GeneralPreferences(),
userPrefs = GeneralPreferences(
enableVibrations = true
),
navigateUp = {},
navControllerAction = {},
updateVibrations = {},

View File

@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.database.CurrencyRatesDao
import com.sadellie.unitto.data.userprefs.GeneralPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -36,7 +35,7 @@ internal class SettingsViewModel @Inject constructor(
private val currencyRatesDao: CurrencyRatesDao,
) : ViewModel() {
val userPrefs = userPrefsRepository.generalPrefs
.stateIn(viewModelScope, GeneralPreferences())
.stateIn(viewModelScope, null)
val cachePercentage = currencyRatesDao.size()
.map {

View File

@ -47,6 +47,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.BuildConfig
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink
@ -59,14 +60,17 @@ internal fun AboutRoute(
navigateUpAction: () -> Unit,
navigateToThirdParty: () -> Unit,
) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
AboutScreen(
prefs = prefs.value,
prefs = prefs,
navigateUpAction = navigateUpAction,
navigateToThirdParty = navigateToThirdParty,
enableToolsExperiment = viewModel::enableToolsExperiment
)
}
}
}
@Composable
@ -196,7 +200,9 @@ private fun AboutScreen(
@Composable
fun PreviewAboutScreen() {
AboutScreen(
prefs = AboutPreferences(),
prefs = AboutPreferences(
enableToolsExperiment = false
),
navigateUpAction = {},
navigateToThirdParty = {},
enableToolsExperiment = {}

View File

@ -21,7 +21,6 @@ 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
@ -32,7 +31,7 @@ internal class AboutViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.aboutPrefs
.stateIn(viewModelScope, AboutPreferences())
.stateIn(viewModelScope, null)
fun enableToolsExperiment() = viewModelScope.launch {
userPrefsRepository.updateToolsExperiment(true)

View File

@ -26,8 +26,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.data.userprefs.CalculatorPreferences
@ -37,13 +40,16 @@ internal fun CalculatorSettingsRoute(
viewModel: CalculatorViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
CalculatorSettingsScreen(
prefs = prefs.value,
prefs = prefs,
navigateUpAction = navigateUpAction,
updatePartialHistoryView = viewModel::updatePartialHistoryView
)
}
}
}
@Composable
@ -75,7 +81,15 @@ private fun CalculatorSettingsScreen(
@Composable
private fun PreviewCalculatorSettingsScreen() {
CalculatorSettingsScreen(
prefs = CalculatorPreferences(),
prefs = CalculatorPreferences(
radianMode = false,
enableVibrations = false,
separator = Separator.SPACE,
middleZero = false,
partialHistoryView = true,
precision = 3,
outputFormat = OutputFormat.PLAIN
),
navigateUpAction = {},
updatePartialHistoryView = {}
)

View File

@ -21,7 +21,6 @@ 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
@ -32,7 +31,7 @@ class CalculatorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.calculatorPrefs
.stateIn(viewModelScope, CalculatorPreferences())
.stateIn(viewModelScope, null)
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updatePartialHistoryView(enabled)

View File

@ -34,10 +34,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.userprefs.ConverterPreferences
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
@ -48,15 +52,18 @@ internal fun ConverterSettingsRoute(
navigateUpAction: () -> Unit,
navigateToUnitsGroup: () -> Unit,
) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
ConverterSettingsScreen(
prefs = prefs.value,
prefs = prefs,
navigateUpAction = navigateUpAction,
navigateToUnitsGroup = navigateToUnitsGroup,
updateUnitConverterFormatTime = viewModel::updateUnitConverterFormatTime,
updateUnitConverterSorting = viewModel::updateUnitConverterSorting
)
}
}
}
@Composable
@ -127,7 +134,20 @@ private fun ConverterSettingsScreen(
@Composable
private fun PreviewConverterSettingsScreen() {
ConverterSettingsScreen(
prefs = ConverterPreferences(),
prefs = ConverterPreferences(
enableVibrations = true,
separator = Separator.SPACE,
middleZero = false,
precision = 3,
outputFormat = OutputFormat.PLAIN,
unitConverterFormatTime = false,
unitConverterSorting = UnitsListSorting.USAGE,
shownUnitGroups = ALL_UNIT_GROUPS,
unitConverterFavoritesOnly = false,
enableToolsExperiment = false,
latestLeftSideUnit = "kilometer",
latestRightSideUnit = "mile",
),
navigateUpAction = {},
navigateToUnitsGroup = {},
updateUnitConverterFormatTime = {},

View File

@ -22,7 +22,6 @@ 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
@ -33,7 +32,7 @@ internal class ConverterViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.converterPrefs
.stateIn(viewModelScope, ConverterPreferences())
.stateIn(viewModelScope, null)
fun updateUnitConverterFormatTime(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterFormatTime(enabled)

View File

@ -59,6 +59,7 @@ import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.feature.settings.components.ColorSelector
@ -75,46 +76,49 @@ internal fun DisplayRoute(
themmoController: ThemmoController,
navigateToLanguages: () -> Unit,
) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
DisplayScreen(
navigateUp = navigateUp,
currentThemingMode = themmoController.currentThemingMode,
onThemeChange = {
themmoController.setThemingMode(it)
viewModel.updateThemingMode(it)
onThemeChange = { newValue ->
themmoController.setThemingMode(newValue)
viewModel.updateThemingMode(newValue)
},
isDynamicThemeEnabled = themmoController.isDynamicThemeEnabled,
onDynamicThemeChange = {
onDynamicThemeChange = { newValue ->
// Prevent old devices from using other monet modes when dynamic theming is on
if (it) {
if (newValue) {
themmoController.setMonetMode(MonetMode.TonalSpot)
viewModel.updateMonetMode(MonetMode.TonalSpot)
}
themmoController.enableDynamicTheme(it)
viewModel.updateDynamicTheme(it)
themmoController.enableDynamicTheme(newValue)
viewModel.updateDynamicTheme(newValue)
},
isAmoledThemeEnabled = themmoController.isAmoledThemeEnabled,
onAmoledThemeChange = {
themmoController.enableAmoledTheme(it)
viewModel.updateAmoledTheme(it)
onAmoledThemeChange = { newValue ->
themmoController.enableAmoledTheme(newValue)
viewModel.updateAmoledTheme(newValue)
},
selectedColor = themmoController.currentCustomColor,
onColorChange = {
themmoController.setCustomColor(it)
viewModel.updateCustomColor(it)
onColorChange = { newValue ->
themmoController.setCustomColor(newValue)
viewModel.updateCustomColor(newValue)
},
monetMode = themmoController.currentMonetMode,
onMonetModeChange = {
themmoController.setMonetMode(it)
viewModel.updateMonetMode(it)
onMonetModeChange = { newValue ->
themmoController.setMonetMode(newValue)
viewModel.updateMonetMode(newValue)
},
systemFont = prefs.value.systemFont,
systemFont = prefs.systemFont,
updateSystemFont = viewModel::updateSystemFont,
middleZero = prefs.value.middleZero,
middleZero = prefs.middleZero,
updateMiddleZero = viewModel::updateMiddleZero,
navigateToLanguages = navigateToLanguages
)
}
}
}
@Composable

View File

@ -22,7 +22,6 @@ 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.DisplayPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.MonetMode
@ -36,7 +35,7 @@ class DisplayViewModel @Inject constructor(
) : ViewModel() {
val prefs = userPrefsRepository.displayPrefs
.stateIn(viewModelScope, DisplayPreferences())
.stateIn(viewModelScope, null)
/**
* @see UserPreferencesRepository.updateThemingMode

View File

@ -57,6 +57,7 @@ import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.UnittoSlider
@ -70,16 +71,19 @@ fun FormattingRoute(
viewModel: FormattingViewModel = hiltViewModel(),
navigateUpAction: () -> Unit,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
FormattingScreen(
navigateUpAction = navigateUpAction,
uiState = uiState.value,
uiState = uiState,
onPrecisionChange = viewModel::updatePrecision,
onSeparatorChange = viewModel::updateSeparator,
onOutputFormatChange = viewModel::updateOutputFormat,
togglePreview = viewModel::togglePreview
)
}
}
}
@Composable

View File

@ -61,7 +61,7 @@ class FormattingViewModel @Inject constructor(
formatterSymbols = formatterSymbols
)
}
.stateIn(viewModelScope, FormattingUIState())
.stateIn(viewModelScope, null)
fun togglePreview() = _fractional.update { !it }

View File

@ -23,8 +23,8 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.navDeepLink
import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.core.ui.unittoComposable
import com.sadellie.unitto.core.ui.unittoNavigation
import com.sadellie.unitto.core.ui.unittoStackedComposable
import com.sadellie.unitto.feature.settings.SettingsRoute
import com.sadellie.unitto.feature.settings.about.AboutRoute
import com.sadellie.unitto.feature.settings.calculator.CalculatorSettingsRoute
@ -68,14 +68,14 @@ fun NavGraphBuilder.settingGraph(
navDeepLink { uriPattern = "app://com.sadellie.unitto/$graph" }
)
) {
unittoComposable(start) {
unittoStackedComposable(start) {
SettingsRoute(
navigateUp = navController::navigateUp,
navControllerAction = navController::navigate
)
}
unittoComposable(displayRoute) {
unittoStackedComposable(displayRoute) {
DisplayRoute(
navigateUp = navController::navigateUp,
themmoController = themmoController,
@ -83,51 +83,51 @@ fun NavGraphBuilder.settingGraph(
)
}
unittoComposable(languageRoute) {
unittoStackedComposable(languageRoute) {
LanguageRoute(
navigateUp = navController::navigateUp,
)
}
unittoComposable(startingScreenRoute) {
unittoStackedComposable(startingScreenRoute) {
StartingScreenRoute(
navigateUp = navController::navigateUp,
)
}
unittoComposable(formattingRoute) {
unittoStackedComposable(formattingRoute) {
FormattingRoute(
navigateUpAction = navController::navigateUp
)
}
unittoComposable(calculatorSettingsRoute) {
unittoStackedComposable(calculatorSettingsRoute) {
CalculatorSettingsRoute(
navigateUpAction = navController::navigateUp,
)
}
unittoComposable(converterSettingsRoute) {
unittoStackedComposable(converterSettingsRoute) {
ConverterSettingsRoute(
navigateUpAction = navController::navigateUp,
navigateToUnitsGroup = { navController.navigate(unitsGroupRoute) }
)
}
unittoComposable(unitsGroupRoute) {
unittoStackedComposable(unitsGroupRoute) {
UnitGroupsScreen(
navigateUpAction = navController::navigateUp,
)
}
unittoComposable(aboutRoute) {
unittoStackedComposable(aboutRoute) {
AboutRoute(
navigateUpAction = navController::navigateUp,
navigateToThirdParty = { navController.navigate(thirdPartyRoute) }
)
}
unittoComposable(thirdPartyRoute) {
unittoStackedComposable(thirdPartyRoute) {
ThirdPartyLicensesScreen(
navigateUpAction = navController::navigateUp,
)

View File

@ -40,6 +40,7 @@ import com.sadellie.unitto.core.base.TOP_LEVEL_DESTINATIONS
import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.core.ui.addShortcut
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
@ -48,13 +49,16 @@ internal fun StartingScreenRoute(
viewModel: StartingScreenViewModel = hiltViewModel(),
navigateUp: () -> Unit
) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle()
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
else -> {
StartingScreenScreen(
startingScreen = prefs.value.startingScreen,
startingScreen = prefs.startingScreen,
updateStartingScreen = viewModel::updateStartingScreen,
navigateUp = navigateUp
)
}
}
}
@Composable

View File

@ -21,7 +21,6 @@ package com.sadellie.unitto.feature.settings.startingscreen
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.StartingScreenPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
@ -32,7 +31,7 @@ internal class StartingScreenViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() {
val prefs = userPrefsRepository.startingScreenPrefs
.stateIn(viewModelScope, StartingScreenPreferences())
.stateIn(viewModelScope, null)
fun updateStartingScreen(startingScreen: String) = viewModelScope.launch {
userPrefsRepository.updateStartingScreen(startingScreen)