Sorting settings for units list

- Favorites are now always first (except for search)

closes #32
This commit is contained in:
Sad Ellie 2023-04-04 20:51:46 +03:00
parent 82a4f4ad0a
commit 89036b6ea6
7 changed files with 117 additions and 16 deletions

View File

@ -1245,6 +1245,8 @@
<string name="enable_vibrations_support">Haptic feedback when clicking keyboard buttons</string>
<string name="format_time">Format time</string>
<string name="format_time_support">Example: Show 130 minutes as 2h 10m</string>
<string name="units_sorting">Units list sorting</string>
<string name="units_sorting_support">Change units order</string>
<string name="currency_rates_note_setting">Wrong currency rates?</string>
<string name="currency_rates_note_title">Note</string>
<string name="currency_rates_note_text">Currency rates are updated daily. There\'s no real-time market monitoring in the app</string>
@ -1303,6 +1305,12 @@
<string name="allow_engineering">Allow engineering</string>
<string name="force_engineering">Force engineering</string>
<!--Units list sorting-->
<string name="sort_by_usage">Usage</string>
<string name="sort_by_alphabetical">Alphabetical</string>
<string name="sort_by_scale_desc">Scale (Desc.)</string>
<string name="sort_by_scale_asc">Scale (Asc.)</string>
<!--Theme-->
<string name="theme_setting_support">App look and feel</string>
<string name="force_auto_mode">Auto</string>

View File

