From 32eb7422d5ad7a2ed3828d6be096bab27399f3d4 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Tue, 6 Feb 2024 18:07:25 +0300 Subject: [PATCH] Refactor UnitGroupsScreen --- .../sadellie/unitto/data/model/UnitGroup.kt | 9 +- .../repository/UserPreferencesRepository.kt | 4 + .../unitto/data/userprefs/PreferenceExt.kt | 65 ++++++---- .../unitto/data/userprefs/PreferenceModels.kt | 3 +- .../unitto/data/userprefs/PrefsKeys.kt | 2 + .../unitto/data/userprefs/UserPreferences.kt | 20 ++- .../feature/converter/components/ChipsRow.kt | 5 +- .../converter/ConverterSettingsScreen.kt | 4 +- .../settings/navigation/SettingsNavigation.kt | 4 +- .../unitgroups/UnitGroupsRepository .kt | 115 ------------------ .../settings/unitgroups/UnitGroupsScreen.kt | 103 ++++++++++++---- .../settings/unitgroups/UnitGroupsUIState.kt | 30 +++++ .../unitgroups/UnitGroupsViewModel.kt | 93 ++++++-------- 13 files changed, 219 insertions(+), 238 deletions(-) delete mode 100644 feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt create mode 100644 feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt diff --git a/data/model/src/main/java/com/sadellie/unitto/data/model/UnitGroup.kt b/data/model/src/main/java/com/sadellie/unitto/data/model/UnitGroup.kt index 03510dab..d49f79d1 100644 --- a/data/model/src/main/java/com/sadellie/unitto/data/model/UnitGroup.kt +++ b/data/model/src/main/java/com/sadellie/unitto/data/model/UnitGroup.kt @@ -21,17 +21,10 @@ package com.sadellie.unitto.data.model import androidx.annotation.StringRes import com.sadellie.unitto.core.base.R -val ALL_UNIT_GROUPS: List by lazy { - UnitGroup.entries -} - -/** - * As not all measurements can be converted between each other, we separate them into groups. - * Within one group all measurements can be converted - */ enum class UnitGroup( @StringRes val res: Int ) { + // NOTE: This order is used as default for new users LENGTH(res = R.string.unit_group_length), CURRENCY(res = R.string.unit_group_currency), MASS(res = R.string.unit_group_mass), diff --git a/data/model/src/main/java/com/sadellie/unitto/data/model/repository/UserPreferencesRepository.kt b/data/model/src/main/java/com/sadellie/unitto/data/model/repository/UserPreferencesRepository.kt index b8d3d74b..a88a5dbd 100644 --- a/data/model/src/main/java/com/sadellie/unitto/data/model/repository/UserPreferencesRepository.kt +++ b/data/model/src/main/java/com/sadellie/unitto/data/model/repository/UserPreferencesRepository.kt @@ -71,6 +71,10 @@ interface UserPreferencesRepository { suspend fun updateShownUnitGroups(shownUnitGroups: List) + suspend fun addShownUnitGroup(unitGroup: UnitGroup) + + suspend fun removeShownUnitGroup(unitGroup: UnitGroup) + suspend fun updateLastReadChangelog(value: String) suspend fun updateVibrations(enabled: Boolean) diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceExt.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceExt.kt index cb394150..39e5e744 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceExt.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceExt.kt @@ -23,90 +23,105 @@ import com.sadellie.unitto.core.base.OutputFormat import com.sadellie.unitto.core.base.Separator import com.sadellie.unitto.core.base.TopLevelDestinations import com.sadellie.unitto.data.converter.UnitID -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.core.MonetMode import io.github.sadellie.themmo.core.ThemingMode -fun Preferences.getEnableDynamicTheme(): Boolean { +internal fun Preferences.getEnableDynamicTheme(): Boolean { return this[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true } -fun Preferences.getThemingMode(): ThemingMode { +internal fun Preferences.getThemingMode(): ThemingMode { return this[PrefsKeys.THEMING_MODE] ?.letTryOrNull { ThemingMode.valueOf(it) } ?: ThemingMode.AUTO } -fun Preferences.getEnableAmoledTheme(): Boolean { +internal fun Preferences.getEnableAmoledTheme(): Boolean { return this[PrefsKeys.ENABLE_AMOLED_THEME] ?: false } -fun Preferences.getCustomColor(): Long { +internal fun Preferences.getCustomColor(): Long { return this[PrefsKeys.CUSTOM_COLOR] ?: 16L // From Color.Unspecified } -fun Preferences.getMonetMode(): MonetMode { +internal fun Preferences.getMonetMode(): MonetMode { return this[PrefsKeys.MONET_MODE] ?.letTryOrNull { MonetMode.valueOf(it) } ?: MonetMode.TonalSpot } -fun Preferences.getStartingScreen(): String { +internal fun Preferences.getStartingScreen(): String { return this[PrefsKeys.STARTING_SCREEN] ?: TopLevelDestinations.CALCULATOR_GRAPH } -fun Preferences.getEnableToolsExperiment(): Boolean { +internal fun Preferences.getEnableToolsExperiment(): Boolean { return this[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false } -fun Preferences.getSystemFont(): Boolean { +internal fun Preferences.getSystemFont(): Boolean { return this[PrefsKeys.SYSTEM_FONT] ?: false } -fun Preferences.getLastReadChangelog(): String { +internal fun Preferences.getLastReadChangelog(): String { return this[PrefsKeys.LAST_READ_CHANGELOG] ?: "" } -fun Preferences.getEnableVibrations(): Boolean { +internal fun Preferences.getEnableVibrations(): Boolean { return this[PrefsKeys.ENABLE_VIBRATIONS] ?: true } -fun Preferences.getRadianMode(): Boolean { +internal fun Preferences.getRadianMode(): Boolean { return this[PrefsKeys.RADIAN_MODE] ?: true } -fun Preferences.getSeparator(): Int { +internal fun Preferences.getSeparator(): Int { return this[PrefsKeys.SEPARATOR] ?: Separator.SPACE } -fun Preferences.getMiddleZero(): Boolean { +internal fun Preferences.getMiddleZero(): Boolean { return this[PrefsKeys.MIDDLE_ZERO] ?: true } -fun Preferences.getPartialHistoryView(): Boolean { +internal fun Preferences.getPartialHistoryView(): Boolean { return this[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true } -fun Preferences.getDigitsPrecision(): Int { +internal fun Preferences.getDigitsPrecision(): Int { return this[PrefsKeys.DIGITS_PRECISION] ?: 3 } -fun Preferences.getOutputFormat(): Int { +internal fun Preferences.getOutputFormat(): Int { return this[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN } -fun Preferences.getUnitConverterFormatTime(): Boolean { +internal fun Preferences.getUnitConverterFormatTime(): Boolean { return this[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false } -fun Preferences.getUnitConverterSorting(): UnitsListSorting { +internal fun Preferences.getUnitConverterSorting(): UnitsListSorting { return this[PrefsKeys.UNIT_CONVERTER_SORTING] ?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE } -fun Preferences.getShownUnitGroups(): List { +// TODO Remove when 80% users are on sets +internal fun Preferences.getShownUnitGroups(): List { +// Uncomment once legacy is gone +// return this[PrefsKeys.ENABLED_UNIT_GROUPS] +// ?.letTryOrNull { stringSet: Set -> +// stringSet.map { UnitGroup.valueOf(it) } +// } ?: UnitGroup.entries + + // Try new method + val unitGroups: List? = this[PrefsKeys.ENABLED_UNIT_GROUPS] + ?.letTryOrNull { stringSet: Set -> + stringSet.map { UnitGroup.valueOf(it) } + } + + if (!unitGroups.isNullOrEmpty()) return unitGroups + + // Failed to get from sets, try old method return this[PrefsKeys.SHOWN_UNIT_GROUPS] ?.letTryOrNull { list -> list @@ -114,23 +129,23 @@ fun Preferences.getShownUnitGroups(): List { .split(",") .map { UnitGroup.valueOf(it) } } - ?: ALL_UNIT_GROUPS + ?: UnitGroup.entries } -fun Preferences.getUnitConverterFavoritesOnly(): Boolean { +internal fun Preferences.getUnitConverterFavoritesOnly(): Boolean { return this[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] ?: false } -fun Preferences.getLatestLeftSide(): String { +internal fun Preferences.getLatestLeftSide(): String { return this[PrefsKeys.LATEST_LEFT_SIDE] ?: UnitID.kilometer } -fun Preferences.getLatestRightSide(): String { +internal fun Preferences.getLatestRightSide(): String { return this[PrefsKeys.LATEST_RIGHT_SIDE] ?: UnitID.mile } -fun Preferences.getAcButton(): Boolean { +internal fun Preferences.getAcButton(): Boolean { return this[PrefsKeys.AC_BUTTON] ?: true } diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceModels.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceModels.kt index d9b09e13..6b152cde 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceModels.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PreferenceModels.kt @@ -18,7 +18,6 @@ package com.sadellie.unitto.data.userprefs -import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitsListSorting import com.sadellie.unitto.data.model.userprefs.AboutPreferences @@ -90,7 +89,7 @@ data class FormattingPreferencesImpl( ) : FormattingPreferences data class UnitGroupsPreferencesImpl( - override val shownUnitGroups: List = ALL_UNIT_GROUPS, + override val shownUnitGroups: List = UnitGroup.entries, ) : UnitGroupsPreferences data class AddSubtractPreferencesImpl( diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PrefsKeys.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PrefsKeys.kt index 0c65ccce..9b70976d 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PrefsKeys.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/PrefsKeys.kt @@ -22,6 +22,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.core.stringSetPreferencesKey object PrefsKeys { // COMMON @@ -52,6 +53,7 @@ object PrefsKeys { 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 ENABLED_UNIT_GROUPS = stringSetPreferencesKey("ENABLED_UNIT_GROUPS_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") diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt index 92213e0e..56e20694 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt @@ -221,7 +221,25 @@ class UserPreferencesRepositoryImpl @Inject constructor( override suspend fun updateShownUnitGroups(shownUnitGroups: List) { dataStore.edit { preferences -> - preferences[PrefsKeys.SHOWN_UNIT_GROUPS] = shownUnitGroups.joinToString(",") + preferences[PrefsKeys.ENABLED_UNIT_GROUPS] = shownUnitGroups + .map { it.name } + .toSet() + } + } + + override suspend fun addShownUnitGroup(unitGroup: UnitGroup) { + dataStore.edit { preferences -> + preferences[PrefsKeys.ENABLED_UNIT_GROUPS] = preferences[PrefsKeys.ENABLED_UNIT_GROUPS] + ?.plus(unitGroup.name) + ?: emptySet() + } + } + + override suspend fun removeShownUnitGroup(unitGroup: UnitGroup) { + dataStore.edit { preferences -> + preferences[PrefsKeys.ENABLED_UNIT_GROUPS] = preferences[PrefsKeys.ENABLED_UNIT_GROUPS] + ?.minus(unitGroup.name) + ?: emptySet() } } diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/ChipsRow.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/ChipsRow.kt index 4d9b6e2a..55f54072 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/ChipsRow.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/ChipsRow.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.ui.common.AssistChip import com.sadellie.unitto.core.ui.common.FilterChip -import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS import com.sadellie.unitto.data.model.UnitGroup /** @@ -55,7 +54,7 @@ import com.sadellie.unitto.data.model.UnitGroup */ @Composable internal fun ChipsRow( - items: List = ALL_UNIT_GROUPS, + items: List = UnitGroup.entries, chosenUnitGroup: UnitGroup?, selectAction: (UnitGroup?) -> Unit, navigateToSettingsAction: () -> Unit, @@ -105,7 +104,7 @@ fun PreviewUnittoChips() { } ChipsRow( - items = ALL_UNIT_GROUPS, + items = UnitGroup.entries, chosenUnitGroup = selected, selectAction = { selectAction(it) }, navigateToSettingsAction = {}, diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/converter/ConverterSettingsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/converter/ConverterSettingsScreen.kt index ffd84316..7d462f30 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/converter/ConverterSettingsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/converter/ConverterSettingsScreen.kt @@ -41,7 +41,7 @@ import com.sadellie.unitto.core.ui.common.EmptyScreen import com.sadellie.unitto.core.ui.common.ListItem import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.ScaffoldWithLargeTopBar -import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS +import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitsListSorting import com.sadellie.unitto.data.model.userprefs.ConverterPreferences import com.sadellie.unitto.data.userprefs.ConverterPreferencesImpl @@ -139,7 +139,7 @@ private fun PreviewConverterSettingsScreen() { outputFormat = OutputFormat.PLAIN, unitConverterFormatTime = false, unitConverterSorting = UnitsListSorting.USAGE, - shownUnitGroups = ALL_UNIT_GROUPS, + shownUnitGroups = UnitGroup.entries, unitConverterFavoritesOnly = false, enableToolsExperiment = false, latestLeftSideUnit = "kilometer", diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt index fcb77b70..1229e1fe 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt @@ -34,7 +34,7 @@ import com.sadellie.unitto.feature.settings.formatting.FormattingRoute import com.sadellie.unitto.feature.settings.language.LanguageRoute import com.sadellie.unitto.feature.settings.startingscreen.StartingScreenRoute import com.sadellie.unitto.feature.settings.thirdparty.ThirdPartyLicensesScreen -import com.sadellie.unitto.feature.settings.unitgroups.UnitGroupsScreen +import com.sadellie.unitto.feature.settings.unitgroups.UnitGroupsRoute import io.github.sadellie.themmo.ThemmoController private val graph = DrawerItem.Settings.graph @@ -115,7 +115,7 @@ fun NavGraphBuilder.settingGraph( } unittoStackedComposable(unitsGroupRoute) { - UnitGroupsScreen( + UnitGroupsRoute( navigateUpAction = navController::navigateUp, ) } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt deleted file mode 100644 index e4d258ae..00000000 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsRepository .kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Unitto is a calculator for Android - * Copyright (c) 2023-2024 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 . - */ - -package com.sadellie.unitto.feature.settings.unitgroups - -import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS -import com.sadellie.unitto.data.model.UnitGroup -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.burnoutcrew.reorderable.ItemPosition -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Repository that holds information about shown and hidden [UnitGroup]s and provides methods to - * show/hide [UnitGroup]s. - */ -@Singleton -class UnitGroupsRepository @Inject constructor() { - - /** - * Mutex is need needed because we work with flow (sync stuff). - */ - private val mutex = Mutex() - - /** - * Currently shown [UnitGroup]s. - */ - var shownUnitGroups = MutableStateFlow(listOf()) - private set - - /** - * Currently hidden [UnitGroup]s. - */ - var hiddenUnitGroups = MutableStateFlow(listOf()) - private set - - /** - * Sets [shownUnitGroups] and updates [hiddenUnitGroups] as a side effect. [hiddenUnitGroups] is - * everything from [ALL_UNIT_GROUPS] that was not in [shownUnitGroups]. - * - * @param list List of [UnitGroup]s that need to be shown. - */ - suspend fun updateShownGroups(list: List) { - mutex.withLock { - shownUnitGroups.value = list - hiddenUnitGroups.value = ALL_UNIT_GROUPS - list.toSet() - } - } - - /** - * Moves [UnitGroup] from [shownUnitGroups] to [hiddenUnitGroups] - * - * @param unitGroup [UnitGroup] to hide. - */ - suspend fun markUnitGroupAsHidden(unitGroup: UnitGroup) { - mutex.withLock { - shownUnitGroups.value = shownUnitGroups.value - unitGroup - // Newly hidden unit will appear at the top of the list - hiddenUnitGroups.value = listOf(unitGroup) + hiddenUnitGroups.value - } - } - - /** - * Moves [UnitGroup] from [hiddenUnitGroups] to [shownUnitGroups] - * - * @param unitGroup [UnitGroup] to show. - */ - suspend fun markUnitGroupAsShown(unitGroup: UnitGroup) { - mutex.withLock { - hiddenUnitGroups.value = hiddenUnitGroups.value - unitGroup - shownUnitGroups.value = shownUnitGroups.value + unitGroup - } - } - - /** - * Moves [UnitGroup] in [shownUnitGroups] from one index to another (reorder). - * - * @param from Position from which we need to move from - * @param to Position where to put [UnitGroup] - */ - suspend fun moveShownUnitGroups(from: ItemPosition, to: ItemPosition) { - mutex.withLock { - shownUnitGroups.value = shownUnitGroups.value.toMutableList().apply { - val initialIndex = shownUnitGroups.value.indexOfFirst { it == from.key } - /** - * No such item. Happens when dragging item and clicking "remove" while item is - * still being dragged. - */ - if (initialIndex == -1) return - - add( - shownUnitGroups.value.indexOfFirst { it == to.key }, - removeAt(initialIndex) - ) - } - } - } -} diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt index 29413f26..0cb62042 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsScreen.kt @@ -40,18 +40,23 @@ import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.ui.common.EmptyScreen import com.sadellie.unitto.core.ui.common.Header import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.ScaffoldWithLargeTopBar +import com.sadellie.unitto.data.model.UnitGroup import org.burnoutcrew.reorderable.ReorderableItem import org.burnoutcrew.reorderable.detectReorder import org.burnoutcrew.reorderable.detectReorderAfterLongPress @@ -59,22 +64,52 @@ import org.burnoutcrew.reorderable.rememberReorderableLazyListState import org.burnoutcrew.reorderable.reorderable @Composable -internal fun UnitGroupsScreen( +internal fun UnitGroupsRoute( viewModel: UnitGroupsViewModel = hiltViewModel(), navigateUpAction: () -> Unit, +) { + when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) { + UnitGroupsUIState.Loading -> EmptyScreen() + is UnitGroupsUIState.Ready -> UnitGroupsScreen( + uiState = uiState, + navigateUpAction = navigateUpAction, + updateShownUnitGroups = viewModel::updateShownUnitGroups, + addShownUnitGroup = viewModel::addShownUnitGroup, + removeShownUnitGroup = viewModel::removeShownUnitGroup, + ) + } +} + +@Composable +private fun UnitGroupsScreen( + uiState: UnitGroupsUIState.Ready, + navigateUpAction: () -> Unit, + updateShownUnitGroups: (List) -> Unit, + addShownUnitGroup: (UnitGroup) -> Unit, + removeShownUnitGroup: (UnitGroup) -> Unit, ) { ScaffoldWithLargeTopBar( title = stringResource(R.string.settings_unit_groups_title), navigationIcon = { NavigateUpButton(navigateUpAction) } ) { paddingValues -> - - val shownUnits = viewModel.shownUnitGroups.collectAsState() - val hiddenUnits = viewModel.hiddenUnitGroups.collectAsState() - + val copiedShownList = rememberUpdatedState(uiState.shownUnitGroups) as MutableState val state = rememberReorderableLazyListState( - onMove = viewModel::onMove, - canDragOver = { from, _ -> viewModel.canDragOver(from) }, - onDragEnd = { _, _ -> viewModel.onDragEnd() } + onMove = { from, to -> + copiedShownList.value = copiedShownList.value + .toMutableList() + .apply { + // -1 for list header + add(to.index - 1, removeAt(from.index - 1)) + } + }, + canDragOver = { draggedOver, _ -> + // offset by 1 for list header + draggedOver.index in 1..(copiedShownList.value.lastIndex + 1) + }, + onDragEnd = onDragEnd@{ from, to -> + if (from == to) return@onDragEnd + updateShownUnitGroups(copiedShownList.value) + } ) LazyColumn( @@ -90,12 +125,18 @@ internal fun UnitGroupsScreen( ) } - items(shownUnits.value, { it }) { item -> + items(copiedShownList.value, { it }) { item -> ReorderableItem(state, key = item) { isDragging -> val transition = updateTransition(isDragging, label = "draggedTransition") val background by transition.animateColor(label = "background") { if (it) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface } + val textColor by transition.animateColor(label = "background") { + if (it) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface + } + val iconColor by transition.animateColor(label = "background") { + if (it) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.outline + } val itemPadding by transition.animateDp(label = "itemPadding") { if (it) 16.dp else 0.dp } @@ -105,28 +146,29 @@ internal fun UnitGroupsScreen( modifier = Modifier .padding(horizontal = itemPadding) .clip(CircleShape) - .clickable { viewModel.hideUnitGroup(item) } + .clickable { removeShownUnitGroup(item) } .detectReorderAfterLongPress(state), colors = ListItemDefaults.colors( - containerColor = background + containerColor = background, + headlineColor = textColor, + leadingIconColor = iconColor, + trailingIconColor = iconColor, ), leadingContent = { Icon( - Icons.Default.RemoveCircle, - stringResource(R.string.settings_disable_unit_group_description), - tint = MaterialTheme.colorScheme.outline, + imageVector = Icons.Default.RemoveCircle, + contentDescription = stringResource(R.string.settings_disable_unit_group_description), modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(false), - onClick = { viewModel.hideUnitGroup(item) } + onClick = { removeShownUnitGroup(item) } ) ) }, trailingContent = { Icon( - Icons.Default.DragHandle, - stringResource(R.string.settings_reorder_unit_group_description), - tint = MaterialTheme.colorScheme.outline, + imageVector = Icons.Default.DragHandle, + contentDescription = stringResource(R.string.settings_reorder_unit_group_description), modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, @@ -148,11 +190,11 @@ internal fun UnitGroupsScreen( ) } - items(hiddenUnits.value, { it }) { + items(uiState.hiddenUnitGroups, { it }) { ListItem( modifier = Modifier .background(MaterialTheme.colorScheme.surface) - .clickable { viewModel.returnUnitGroup(it) } + .clickable { addShownUnitGroup(it) } .animateItemPlacement(), headlineContent = { Text(stringResource(it.res)) }, trailingContent = { @@ -163,7 +205,7 @@ internal fun UnitGroupsScreen( modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(false), - onClick = { viewModel.returnUnitGroup(it) } + onClick = { addShownUnitGroup(it) } ) ) } @@ -172,3 +214,20 @@ internal fun UnitGroupsScreen( } } } + +@Preview +@Composable +private fun PreviewUnitGroupsScreen() { + val shownUnitGroups = UnitGroup.entries.take(4) + + UnitGroupsScreen( + uiState = UnitGroupsUIState.Ready( + shownUnitGroups = shownUnitGroups, + hiddenUnitGroups = UnitGroup.entries - shownUnitGroups.toSet() + ), + navigateUpAction = {}, + updateShownUnitGroups = {}, + addShownUnitGroup = {}, + removeShownUnitGroup = {}, + ) +} diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt new file mode 100644 index 00000000..08d1a1b1 --- /dev/null +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsUIState.kt @@ -0,0 +1,30 @@ +/* + * Unitto is a calculator for Android + * Copyright (c) 2024 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 . + */ + +package com.sadellie.unitto.feature.settings.unitgroups + +import com.sadellie.unitto.data.model.UnitGroup + +internal sealed class UnitGroupsUIState { + data object Loading: UnitGroupsUIState() + + data class Ready( + val shownUnitGroups: List, + val hiddenUnitGroups: List + ): UnitGroupsUIState() +} diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt index 47245de9..bd8e8285 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/unitgroups/UnitGroupsViewModel.kt @@ -20,75 +20,52 @@ package com.sadellie.unitto.feature.settings.unitgroups import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.repository.UserPreferencesRepository import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import org.burnoutcrew.reorderable.ItemPosition import javax.inject.Inject @HiltViewModel -class UnitGroupsViewModel @Inject constructor( +internal class UnitGroupsViewModel @Inject constructor( private val userPrefsRepository: UserPreferencesRepository, - private val unitGroupsRepository: UnitGroupsRepository, ) : ViewModel() { - val shownUnitGroups = unitGroupsRepository.shownUnitGroups - val hiddenUnitGroups = unitGroupsRepository.hiddenUnitGroups - /** - * @see UnitGroupsRepository.markUnitGroupAsHidden - * @see UserPreferencesRepository.updateShownUnitGroups - */ - fun hideUnitGroup(unitGroup: UnitGroup) { - viewModelScope.launch { - unitGroupsRepository.markUnitGroupAsHidden(unitGroup) - userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) - } - } - - /** - * @see UnitGroupsRepository.markUnitGroupAsShown - * @see UserPreferencesRepository.updateShownUnitGroups - */ - fun returnUnitGroup(unitGroup: UnitGroup) { - viewModelScope.launch { - unitGroupsRepository.markUnitGroupAsShown(unitGroup) - userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) - } - } - - /** - * @see UnitGroupsRepository.moveShownUnitGroups - */ - fun onMove(from: ItemPosition, to: ItemPosition) { - viewModelScope.launch { - unitGroupsRepository.moveShownUnitGroups(from, to) - } - } - - /** - * @see UserPreferencesRepository.updateShownUnitGroups - */ - fun onDragEnd() { - viewModelScope.launch { - userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) - } - } - - /** - * Prevent from dragging over non-draggable items (headers and hidden) - * - * @param pos Position we are dragging over. - * @return True if can drag over given item. - */ - fun canDragOver(pos: ItemPosition) = shownUnitGroups.value.any { it == pos.key } - - init { - viewModelScope.launch { - unitGroupsRepository.updateShownGroups( - userPrefsRepository.unitGroupsPrefs.first().shownUnitGroups + val uiState = userPrefsRepository.unitGroupsPrefs + .map { + UnitGroupsUIState.Ready( + shownUnitGroups = it.shownUnitGroups, + hiddenUnitGroups = UnitGroup.entries - it.shownUnitGroups.toSet() ) } + .stateIn(viewModelScope, UnitGroupsUIState.Loading) + + /** + * @see UserPreferencesRepository.removeShownUnitGroup + */ + fun removeShownUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + userPrefsRepository.removeShownUnitGroup(unitGroup) + } + } + + /** + * @see UserPreferencesRepository.addShownUnitGroup + */ + fun addShownUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + userPrefsRepository.addShownUnitGroup(unitGroup) + } + } + + /** + * @see UserPreferencesRepository.updateShownUnitGroups + */ + fun updateShownUnitGroups(unitGroups: List) { + viewModelScope.launch { + userPrefsRepository.updateShownUnitGroups(unitGroups) + } } }