From e3c1c5376f17d5da53dd1890db82608ca56e7318 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Fri, 12 Aug 2022 20:31:39 +0300 Subject: [PATCH] Personal UnitGroups list User can now reorder/add/hide unit groups. Lots of stuff were implemented in a very strange/ugly way, but will be fixed in later commits. --- app/build.gradle.kts | 3 + .../java/com/sadellie/unitto/MainActivity.kt | 13 ++- .../com/sadellie/unitto/data/NavRoutes.kt | 1 + .../data/preferences/UserPreferences.kt | 31 ++++- .../unitto/data/units/AllUnitsRepository.kt | 9 +- .../unitto/data/units/UnitGroupsRepository.kt | 106 +++++++++++++++++ .../unitto/screens/second/SecondScreen.kt | 1 + .../screens/second/SecondScreenUIState.kt | 2 + .../unitto/screens/second/SecondViewModel.kt | 15 ++- .../screens/second/components/Header.kt | 5 +- .../screens/setttings/SettingsScreen.kt | 40 ++++--- .../screens/setttings/SettingsViewModel.kt | 52 +++++++++ .../screens/setttings/UnitGroupsScreen.kt | 107 ++++++++++++++++++ .../setttings/components/SettingsListItem.kt | 12 +- app/src/main/res/values-en-rGB/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 17 files changed, 370 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/sadellie/unitto/data/units/UnitGroupsRepository.kt create mode 100644 app/src/main/java/com/sadellie/unitto/screens/setttings/UnitGroupsScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 734a7519..71a25024 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -194,4 +194,7 @@ dependencies { // Themmo implementation("com.github.sadellie:themmo:0.0.3") + + // ComposeReorderable + implementation("org.burnoutcrew.composereorderable:reorderable:0.9.2") } \ No newline at end of file diff --git a/app/src/main/java/com/sadellie/unitto/MainActivity.kt b/app/src/main/java/com/sadellie/unitto/MainActivity.kt index ddd494e8..c6efa1b4 100644 --- a/app/src/main/java/com/sadellie/unitto/MainActivity.kt +++ b/app/src/main/java/com/sadellie/unitto/MainActivity.kt @@ -40,15 +40,17 @@ import com.sadellie.unitto.data.NavRoutes.RIGHT_LIST_SCREEN import com.sadellie.unitto.data.NavRoutes.SETTINGS_GRAPH import com.sadellie.unitto.data.NavRoutes.SETTINGS_SCREEN import com.sadellie.unitto.data.NavRoutes.THEMES_SCREEN +import com.sadellie.unitto.data.NavRoutes.UNIT_GROUPS_SCREEN import com.sadellie.unitto.screens.MainViewModel -import com.sadellie.unitto.screens.setttings.SettingsViewModel -import com.sadellie.unitto.screens.setttings.AboutScreen import com.sadellie.unitto.screens.main.MainScreen import com.sadellie.unitto.screens.second.LeftSideScreen import com.sadellie.unitto.screens.second.RightSideScreen import com.sadellie.unitto.screens.second.SecondViewModel +import com.sadellie.unitto.screens.setttings.AboutScreen import com.sadellie.unitto.screens.setttings.SettingsScreen +import com.sadellie.unitto.screens.setttings.SettingsViewModel import com.sadellie.unitto.screens.setttings.ThemesScreen +import com.sadellie.unitto.screens.setttings.UnitGroupsScreen import com.sadellie.unitto.ui.theme.AppTypography import com.sadellie.unitto.ui.theme.DarkThemeColors import com.sadellie.unitto.ui.theme.LightThemeColors @@ -168,5 +170,12 @@ private fun NavGraphBuilder.settingGraph( composable(ABOUT_SCREEN) { AboutScreen(navigateUpAction = { navController.navigateUp() }) } + + composable(UNIT_GROUPS_SCREEN) { + UnitGroupsScreen( + viewModel = settingsViewModel, + navigateUpAction = { navController.navigateUp() } + ) + } } } diff --git a/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt b/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt index 46858f29..1bdf4724 100644 --- a/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt +++ b/app/src/main/java/com/sadellie/unitto/data/NavRoutes.kt @@ -31,4 +31,5 @@ object NavRoutes { const val SETTINGS_SCREEN = "SettingsScreen" const val THEMES_SCREEN = "ThemesScreen" const val ABOUT_SCREEN = "AboutScreen" + const val UNIT_GROUPS_SCREEN = "UnitGroupScreen" } diff --git a/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt b/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt index 7daaaefc..96c5c3a4 100644 --- a/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt +++ b/app/src/main/java/com/sadellie/unitto/data/preferences/UserPreferences.kt @@ -18,6 +18,8 @@ package com.sadellie.unitto.data.preferences +import android.text.TextUtils.split +import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -25,8 +27,10 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey +import com.sadellie.unitto.data.units.ALL_UNIT_GROUPS import com.sadellie.unitto.data.units.AbstractUnit import com.sadellie.unitto.data.units.MyUnitIDS +import com.sadellie.unitto.data.units.UnitGroup import io.github.sadellie.themmo.ThemingMode import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -47,6 +51,7 @@ import javax.inject.Inject * @property latestLeftSideUnit Latest [AbstractUnit] that was on the left side * @property latestRightSideUnit Latest [AbstractUnit] that was on the right side * @property enableAnalytics Whether or not user wants to share application usage data + * @property shownUnitGroups [UnitGroup]s that user wants to see. Excludes other [UnitGroup]s */ data class UserPreferences( val themingMode: ThemingMode? = null, @@ -57,7 +62,8 @@ data class UserPreferences( val outputFormat: Int = OutputFormat.PLAIN, val latestLeftSideUnit: String = MyUnitIDS.kilometer, val latestRightSideUnit: String = MyUnitIDS.mile, - val enableAnalytics: Boolean = true + val enableAnalytics: Boolean = true, + val shownUnitGroups: List = ALL_UNIT_GROUPS ) /** @@ -77,6 +83,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS val LATEST_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY") val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY") val ENABLE_ANALYTICS = booleanPreferencesKey("ENABLE_ANALYTICS_PREF_KEY") + val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY") } val userPreferencesFlow: Flow = dataStore.data @@ -107,6 +114,19 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile val enableAnalytics: Boolean = preferences[PrefsKeys.ENABLE_ANALYTICS] ?: true + val shownUnitGroups: List = + preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.let { list -> + // Everything is in hidden (nothing in shown) + list.ifEmpty { return@let listOf() } + + try { + list.split(",").map { UnitGroup.valueOf(it) } + } catch (e: Exception) { + // Bad thing happened, return null so all units will be shown + null + } + + } ?: ALL_UNIT_GROUPS UserPreferences( themingMode = themingMode, @@ -117,7 +137,8 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS outputFormat = outputFormat, latestLeftSideUnit = latestLeftSideUnit, latestRightSideUnit = latestRightSideUnit, - enableAnalytics = enableAnalytics + enableAnalytics = enableAnalytics, + shownUnitGroups = shownUnitGroups ) } @@ -211,4 +232,10 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS preferences[PrefsKeys.ENABLE_AMOLED_THEME] = enabled } } + + suspend fun updateShownUnitGroups(shownUnitGroups: List) { + dataStore.edit { preferences -> + preferences[PrefsKeys.SHOWN_UNIT_GROUPS] = shownUnitGroups.joinToString(",") + } + } } diff --git a/app/src/main/java/com/sadellie/unitto/data/units/AllUnitsRepository.kt b/app/src/main/java/com/sadellie/unitto/data/units/AllUnitsRepository.kt index 5aa8c0fa..bd329980 100644 --- a/app/src/main/java/com/sadellie/unitto/data/units/AllUnitsRepository.kt +++ b/app/src/main/java/com/sadellie/unitto/data/units/AllUnitsRepository.kt @@ -96,16 +96,21 @@ class AllUnitsRepository @Inject constructor() { * @param favoritesOnly When True will filter only [AbstractUnit]s with [AbstractUnit.isFavorite] * set to True. * @param searchQuery When not empty, will search by [AbstractUnit.renderedName] using [sortByLev]. + * @param allUnitsGroups All [UnitGroup]s. Determines which units will be used for filtering. * @return Grouped by [UnitGroup] list of [AbstractUnit]s. */ fun filterUnits( hideBrokenCurrencies: Boolean, chosenUnitGroup: UnitGroup?, favoritesOnly: Boolean, - searchQuery: String + searchQuery: String, + allUnitsGroups: List ): Map> { + val shownUnits: List = + allUnitsGroups.flatMap { getCollectionByGroup(it) ?: listOf() } + var basicFilteredUnits: Sequence = - getCollectionByGroup(unitGroup = chosenUnitGroup)?.asSequence() ?: allUnits.asSequence() + getCollectionByGroup(unitGroup = chosenUnitGroup)?.asSequence() ?: shownUnits.asSequence() if (favoritesOnly) { basicFilteredUnits = basicFilteredUnits.filter { it.isFavorite } diff --git a/app/src/main/java/com/sadellie/unitto/data/units/UnitGroupsRepository.kt b/app/src/main/java/com/sadellie/unitto/data/units/UnitGroupsRepository.kt new file mode 100644 index 00000000..01d7f827 --- /dev/null +++ b/app/src/main/java/com/sadellie/unitto/data/units/UnitGroupsRepository.kt @@ -0,0 +1,106 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2022 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.data.units + +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 { + add( + shownUnitGroups.value.indexOfFirst { it == to.key }, + removeAt(shownUnitGroups.value.indexOfFirst { it == from.key }) + ) + } + } + } +} diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt index 34a69488..7f5cce46 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt @@ -151,6 +151,7 @@ fun LeftSideScreen( viewModel = viewModel, chipsRow = { unitGroup, lazyListState -> ChipsRow( + items = viewModel.uiState.shownUnitGroups, chosenUnitGroup = unitGroup, selectAction = { viewModel.toggleSelectedChip(it) diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreenUIState.kt b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreenUIState.kt index e006f157..48f9db37 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreenUIState.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreenUIState.kt @@ -27,11 +27,13 @@ import com.sadellie.unitto.data.units.UnitGroup * @property favoritesOnly Whether or not show only favorite [AbstractUnit]s. * @property unitsToShow Grouped list of [AbstractUnit]s. * @property searchQuery Search query in search bar. + * @property shownUnitGroups All [UnitGroup]s that can be seen in chips row * @property chosenUnitGroup Currently selected chip. Nul means that no chip is selected. */ data class SecondScreenUIState( val favoritesOnly: Boolean = false, val unitsToShow: Map> = emptyMap(), val searchQuery: String = "", + val shownUnitGroups: List = listOf(), val chosenUnitGroup: UnitGroup? = null ) diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/SecondViewModel.kt b/app/src/main/java/com/sadellie/unitto/screens/second/SecondViewModel.kt index 07ca8058..575a1989 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/SecondViewModel.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/SecondViewModel.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.viewModelScope import com.sadellie.unitto.data.units.AbstractUnit import com.sadellie.unitto.data.units.AllUnitsRepository import com.sadellie.unitto.data.units.UnitGroup +import com.sadellie.unitto.data.units.UnitGroupsRepository import com.sadellie.unitto.data.units.database.MyBasedUnit import com.sadellie.unitto.data.units.database.MyBasedUnitsRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -37,7 +38,8 @@ import javax.inject.Inject @HiltViewModel class SecondViewModel @Inject constructor( private val basedUnitRepository: MyBasedUnitsRepository, - private val allUnitsRepository: AllUnitsRepository + private val allUnitsRepository: AllUnitsRepository, + private val unitGroupsRepository: UnitGroupsRepository ) : ViewModel() { var uiState: SecondScreenUIState by mutableStateOf(SecondScreenUIState()) @@ -93,7 +95,8 @@ class SecondViewModel @Inject constructor( hideBrokenCurrencies = hideBrokenCurrencies, chosenUnitGroup = uiState.chosenUnitGroup, favoritesOnly = uiState.favoritesOnly, - searchQuery = uiState.searchQuery + searchQuery = uiState.searchQuery, + allUnitsGroups = uiState.shownUnitGroups ) uiState = uiState.copy(unitsToShow = unitsToShow) @@ -119,4 +122,12 @@ class SecondViewModel @Inject constructor( ) } } + + init { + viewModelScope.launch { + unitGroupsRepository.shownUnitGroups.collect { + uiState = uiState.copy(shownUnitGroups = it) + } + } + } } diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/components/Header.kt b/app/src/main/java/com/sadellie/unitto/screens/second/components/Header.kt index 1edd05dc..fe1e1058 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/components/Header.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/components/Header.kt @@ -31,11 +31,12 @@ import androidx.compose.ui.unit.dp * Unit group header. * * @param text Unit group name. + * @param modifier Modifier that will be applied to Text composable. */ @Composable -fun Header(text: String) { +fun Header(text: String, modifier: Modifier = Modifier) { Text( - modifier = Modifier + modifier = modifier .background(MaterialTheme.colorScheme.background) .fillMaxWidth() .padding(vertical = 12.dp, horizontal = 8.dp), diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt index 8790277f..d853c9a2 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsScreen.kt @@ -30,6 +30,7 @@ import com.sadellie.unitto.BuildConfig import com.sadellie.unitto.R import com.sadellie.unitto.data.NavRoutes.ABOUT_SCREEN import com.sadellie.unitto.data.NavRoutes.THEMES_SCREEN +import com.sadellie.unitto.data.NavRoutes.UNIT_GROUPS_SCREEN import com.sadellie.unitto.data.preferences.OUTPUT_FORMAT import com.sadellie.unitto.data.preferences.PRECISIONS import com.sadellie.unitto.data.preferences.SEPARATORS @@ -59,42 +60,49 @@ fun SettingsScreen( // GENERAL GROUP item { SettingsHeader(stringResource(R.string.general_settings_group)) } + // THEME + item { + SettingsListItem( + label = stringResource(id = R.string.unit_groups_setting) + ) { navControllerAction(UNIT_GROUPS_SCREEN) } + } + // PRECISION item { SettingsListItem( - stringResource(R.string.precision_setting), - stringResource(R.string.precision_setting_support) + label = stringResource(R.string.precision_setting), + supportText = stringResource(R.string.precision_setting_support) ) { dialogState = DialogState.PRECISION } } // SEPARATOR item { SettingsListItem( - stringResource(R.string.separator_setting), - stringResource(R.string.separator_setting_support) + label = stringResource(R.string.separator_setting), + supportText = stringResource(R.string.separator_setting_support) ) { dialogState = DialogState.SEPARATOR } } // OUTPUT FORMAT item { SettingsListItem( - stringResource(R.string.output_format_setting), - stringResource(R.string.output_format_setting_support) + label = stringResource(R.string.output_format_setting), + supportText = stringResource(R.string.output_format_setting_support) ) { dialogState = DialogState.OUTPUT_FORMAT } } // THEME item { SettingsListItem( - stringResource(R.string.theme_setting), - stringResource(R.string.theme_setting_support) + label = stringResource(R.string.theme_setting), + supportText = stringResource(R.string.theme_setting_support) ) { navControllerAction(THEMES_SCREEN) } } // CURRENCY RATE NOTE item { SettingsListItem( - stringResource(R.string.currency_rates_note_setting) + label = stringResource(R.string.currency_rates_note_setting) ) { dialogState = DialogState.CURRENCY_RATE } } @@ -104,14 +112,14 @@ fun SettingsScreen( // TERMS AND CONDITIONS item { SettingsListItem( - stringResource(R.string.terms_and_conditions) + label = stringResource(R.string.terms_and_conditions) ) { openLink(mContext, "http://sadellie.github.io/unitto/terms-app.html") } } // PRIVACY POLICY item { SettingsListItem( - stringResource(R.string.privacy_policy) + label = stringResource(R.string.privacy_policy) ) { openLink(mContext, "http://sadellie.github.io/unitto/privacy-app.html") } } @@ -119,9 +127,9 @@ fun SettingsScreen( if (BuildConfig.ANALYTICS) { item { SettingsListItem( - stringResource(R.string.send_usage_statistics), - stringResource(R.string.send_usage_statistics_support), - viewModel.userPrefs.enableAnalytics + label = stringResource(R.string.send_usage_statistics), + supportText = stringResource(R.string.send_usage_statistics_support), + switchState = viewModel.userPrefs.enableAnalytics ) { viewModel.updateEnableAnalytics(it) } } } @@ -129,7 +137,7 @@ fun SettingsScreen( // THIRD PARTY item { SettingsListItem( - stringResource(R.string.third_party_licenses) + label = stringResource(R.string.third_party_licenses) ) { navControllerAction(ABOUT_SCREEN) } } @@ -137,7 +145,7 @@ fun SettingsScreen( if (BuildConfig.STORE_LINK.isNotEmpty()) { item { SettingsListItem( - stringResource(R.string.rate_this_app) + label = stringResource(R.string.rate_this_app) ) { openLink(mContext, BuildConfig.STORE_LINK) } } } diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt index 76964760..0c26f1b1 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/SettingsViewModel.kt @@ -27,18 +27,25 @@ import androidx.lifecycle.viewModelScope import com.sadellie.unitto.FirebaseHelper import com.sadellie.unitto.data.preferences.UserPreferences import com.sadellie.unitto.data.preferences.UserPreferencesRepository +import com.sadellie.unitto.data.units.UnitGroup +import com.sadellie.unitto.data.units.UnitGroupsRepository import com.sadellie.unitto.screens.Formatter import dagger.hilt.android.lifecycle.HiltViewModel import io.github.sadellie.themmo.ThemingMode +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import org.burnoutcrew.reorderable.ItemPosition import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( private val userPrefsRepository: UserPreferencesRepository, + private val unitGroupsRepository: UnitGroupsRepository, private val application: Application, ) : ViewModel() { var userPrefs: UserPreferences by mutableStateOf(UserPreferences()) + val shownUnitGroups = unitGroupsRepository.shownUnitGroups + val hiddenUnitGroups = unitGroupsRepository.hiddenUnitGroups /** * @see [UserPreferencesRepository.updateThemingMode] @@ -104,8 +111,53 @@ class SettingsViewModel @Inject constructor( } } + /** + * See [UnitGroupsRepository.markUnitGroupAsHidden] and + * [UserPreferencesRepository.updateShownUnitGroups] + */ + fun hideUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + unitGroupsRepository.markUnitGroupAsHidden(unitGroup) + userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) + } + } + + /** + * See [UnitGroupsRepository.markUnitGroupAsShown] and + * [UserPreferencesRepository.updateShownUnitGroups] + */ + fun returnUnitGroup(unitGroup: UnitGroup) { + viewModelScope.launch { + unitGroupsRepository.markUnitGroupAsShown(unitGroup) + userPrefsRepository.updateShownUnitGroups(unitGroupsRepository.shownUnitGroups.value) + } + } + + /** + * See [UnitGroupsRepository.moveShownUnitGroups] and + * [UserPreferencesRepository.updateShownUnitGroups] + */ + fun onMove(from: ItemPosition, to: ItemPosition) { + viewModelScope.launch { + unitGroupsRepository.moveShownUnitGroups(from, to) + 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.userPreferencesFlow.first().shownUnitGroups + ) + userPrefsRepository.userPreferencesFlow.collect { userPrefs = it Formatter.setSeparator(it.separator) diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/UnitGroupsScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/UnitGroupsScreen.kt new file mode 100644 index 00000000..bc5726e7 --- /dev/null +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/UnitGroupsScreen.kt @@ -0,0 +1,107 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2022 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.screens.setttings + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sadellie.unitto.R +import com.sadellie.unitto.screens.common.UnittoLargeTopAppBar +import com.sadellie.unitto.screens.second.components.Header +import com.sadellie.unitto.screens.setttings.components.SettingsListItem +import org.burnoutcrew.reorderable.ReorderableItem +import org.burnoutcrew.reorderable.detectReorderAfterLongPress +import org.burnoutcrew.reorderable.rememberReorderableLazyListState +import org.burnoutcrew.reorderable.reorderable + +@Composable +fun UnitGroupsScreen( + viewModel: SettingsViewModel, + navigateUpAction: () -> Unit +) { + UnittoLargeTopAppBar( + title = stringResource(R.string.unit_groups_setting), + navigateUpAction = navigateUpAction + ) { paddingValues -> + + val shownUnits = viewModel.shownUnitGroups.collectAsState() + val hiddenUnits = viewModel.hiddenUnitGroups.collectAsState() + + val state = rememberReorderableLazyListState( + onMove = viewModel::onMove, + canDragOver = viewModel::canDragOver + ) + + LazyColumn( + state = state.listState, + modifier = Modifier + .padding(paddingValues) + .reorderable(state) + .detectReorderAfterLongPress(state) + ) { + item(key = "enabled") { + Header(text = "Enabled") + } + + items(shownUnits.value, { it }) { item -> + ReorderableItem(state, key = item) { isDragging -> + val background = animateColorAsState( + if (isDragging) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface + ) + val cornerRadius = animateDpAsState(if (isDragging) 16.dp else 0.dp) + + SettingsListItem( + modifier = Modifier + .padding(horizontal = cornerRadius.value) + .clip(RoundedCornerShape(cornerRadius.value)) + .background(background.value), + label = stringResource(item.res), + onClick = { viewModel.hideUnitGroup(item) } + ) + } + } + + item(key = "disabled") { + Header( + text = "Disabled", + modifier = Modifier.animateItemPlacement() + ) + } + + items(hiddenUnits.value, { it }) { + SettingsListItem( + modifier = Modifier.animateItemPlacement(), + label = stringResource(it.res), + onClick = { viewModel.returnUnitGroup(it) } + ) + } + } + } +} diff --git a/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt b/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt index 05f0e2fd..10f3174a 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/setttings/components/SettingsListItem.kt @@ -59,6 +59,7 @@ import com.sadellie.unitto.R * This component can be easily modified if you provide additional component to it, * for example a switch or a checkbox. * + * @param modifier Modifier that will be applied to a Row. * @param label Main text. * @param supportText Text that is located below label. * @param onClick Action to perform when user clicks on this component (whole component is clickable). @@ -66,13 +67,14 @@ import com.sadellie.unitto.R */ @Composable private fun BasicSettingsListItem( + modifier: Modifier = Modifier, label: String, supportText: String? = null, onClick: () -> Unit = {}, content: @Composable RowScope.() -> Unit = {} ) { Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, @@ -111,16 +113,18 @@ private fun BasicSettingsListItem( /** * Represents one item in list on Settings screen. * + * @param modifier Modifier that will be applied to a Row. * @param label Main text. * @param supportText Text that is located below label. * @param onClick Action to perform when user clicks on this component (whole component is clickable). */ @Composable fun SettingsListItem( + modifier: Modifier = Modifier, label: String, supportText: String? = null, onClick: () -> Unit, -) = BasicSettingsListItem(label, supportText, onClick) +) = BasicSettingsListItem(modifier, label, supportText, onClick) /** * Represents one item in list on Settings screen. @@ -137,7 +141,7 @@ fun SettingsListItem( supportText: String? = null, switchState: Boolean, onSwitchChange: (Boolean) -> Unit -) = BasicSettingsListItem(label, supportText, { onSwitchChange(!switchState) }) { +) = BasicSettingsListItem(Modifier, label, supportText, { onSwitchChange(!switchState) }) { Switch(checked = switchState, onCheckedChange = { onSwitchChange(it) }) } @@ -158,7 +162,7 @@ fun SettingsListItem( allOptions: Map, selected: T, onSelectedChange: (T) -> Unit -) = BasicSettingsListItem(label, supportText, {}) { +) = BasicSettingsListItem(Modifier, label, supportText, {}) { var dropDownExpanded by rememberSaveable { mutableStateOf(false) } var currentOption by rememberSaveable { mutableStateOf(selected) } val dropDownRotation: Float by animateFloatAsState( diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index c396de67..6ced7c11 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -741,5 +741,6 @@ Colour theme Open or close drop down menu Hello! + Unit groups \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bf3bb775..4081e1c0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -676,5 +676,6 @@ Цветовая тема Открыть или закрыть меню Привет! + Группы величин \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d662a3e..9041485a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -940,6 +940,7 @@ Precision Separator Output format + Unit groups Wrong currency rates? Note Currency rates are updated daily. There\'s no real-time market monitoring in the app