@ -0,0 +1,21 @@
/*
* 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.model
enum class UnitsListSorting { USAGE, ALPHABETICAL, SCALE_DESC, SCALE_ASC }

View File

@ -23,6 +23,7 @@ import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.AbstractUnit
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.sortByLev
import com.sadellie.unitto.data.units.collections.accelerationCollection
import com.sadellie.unitto.data.units.collections.angleCollection
@ -129,6 +130,8 @@ class AllUnitsRepository @Inject constructor() {
* 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.
* @param sorting Sorting mode from [UnitsListSorting]
*
* @return Grouped by [UnitGroup] list of [AbstractUnit]s.
*/
fun filterUnits(
@ -136,9 +139,11 @@ class AllUnitsRepository @Inject constructor() {
chosenUnitGroup: UnitGroup?,
favoritesOnly: Boolean,
searchQuery: String,
allUnitsGroups: List<UnitGroup>
allUnitsGroups: List<UnitGroup>,
sorting: UnitsListSorting = UnitsListSorting.USAGE
): Map<UnitGroup, List<AbstractUnit>> {
var basicFilteredUnits: Sequence<AbstractUnit> = if (chosenUnitGroup == null) {
// Leave only shown unit groups
var units: Sequence<AbstractUnit> = if (chosenUnitGroup == null) {
allUnits.filter { it.group in allUnitsGroups }
} else {
val collection = getCollectionByGroup(chosenUnitGroup)
@ -146,20 +151,27 @@ class AllUnitsRepository @Inject constructor() {
}.asSequence()
if (favoritesOnly) {
basicFilteredUnits = basicFilteredUnits.filter { it.isFavorite }
units = units.filter { it.isFavorite }
}
if (hideBrokenCurrencies) {
basicFilteredUnits = basicFilteredUnits.filter { it.isEnabled }
}
val unitsToShow = if (searchQuery.isEmpty()) {
// Query is empty, i.e. we want to see all units and they need to be sorted by usage
basicFilteredUnits.sortedByDescending { it.counter }
} else {
// We sort by popularity and Levenshtein distance (short and long name).
basicFilteredUnits.sortedByDescending { it.counter }.sortByLev(searchQuery)
units = units.filter { it.isEnabled }
}
return unitsToShow.groupBy { it.group }
units = when (sorting) {
UnitsListSorting.USAGE -> units.sortedByDescending { it.counter }
UnitsListSorting.ALPHABETICAL -> units.sortedBy { it.renderedName }
UnitsListSorting.SCALE_ASC -> units.sortedBy { it.basicUnit }
UnitsListSorting.SCALE_DESC -> units.sortedByDescending { it.basicUnit }
}
units = if (searchQuery.isEmpty()) {
units.sortedByDescending { it.isFavorite }
} else {
// For search we sort by popularity and Levenshtein distance (short and long name).
units.sortByLev(searchQuery)
}
return units.groupBy { it.group }
}
/**

View File

@ -31,6 +31,7 @@ import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
import com.sadellie.unitto.data.model.AbstractUnit
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.units.MyUnitIDS
import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.Flow
@ -56,7 +57,8 @@ import javax.inject.Inject
* @property enableToolsExperiment When true will enable experimental Tools screen.
* @property radianMode AngleMode in mxParser. When true - Radian, when False - Degree.
* @property unitConverterFavoritesOnly If true will show only units that are marked as favorite.
* @property unitConverterFormatTime If true will format time to be more human readable
* @property unitConverterFormatTime If true will format time to be more human readable.
* @property unitConverterSorting Units list sorting mode.
*/
data class UserPreferences(
val themingMode: ThemingMode? = null,
@ -74,6 +76,7 @@ data class UserPreferences(
val radianMode: Boolean = true,
val unitConverterFavoritesOnly: Boolean = false,
val unitConverterFormatTime: Boolean = false,
val unitConverterSorting: UnitsListSorting = UnitsListSorting.USAGE,
)
/**
@ -99,6 +102,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
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 userPreferencesFlow: Flow<UserPreferences> = dataStore.data
@ -146,6 +150,8 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val radianMode: Boolean = preferences[PrefsKeys.RADIAN_MODE] ?: true
val unitConverterFavoritesOnly: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] ?: false
val unitConverterFormatTime: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
val unitConverterSorting: UnitsListSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING]?.let { UnitsListSorting.valueOf(it) }
?: UnitsListSorting.USAGE
UserPreferences(
themingMode = themingMode,
@ -163,6 +169,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
radianMode = radianMode,
unitConverterFavoritesOnly = unitConverterFavoritesOnly,
unitConverterFormatTime = unitConverterFormatTime,
unitConverterSorting = unitConverterSorting
)
}
@ -317,4 +324,15 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = enabled
}
}
/**
* Update [UserPreferences.unitConverterSorting].
*
* @see UserPreferences.unitConverterSorting
*/
suspend fun updateUnitConverterSorting(sorting: UnitsListSorting) {
dataStore.edit { preferences ->
preferences[PrefsKeys.UNIT_CONVERTER_SORTING] = sorting.name
}
}
}

View File

@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.RateReview
import androidx.compose.material.icons.filled.Rule
import androidx.compose.material.icons.filled.Sort
import androidx.compose.material.icons.filled.Timer
import androidx.compose.material.icons.filled.Vibration
import androidx.compose.material.icons.filled._123
@ -51,9 +52,10 @@ import com.sadellie.unitto.core.base.TOP_LEVEL_DESTINATIONS
import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
import com.sadellie.unitto.feature.settings.navigation.themesRoute
@ -165,6 +167,21 @@ internal fun SettingsScreen(
)
}
// UNITS LIST SORTING
item {
ListItem(
leadingContent = {
Icon(
Icons.Default.Sort,
stringResource(R.string.units_sorting)
)
},
headlineText = { Text(stringResource(R.string.units_sorting)) },
supportingText = { Text(stringResource(R.string.units_sorting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.UNIT_LIST_SORTING }
)
}
// FORMAT TIME
item {
UnittoListItem(
@ -280,6 +297,20 @@ internal fun SettingsScreen(
dismissAction = { resetDialog() }
)
}
DialogState.UNIT_LIST_SORTING -> {
AlertDialogWithList(
title = stringResource(R.string.units_sorting),
listItems = mapOf(
UnitsListSorting.USAGE to R.string.sort_by_usage,
UnitsListSorting.ALPHABETICAL to R.string.sort_by_alphabetical,
UnitsListSorting.SCALE_DESC to R.string.sort_by_scale_desc,
UnitsListSorting.SCALE_ASC to R.string.sort_by_scale_asc,
),
selectedItemIndex = userPrefs.value.unitConverterSorting,
selectAction = viewModel::updateUnitConverterSorting,
dismissAction = { resetDialog() }
)
}
// Dismissing alert dialog
else -> {}
}
@ -289,5 +320,5 @@ internal fun SettingsScreen(
* All possible states for alert dialog that opens when user clicks on settings.
*/
private enum class DialogState {
NONE, PRECISION, SEPARATOR, OUTPUT_FORMAT, START_SCREEN
NONE, PRECISION, SEPARATOR, OUTPUT_FORMAT, START_SCREEN, UNIT_LIST_SORTING
}

View File

@ -22,6 +22,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.unitgroups.UnitGroupsRepository
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@ -177,6 +178,15 @@ class SettingsViewModel @Inject constructor(
}
}
/**
* @see UserPreferencesRepository.updateUnitConverterSorting
*/
fun updateUnitConverterSorting(sorting: UnitsListSorting) {
viewModelScope.launch {
userPrefsRepository.updateUnitConverterSorting(sorting)
}
}
/**
* Prevent from dragging over non-draggable items (headers and hidden)
*

View File

@ -134,7 +134,8 @@ class UnitsListViewModel @Inject constructor(
chosenUnitGroup = _chosenUnitGroup.value,
favoritesOnly = _userPrefs.value.unitConverterFavoritesOnly,
searchQuery = _searchQuery.value,
allUnitsGroups = _shownUnitGroups.value
allUnitsGroups = _shownUnitGroups.value,
sorting = _userPrefs.value.unitConverterSorting
)
_unitsToShow.update { unitsToShow }