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 val prefs = userPrefsRepository.appPrefs
.collectAsStateWithLifecycle(null).value .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.core.ui.theme.TypographyUnitto
import com.sadellie.unitto.data.userprefs.AppPreferences import com.sadellie.unitto.data.userprefs.AppPreferences
import io.github.sadellie.themmo.Themmo import io.github.sadellie.themmo.Themmo
import io.github.sadellie.themmo.rememberThemmoController import io.github.sadellie.themmo.ThemmoController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
internal fun UnittoApp(prefs: AppPreferences) { internal fun UnittoApp(prefs: AppPreferences?) {
val mContext = LocalContext.current 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 navController = rememberNavController()
val sysUiController = rememberSystemUiController() val sysUiController = rememberSystemUiController()
@ -97,66 +88,80 @@ internal fun UnittoApp(prefs: AppPreferences) {
} }
} }
Themmo( if (prefs != null) {
themmoController = themmoController, val themmoController = remember(prefs) {
typography = if (prefs.systemFont) TypographySystem else TypographyUnitto, ThemmoController(
animationSpec = tween(250) lightColorScheme = LightThemeColors,
) { darkColorScheme = DarkThemeColors,
val backgroundColor = MaterialTheme.colorScheme.background themingMode = prefs.themingMode,
val useDarkIcons by remember(backgroundColor) { dynamicThemeEnabled = prefs.enableDynamicTheme,
mutableStateOf(backgroundColor.luminance() > 0.5f) amoledThemeEnabled = prefs.enableAmoledTheme,
customColor = prefs.customColor,
monetMode = prefs.monetMode
)
} }
UnittoModalNavigationDrawer( Themmo(
drawer = { themmoController = themmoController,
UnittoDrawerSheet( typography = if (prefs.systemFont) TypographySystem else TypographyUnitto,
modifier = Modifier, animationSpec = tween(250)
tabs = tabs, ) {
currentDestination = navBackStackEntry?.destination?.route val backgroundColor = MaterialTheme.colorScheme.background
) { destination -> val useDarkIcons by remember(backgroundColor) {
drawerScope.launch { drawerState.close() } mutableStateOf(backgroundColor.luminance() > 0.5f)
navController.navigate(destination.graph) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
shortcutsScope.launch {
destination.shortcut?.let { shortcut: Shortcut ->
mContext.pushDynamicShortcut(
destination.graph,
shortcut.shortcutShortLabel,
shortcut.shortcutLongLabel,
shortcut.shortcutDrawable
)
}
}
}
},
modifier = Modifier,
state = drawerState,
gesturesEnabled = gesturesEnabled,
scope = drawerScope,
content = {
UnittoNavigation(
navController = navController,
themmoController = it,
startDestination = prefs.startingScreen,
openDrawer = { drawerScope.launch { drawerState.open() } }
)
} }
)
BackHandler(drawerState.isOpen) { UnittoModalNavigationDrawer(
drawerScope.launch { drawerState.close() } drawer = {
} UnittoDrawerSheet(
modifier = Modifier,
tabs = tabs,
currentDestination = navBackStackEntry?.destination?.route
) { destination ->
drawerScope.launch { drawerState.close() }
LaunchedEffect(useDarkIcons) { navController.navigate(destination.graph) {
sysUiController.setNavigationBarColor(Color.Transparent, useDarkIcons) popUpTo(navController.graph.findStartDestination().id) {
sysUiController.setStatusBarColor(Color.Transparent, useDarkIcons) saveState = true
}
launchSingleTop = true
restoreState = true
}
shortcutsScope.launch {
destination.shortcut?.let { shortcut: Shortcut ->
mContext.pushDynamicShortcut(
destination.graph,
shortcut.shortcutShortLabel,
shortcut.shortcutLongLabel,
shortcut.shortcutDrawable
)
}
}
}
},
modifier = Modifier,
state = drawerState,
gesturesEnabled = gesturesEnabled,
scope = drawerScope,
content = {
UnittoNavigation(
navController = navController,
themmoController = it,
startDestination = prefs.startingScreen,
openDrawer = { drawerScope.launch { drawerState.open() } }
)
}
)
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.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition 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.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.IntOffset
import androidx.navigation.NamedNavArgument import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink import androidx.navigation.NavDeepLink
@ -40,16 +45,16 @@ fun NavGraphBuilder.unittoComposable(
arguments: List<NamedNavArgument> = emptyList(), arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(), deepLinks: List<NavDeepLink> = emptyList(),
enterTransition: (@JvmSuppressWildcards enterTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = { fadeIn() }, AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = { unittoFadeIn() },
exitTransition: (@JvmSuppressWildcards exitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = { fadeOut() }, AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = { unittoFadeOut() },
popEnterTransition: (@JvmSuppressWildcards popEnterTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
enterTransition, enterTransition,
popExitTransition: (@JvmSuppressWildcards popExitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
exitTransition, exitTransition,
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
): Unit = composable( ): Unit = composable(
route = route, route = route,
arguments = arguments, arguments = arguments,
@ -61,6 +66,40 @@ fun NavGraphBuilder.unittoComposable(
content = content, 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 * @see NavGraphBuilder.navigation
*/ */
@ -79,7 +118,7 @@ fun NavGraphBuilder.unittoNavigation(
popExitTransition: ( popExitTransition: (
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition? AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
)? = exitTransition, )? = exitTransition,
builder: NavGraphBuilder.() -> Unit builder: NavGraphBuilder.() -> Unit,
): Unit = navigation( ): Unit = navigation(
startDestination = startDestination, startDestination = startDestination,
route = route, route = route,
@ -91,3 +130,11 @@ fun NavGraphBuilder.unittoNavigation(
popExitTransition = popExitTransition, popExitTransition = popExitTransition,
builder = builder 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.compose.ui.graphics.Color
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences 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.OutputFormat
import com.sadellie.unitto.core.base.Separator import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.base.TopLevelDestinations import com.sadellie.unitto.core.base.TopLevelDestinations
@ -43,214 +39,107 @@ import kotlinx.coroutines.flow.map
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
private object PrefsKeys { class UserPreferencesRepository @Inject constructor(
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY") private val dataStore: DataStore<Preferences>,
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>) {
private val data = dataStore.data private val data = dataStore.data
.catch { if (it is IOException) emit(emptyPreferences()) else throw it } .catch { if (it is IOException) emit(emptyPreferences()) else throw it }
val appPrefs: Flow<AppPreferences> = data val appPrefs: Flow<AppPreferences> = data
.map { preferences -> .map { preferences ->
AppPreferences( AppPreferences(
themingMode = preferences[PrefsKeys.THEMING_MODE]?.letTryOrNull { themingMode = preferences.getThemingMode(),
ThemingMode.valueOf(it) enableDynamicTheme = preferences.getEnableDynamicTheme(),
} enableAmoledTheme = preferences.getEnableAmoledTheme(),
?: ThemingMode.AUTO, customColor = preferences.getCustomColor(),
enableDynamicTheme = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true, monetMode = preferences.getMonetMode(),
enableAmoledTheme = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false, startingScreen = preferences.getStartingScreen(),
customColor = preferences[PrefsKeys.CUSTOM_COLOR]?.letTryOrNull { Color(it.toULong()) } enableToolsExperiment = preferences.getEnableToolsExperiment(),
?: Color.Unspecified, systemFont = preferences.getSystemFont()
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
) )
} }
val generalPrefs: Flow<GeneralPreferences> = data val generalPrefs: Flow<GeneralPreferences> = data
.map { preferences -> .map { preferences ->
GeneralPreferences( GeneralPreferences(
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true, enableVibrations = preferences.getEnableVibrations(),
) )
} }
val calculatorPrefs: Flow<CalculatorPreferences> = data val calculatorPrefs: Flow<CalculatorPreferences> = data
.map { preferences -> .map { preferences ->
CalculatorPreferences( CalculatorPreferences(
radianMode = preferences[PrefsKeys.RADIAN_MODE] ?: true, radianMode = preferences.getRadianMode(),
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true, enableVibrations = preferences.getEnableVibrations(),
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE, separator = preferences.getSeparator(),
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false, middleZero = preferences.getMiddleZero(),
partialHistoryView = preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true, partialHistoryView = preferences.getPartialHistoryView(),
precision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3, precision = preferences.getDigitsPrecision(),
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN outputFormat = preferences.getOutputFormat()
) )
} }
val converterPrefs: Flow<ConverterPreferences> = data val converterPrefs: Flow<ConverterPreferences> = data
.map { preferences -> .map { preferences ->
ConverterPreferences( ConverterPreferences(
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true, enableVibrations = preferences.getEnableVibrations(),
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE, separator = preferences.getSeparator(),
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false, middleZero = preferences.getMiddleZero(),
precision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3, precision = preferences.getDigitsPrecision(),
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN, outputFormat = preferences.getOutputFormat(),
unitConverterFormatTime = preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] unitConverterFormatTime = preferences.getUnitConverterFormatTime(),
?: false, unitConverterSorting = preferences.getUnitConverterSorting(),
unitConverterSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING] shownUnitGroups = preferences.getShownUnitGroups(),
?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE, unitConverterFavoritesOnly = preferences.getUnitConverterFavoritesOnly(),
shownUnitGroups = preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list -> enableToolsExperiment = preferences.getEnableToolsExperiment(),
list.ifEmpty { return@letTryOrNull listOf() }.split(",") latestLeftSideUnit = preferences.getLatestLeftSide(),
.map { UnitGroup.valueOf(it) } latestRightSideUnit = preferences.getLatestRightSide(),
} ?: 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 displayPrefs: Flow<DisplayPreferences> = data val displayPrefs: Flow<DisplayPreferences> = data
.map { preferences -> .map { preferences ->
DisplayPreferences( DisplayPreferences(
systemFont = preferences[PrefsKeys.SYSTEM_FONT] ?: false, systemFont = preferences.getSystemFont(),
middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false, middleZero = preferences.getMiddleZero(),
) )
} }
val formattingPrefs: Flow<FormattingPreferences> = data val formattingPrefs: Flow<FormattingPreferences> = data
.map { preferences -> .map { preferences ->
FormattingPreferences( FormattingPreferences(
digitsPrecision = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3, digitsPrecision = preferences.getDigitsPrecision(),
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE, separator = preferences.getSeparator(),
outputFormat = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN, outputFormat = preferences.getOutputFormat(),
) )
} }
val unitGroupsPrefs: Flow<UnitGroupsPreferences> = data val unitGroupsPrefs: Flow<UnitGroupsPreferences> = data
.map { preferences -> .map { preferences ->
UnitGroupsPreferences( UnitGroupsPreferences(
shownUnitGroups = preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list -> shownUnitGroups = preferences.getShownUnitGroups(),
list.ifEmpty { return@letTryOrNull listOf() }.split(",")
.map { UnitGroup.valueOf(it) }
} ?: ALL_UNIT_GROUPS,
) )
} }
val addSubtractPrefs: Flow<AddSubtractPreferences> = data val addSubtractPrefs: Flow<AddSubtractPreferences> = data
.map { preferences -> .map { preferences ->
AddSubtractPreferences( AddSubtractPreferences(
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE, separator = preferences.getSeparator(),
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true, enableVibrations = preferences.getEnableVibrations(),
) )
} }
val aboutPrefs: Flow<AboutPreferences> = data val aboutPrefs: Flow<AboutPreferences> = data
.map { preferences -> .map { preferences ->
AboutPreferences( AboutPreferences(
enableToolsExperiment = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false enableToolsExperiment = preferences.getEnableToolsExperiment()
) )
} }
val startingScreenPrefs: Flow<StartingScreenPreferences> = data val startingScreenPrefs: Flow<StartingScreenPreferences> = data
.map { preferences -> .map { preferences ->
StartingScreenPreferences( StartingScreenPreferences(
startingScreen = preferences[PrefsKeys.STARTING_SCREEN] startingScreen = preferences.getStartingScreen(),
?: TopLevelDestinations.Calculator.graph,
) )
} }
@ -374,10 +263,104 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] = enabled preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] = enabled
} }
} }
}
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
this?.let(block) private fun Preferences.getEnableDynamicTheme(): Boolean {
} catch (e: Exception) { return this[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true
null }
}
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 package com.sadellie.unitto.feature.calculator
import android.content.res.Configuration
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable 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.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding 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.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@ -53,9 +46,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource 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.base.R
import com.sadellie.unitto.core.ui.common.MenuButton import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.SettingsButton 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.UnittoScreenWithTopBar
import com.sadellie.unitto.core.ui.common.textfield.UnformattedTextField
import com.sadellie.unitto.data.model.HistoryItem import com.sadellie.unitto.data.model.HistoryItem
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard 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.HistoryItemHeight
import com.sadellie.unitto.feature.calculator.components.HistoryList import com.sadellie.unitto.feature.calculator.components.HistoryList
import com.sadellie.unitto.feature.calculator.components.TextBox import com.sadellie.unitto.feature.calculator.components.TextBox
@ -117,10 +107,7 @@ internal fun CalculatorScreen(
clearHistory: () -> Unit clearHistory: () -> Unit
) { ) {
when (uiState) { when (uiState) {
is CalculatorUIState.Loading -> Loading( is CalculatorUIState.Loading -> UnittoEmptyScreen()
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings,
)
is CalculatorUIState.Ready -> Ready( is CalculatorUIState.Ready -> Ready(
uiState = uiState, uiState = uiState,
navigateToMenu = navigateToMenu, 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 = 1008, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 432, heightDp = 864, 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") @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.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.base.Token
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.addTokens import com.sadellie.unitto.core.ui.common.textfield.addTokens
@ -58,7 +60,15 @@ internal class CalculatorViewModel @Inject constructor(
userPrefsRepository.calculatorPrefs.stateIn( userPrefsRepository.calculatorPrefs.stateIn(
viewModelScope, viewModelScope,
SharingStarted.WhileSubscribed(5000L), 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()) private val _input: MutableStateFlow<TextFieldValue> = MutableStateFlow(TextFieldValue())
@ -79,7 +89,8 @@ internal class CalculatorViewModel @Inject constructor(
middleZero = userPrefs.middleZero, middleZero = userPrefs.middleZero,
partialHistoryView = userPrefs.partialHistoryView, partialHistoryView = userPrefs.partialHistoryView,
) )
}.stateIn( }
.stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000L), CalculatorUIState.Loading 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.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -50,11 +48,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview 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.base.Token
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional 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 @Composable
private fun PortraitKeyboard( private fun PortraitKeyboard(
modifier: Modifier, modifier: Modifier,

View File

@ -31,7 +31,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background 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.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat 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.MenuButton
import com.sadellie.unitto.core.ui.common.PortraitLandscape import com.sadellie.unitto.core.ui.common.PortraitLandscape
import com.sadellie.unitto.core.ui.common.SettingsButton 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.UnittoScreenWithTopBar
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTextField import com.sadellie.unitto.core.ui.common.textfield.ExpressionTextField
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
@ -124,6 +124,56 @@ private fun ConverterScreen(
clearInput: () -> Unit, clearInput: () -> Unit,
onCursorChange: (TextRange) -> Unit, onCursorChange: (TextRange) -> Unit,
onErrorClick: (AbstractUnit) -> Unit, onErrorClick: (AbstractUnit) -> Unit,
) {
when (uiState) {
UnitConverterUIState.Loading -> UnittoEmptyScreen()
is UnitConverterUIState.NumberBase -> {
UnitConverterTopBar(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings
) {
NumberBase(
modifier = Modifier.padding(it),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
deleteDigit = deleteDigit,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput
)
}
}
is UnitConverterUIState.Default -> {
UnitConverterTopBar(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings
) {
Default(
modifier = Modifier.padding(it),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
deleteDigit = deleteDigit,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput,
refreshCurrencyRates = onErrorClick
)
}
}
}
}
@Composable
private fun UnitConverterTopBar(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
content: @Composable (PaddingValues) -> Unit
) { ) {
UnittoScreenWithTopBar( UnittoScreenWithTopBar(
title = { Text(stringResource(R.string.unit_converter_title)) }, title = { Text(stringResource(R.string.unit_converter_title)) },
@ -131,83 +181,8 @@ private fun ConverterScreen(
actions = { actions = {
SettingsButton(navigateToSettings) SettingsButton(navigateToSettings)
}, },
colors = TopAppBarDefaults colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
.centerAlignedTopAppBarColors(containerColor = Color.Transparent), content = { content(it) }
content = { padding ->
when (uiState) {
is UnitConverterUIState.Loading -> {
ConverterLoading(modifier = Modifier.padding(padding))
}
is UnitConverterUIState.NumberBase -> {
NumberBase(
modifier = Modifier.padding(padding),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
deleteDigit = deleteDigit,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput
)
}
is UnitConverterUIState.Default -> {
Default(
modifier = Modifier.padding(padding),
uiState = uiState,
onCursorChange = onCursorChange,
processInput = processInput,
deleteDigit = deleteDigit,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput,
refreshCurrencyRates = onErrorClick
)
}
}
}
)
}
@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()
}
},
content2 = {
Box(
modifier = it
.clip(RoundedCornerShape(32.dp))
.background(MaterialTheme.colorScheme.inverseOnSurface)
.fillMaxSize()
)
}
) )
} }

View File

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

View File

@ -19,7 +19,6 @@
package com.sadellie.unitto.feature.converter package com.sadellie.unitto.feature.converter
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -36,6 +35,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.OutputFormat import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R 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.UnittoSearchBar
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression import com.sadellie.unitto.core.ui.common.textfield.formatExpression
@ -62,7 +62,7 @@ internal fun RightSideRoute(
when ( when (
val uiState = viewModel.rightSideUIState.collectAsStateWithLifecycle().value val uiState = viewModel.rightSideUIState.collectAsStateWithLifecycle().value
) { ) {
is RightSideUIState.Loading -> Box(Modifier.fillMaxSize()) is RightSideUIState.Loading -> UnittoEmptyScreen()
is RightSideUIState.Ready -> is RightSideUIState.Ready ->
RightSideScreen( RightSideScreen(
uiState = uiState, 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.base.R
import com.sadellie.unitto.core.ui.common.Header import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink import com.sadellie.unitto.core.ui.openLink
@ -72,17 +73,22 @@ internal fun SettingsRoute(
navigateUp: () -> Unit, navigateUp: () -> Unit,
navControllerAction: (String) -> Unit, navControllerAction: (String) -> Unit,
) { ) {
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle() val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle().value
val cachePercentage = viewModel.cachePercentage.collectAsStateWithLifecycle() val cachePercentage = viewModel.cachePercentage.collectAsStateWithLifecycle()
SettingsScreen( when (userPrefs) {
userPrefs = userPrefs.value, null -> UnittoEmptyScreen()
navigateUp = navigateUp, else -> {
navControllerAction = navControllerAction, SettingsScreen(
updateVibrations = viewModel::updateVibrations, userPrefs = userPrefs,
cachePercentage = cachePercentage.value, navigateUp = navigateUp,
clearCache = viewModel::clearCache, navControllerAction = navControllerAction,
) updateVibrations = viewModel::updateVibrations,
cachePercentage = cachePercentage.value,
clearCache = viewModel::clearCache,
)
}
}
} }
@Composable @Composable
@ -197,7 +203,9 @@ private fun PreviewSettingsScreen() {
var cacheSize by remember { mutableFloatStateOf(0.9f) } var cacheSize by remember { mutableFloatStateOf(0.9f) }
SettingsScreen( SettingsScreen(
userPrefs = GeneralPreferences(), userPrefs = GeneralPreferences(
enableVibrations = true
),
navigateUp = {}, navigateUp = {},
navControllerAction = {}, navControllerAction = {},
updateVibrations = {}, updateVibrations = {},

View File

@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.database.CurrencyRatesDao import com.sadellie.unitto.data.database.CurrencyRatesDao
import com.sadellie.unitto.data.userprefs.GeneralPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -36,7 +35,7 @@ internal class SettingsViewModel @Inject constructor(
private val currencyRatesDao: CurrencyRatesDao, private val currencyRatesDao: CurrencyRatesDao,
) : ViewModel() { ) : ViewModel() {
val userPrefs = userPrefsRepository.generalPrefs val userPrefs = userPrefsRepository.generalPrefs
.stateIn(viewModelScope, GeneralPreferences()) .stateIn(viewModelScope, null)
val cachePercentage = currencyRatesDao.size() val cachePercentage = currencyRatesDao.size()
.map { .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.BuildConfig
import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.NavigateUpButton 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink import com.sadellie.unitto.core.ui.openLink
@ -59,14 +60,17 @@ internal fun AboutRoute(
navigateUpAction: () -> Unit, navigateUpAction: () -> Unit,
navigateToThirdParty: () -> Unit, navigateToThirdParty: () -> Unit,
) { ) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle() when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
AboutScreen( else -> {
prefs = prefs.value, AboutScreen(
navigateUpAction = navigateUpAction, prefs = prefs,
navigateToThirdParty = navigateToThirdParty, navigateUpAction = navigateUpAction,
enableToolsExperiment = viewModel::enableToolsExperiment navigateToThirdParty = navigateToThirdParty,
) enableToolsExperiment = viewModel::enableToolsExperiment
)
}
}
} }
@Composable @Composable
@ -196,7 +200,9 @@ private fun AboutScreen(
@Composable @Composable
fun PreviewAboutScreen() { fun PreviewAboutScreen() {
AboutScreen( AboutScreen(
prefs = AboutPreferences(), prefs = AboutPreferences(
enableToolsExperiment = false
),
navigateUpAction = {}, navigateUpAction = {},
navigateToThirdParty = {}, navigateToThirdParty = {},
enableToolsExperiment = {} enableToolsExperiment = {}

View File

@ -21,7 +21,6 @@ package com.sadellie.unitto.feature.settings.about
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.AboutPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -32,7 +31,7 @@ internal class AboutViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository, private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() { ) : ViewModel() {
val prefs = userPrefsRepository.aboutPrefs val prefs = userPrefsRepository.aboutPrefs
.stateIn(viewModelScope, AboutPreferences()) .stateIn(viewModelScope, null)
fun enableToolsExperiment() = viewModelScope.launch { fun enableToolsExperiment() = viewModelScope.launch {
userPrefsRepository.updateToolsExperiment(true) userPrefsRepository.updateToolsExperiment(true)

View File

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

View File

@ -21,7 +21,6 @@ package com.sadellie.unitto.feature.settings.calculator
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.CalculatorPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -32,7 +31,7 @@ class CalculatorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository, private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() { ) : ViewModel() {
val prefs = userPrefsRepository.calculatorPrefs val prefs = userPrefsRepository.calculatorPrefs
.stateIn(viewModelScope, CalculatorPreferences()) .stateIn(viewModelScope, null)
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch { fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updatePartialHistoryView(enabled) userPrefsRepository.updatePartialHistoryView(enabled)

View File

@ -34,10 +34,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.R
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.common.NavigateUpButton 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar 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.model.UnitsListSorting
import com.sadellie.unitto.data.userprefs.ConverterPreferences import com.sadellie.unitto.data.userprefs.ConverterPreferences
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
@ -48,15 +52,18 @@ internal fun ConverterSettingsRoute(
navigateUpAction: () -> Unit, navigateUpAction: () -> Unit,
navigateToUnitsGroup: () -> Unit, navigateToUnitsGroup: () -> Unit,
) { ) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle() when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
ConverterSettingsScreen( else -> {
prefs = prefs.value, ConverterSettingsScreen(
navigateUpAction = navigateUpAction, prefs = prefs,
navigateToUnitsGroup = navigateToUnitsGroup, navigateUpAction = navigateUpAction,
updateUnitConverterFormatTime = viewModel::updateUnitConverterFormatTime, navigateToUnitsGroup = navigateToUnitsGroup,
updateUnitConverterSorting = viewModel::updateUnitConverterSorting updateUnitConverterFormatTime = viewModel::updateUnitConverterFormatTime,
) updateUnitConverterSorting = viewModel::updateUnitConverterSorting
)
}
}
} }
@Composable @Composable
@ -127,7 +134,20 @@ private fun ConverterSettingsScreen(
@Composable @Composable
private fun PreviewConverterSettingsScreen() { private fun PreviewConverterSettingsScreen() {
ConverterSettingsScreen( 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 = {}, navigateUpAction = {},
navigateToUnitsGroup = {}, navigateToUnitsGroup = {},
updateUnitConverterFormatTime = {}, updateUnitConverterFormatTime = {},

View File

@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.UnitsListSorting import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.userprefs.ConverterPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -33,7 +32,7 @@ internal class ConverterViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository, private val userPrefsRepository: UserPreferencesRepository,
) : ViewModel() { ) : ViewModel() {
val prefs = userPrefsRepository.converterPrefs val prefs = userPrefsRepository.converterPrefs
.stateIn(viewModelScope, ConverterPreferences()) .stateIn(viewModelScope, null)
fun updateUnitConverterFormatTime(enabled: Boolean) = viewModelScope.launch { fun updateUnitConverterFormatTime(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterFormatTime(enabled) 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.NavigateUpButton
import com.sadellie.unitto.core.ui.common.SegmentedButton import com.sadellie.unitto.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.feature.settings.components.ColorSelector import com.sadellie.unitto.feature.settings.components.ColorSelector
@ -75,46 +76,49 @@ internal fun DisplayRoute(
themmoController: ThemmoController, themmoController: ThemmoController,
navigateToLanguages: () -> Unit, navigateToLanguages: () -> Unit,
) { ) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle() when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
DisplayScreen( else -> {
navigateUp = navigateUp, DisplayScreen(
currentThemingMode = themmoController.currentThemingMode, navigateUp = navigateUp,
onThemeChange = { currentThemingMode = themmoController.currentThemingMode,
themmoController.setThemingMode(it) onThemeChange = { newValue ->
viewModel.updateThemingMode(it) themmoController.setThemingMode(newValue)
}, viewModel.updateThemingMode(newValue)
isDynamicThemeEnabled = themmoController.isDynamicThemeEnabled, },
onDynamicThemeChange = { isDynamicThemeEnabled = themmoController.isDynamicThemeEnabled,
// Prevent old devices from using other monet modes when dynamic theming is on onDynamicThemeChange = { newValue ->
if (it) { // Prevent old devices from using other monet modes when dynamic theming is on
themmoController.setMonetMode(MonetMode.TonalSpot) if (newValue) {
viewModel.updateMonetMode(MonetMode.TonalSpot) themmoController.setMonetMode(MonetMode.TonalSpot)
} viewModel.updateMonetMode(MonetMode.TonalSpot)
themmoController.enableDynamicTheme(it) }
viewModel.updateDynamicTheme(it) themmoController.enableDynamicTheme(newValue)
}, viewModel.updateDynamicTheme(newValue)
isAmoledThemeEnabled = themmoController.isAmoledThemeEnabled, },
onAmoledThemeChange = { isAmoledThemeEnabled = themmoController.isAmoledThemeEnabled,
themmoController.enableAmoledTheme(it) onAmoledThemeChange = { newValue ->
viewModel.updateAmoledTheme(it) themmoController.enableAmoledTheme(newValue)
}, viewModel.updateAmoledTheme(newValue)
selectedColor = themmoController.currentCustomColor, },
onColorChange = { selectedColor = themmoController.currentCustomColor,
themmoController.setCustomColor(it) onColorChange = { newValue ->
viewModel.updateCustomColor(it) themmoController.setCustomColor(newValue)
}, viewModel.updateCustomColor(newValue)
monetMode = themmoController.currentMonetMode, },
onMonetModeChange = { monetMode = themmoController.currentMonetMode,
themmoController.setMonetMode(it) onMonetModeChange = { newValue ->
viewModel.updateMonetMode(it) themmoController.setMonetMode(newValue)
}, viewModel.updateMonetMode(newValue)
systemFont = prefs.value.systemFont, },
updateSystemFont = viewModel::updateSystemFont, systemFont = prefs.systemFont,
middleZero = prefs.value.middleZero, updateSystemFont = viewModel::updateSystemFont,
updateMiddleZero = viewModel::updateMiddleZero, middleZero = prefs.middleZero,
navigateToLanguages = navigateToLanguages updateMiddleZero = viewModel::updateMiddleZero,
) navigateToLanguages = navigateToLanguages
)
}
}
} }
@Composable @Composable

View File

@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.userprefs.DisplayPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.MonetMode import io.github.sadellie.themmo.MonetMode
@ -36,7 +35,7 @@ class DisplayViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
val prefs = userPrefsRepository.displayPrefs val prefs = userPrefsRepository.displayPrefs
.stateIn(viewModelScope, DisplayPreferences()) .stateIn(viewModelScope, null)
/** /**
* @see UserPreferencesRepository.updateThemingMode * @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.NavigateUpButton
import com.sadellie.unitto.core.ui.common.SegmentedButton import com.sadellie.unitto.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.UnittoSlider import com.sadellie.unitto.core.ui.common.UnittoSlider
@ -70,16 +71,19 @@ fun FormattingRoute(
viewModel: FormattingViewModel = hiltViewModel(), viewModel: FormattingViewModel = hiltViewModel(),
navigateUpAction: () -> Unit, navigateUpAction: () -> Unit,
) { ) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle() when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
FormattingScreen( else -> {
navigateUpAction = navigateUpAction, FormattingScreen(
uiState = uiState.value, navigateUpAction = navigateUpAction,
onPrecisionChange = viewModel::updatePrecision, uiState = uiState,
onSeparatorChange = viewModel::updateSeparator, onPrecisionChange = viewModel::updatePrecision,
onOutputFormatChange = viewModel::updateOutputFormat, onSeparatorChange = viewModel::updateSeparator,
togglePreview = viewModel::togglePreview onOutputFormatChange = viewModel::updateOutputFormat,
) togglePreview = viewModel::togglePreview
)
}
}
} }
@Composable @Composable

View File

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

View File

@ -23,8 +23,8 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.navDeepLink import androidx.navigation.navDeepLink
import com.sadellie.unitto.core.base.TopLevelDestinations 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.unittoNavigation
import com.sadellie.unitto.core.ui.unittoStackedComposable
import com.sadellie.unitto.feature.settings.SettingsRoute import com.sadellie.unitto.feature.settings.SettingsRoute
import com.sadellie.unitto.feature.settings.about.AboutRoute import com.sadellie.unitto.feature.settings.about.AboutRoute
import com.sadellie.unitto.feature.settings.calculator.CalculatorSettingsRoute import com.sadellie.unitto.feature.settings.calculator.CalculatorSettingsRoute
@ -68,14 +68,14 @@ fun NavGraphBuilder.settingGraph(
navDeepLink { uriPattern = "app://com.sadellie.unitto/$graph" } navDeepLink { uriPattern = "app://com.sadellie.unitto/$graph" }
) )
) { ) {
unittoComposable(start) { unittoStackedComposable(start) {
SettingsRoute( SettingsRoute(
navigateUp = navController::navigateUp, navigateUp = navController::navigateUp,
navControllerAction = navController::navigate navControllerAction = navController::navigate
) )
} }
unittoComposable(displayRoute) { unittoStackedComposable(displayRoute) {
DisplayRoute( DisplayRoute(
navigateUp = navController::navigateUp, navigateUp = navController::navigateUp,
themmoController = themmoController, themmoController = themmoController,
@ -83,51 +83,51 @@ fun NavGraphBuilder.settingGraph(
) )
} }
unittoComposable(languageRoute) { unittoStackedComposable(languageRoute) {
LanguageRoute( LanguageRoute(
navigateUp = navController::navigateUp, navigateUp = navController::navigateUp,
) )
} }
unittoComposable(startingScreenRoute) { unittoStackedComposable(startingScreenRoute) {
StartingScreenRoute( StartingScreenRoute(
navigateUp = navController::navigateUp, navigateUp = navController::navigateUp,
) )
} }
unittoComposable(formattingRoute) { unittoStackedComposable(formattingRoute) {
FormattingRoute( FormattingRoute(
navigateUpAction = navController::navigateUp navigateUpAction = navController::navigateUp
) )
} }
unittoComposable(calculatorSettingsRoute) { unittoStackedComposable(calculatorSettingsRoute) {
CalculatorSettingsRoute( CalculatorSettingsRoute(
navigateUpAction = navController::navigateUp, navigateUpAction = navController::navigateUp,
) )
} }
unittoComposable(converterSettingsRoute) { unittoStackedComposable(converterSettingsRoute) {
ConverterSettingsRoute( ConverterSettingsRoute(
navigateUpAction = navController::navigateUp, navigateUpAction = navController::navigateUp,
navigateToUnitsGroup = { navController.navigate(unitsGroupRoute) } navigateToUnitsGroup = { navController.navigate(unitsGroupRoute) }
) )
} }
unittoComposable(unitsGroupRoute) { unittoStackedComposable(unitsGroupRoute) {
UnitGroupsScreen( UnitGroupsScreen(
navigateUpAction = navController::navigateUp, navigateUpAction = navController::navigateUp,
) )
} }
unittoComposable(aboutRoute) { unittoStackedComposable(aboutRoute) {
AboutRoute( AboutRoute(
navigateUpAction = navController::navigateUp, navigateUpAction = navController::navigateUp,
navigateToThirdParty = { navController.navigate(thirdPartyRoute) } navigateToThirdParty = { navController.navigate(thirdPartyRoute) }
) )
} }
unittoComposable(thirdPartyRoute) { unittoStackedComposable(thirdPartyRoute) {
ThirdPartyLicensesScreen( ThirdPartyLicensesScreen(
navigateUpAction = navController::navigateUp, 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.base.TopLevelDestinations
import com.sadellie.unitto.core.ui.addShortcut import com.sadellie.unitto.core.ui.addShortcut
import com.sadellie.unitto.core.ui.common.NavigateUpButton 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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
@ -48,13 +49,16 @@ internal fun StartingScreenRoute(
viewModel: StartingScreenViewModel = hiltViewModel(), viewModel: StartingScreenViewModel = hiltViewModel(),
navigateUp: () -> Unit navigateUp: () -> Unit
) { ) {
val prefs = viewModel.prefs.collectAsStateWithLifecycle() when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
null -> UnittoEmptyScreen()
StartingScreenScreen( else -> {
startingScreen = prefs.value.startingScreen, StartingScreenScreen(
updateStartingScreen = viewModel::updateStartingScreen, startingScreen = prefs.startingScreen,
navigateUp = navigateUp updateStartingScreen = viewModel::updateStartingScreen,
) navigateUp = navigateUp
)
}
}
} }
@Composable @Composable

View File

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