Search: introduce weighted reordering (#272)
* Database: backend for weights & timestamps - Migration 21->22 - SearchableLaunchTimestampEntity.kt - `weight`-column in SavedSearchableEntity - Queries for sorting by and adjusting weight - Queries for sorting by recent use * move weightfactor access to favoritesRepository * reorder calls in SearchVM * no more datahoarding * add settings screen for ordering * ui fix, animations * move ordering settings out of own screen * remove unused localization * weight factors * larger factor for WeightFactor.High * cleanup * sort favorites by weight * icons for favorite screen * I hate coming up with descriptive strings * add default weight factor to settings migration * Add default values for search result ordering preferences * Favorites settings: change order of preferences * Change strings * Replace favorites variability slider with list preference * migration initial weight * Change labels * Include searchable weight column in backup --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
parent
003789f310
commit
2c9cba72a9
@ -25,7 +25,8 @@ fun SliderPreference(
|
|||||||
max: Float = 1f,
|
max: Float = 1f,
|
||||||
step: Float? = null,
|
step: Float? = null,
|
||||||
onValueChanged: (Float) -> Unit,
|
onValueChanged: (Float) -> Unit,
|
||||||
enabled: Boolean = true
|
enabled: Boolean = true,
|
||||||
|
label: (@Composable (Float) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
var sliderValue by remember(value) { mutableStateOf(value) }
|
var sliderValue by remember(value) { mutableStateOf(value) }
|
||||||
Row(
|
Row(
|
||||||
@ -72,16 +73,20 @@ fun SliderPreference(
|
|||||||
onValueChanged(sliderValue)
|
onValueChanged(sliderValue)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val decimalPlaces = -log(step ?: 0.01f, 10f)
|
if (label != null) {
|
||||||
val format = remember { DecimalFormat().apply {
|
label(sliderValue)
|
||||||
maximumFractionDigits = floor(decimalPlaces).toInt()
|
} else {
|
||||||
minimumFractionDigits = 0
|
val decimalPlaces = -log(step ?: 0.01f, 10f)
|
||||||
} }
|
val format = remember { DecimalFormat().apply {
|
||||||
Text(
|
maximumFractionDigits = floor(decimalPlaces).toInt()
|
||||||
modifier = Modifier.width(56.dp).padding(start = 24.dp),
|
minimumFractionDigits = 0
|
||||||
text = format.format(sliderValue),
|
} }
|
||||||
style = MaterialTheme.typography.titleSmall
|
Text(
|
||||||
)
|
modifier = Modifier.width(56.dp).padding(start = 24.dp),
|
||||||
|
text = format.format(sliderValue),
|
||||||
|
style = MaterialTheme.typography.titleSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +101,8 @@ fun SliderPreference(
|
|||||||
max: Int = 100,
|
max: Int = 100,
|
||||||
step: Int = 1,
|
step: Int = 1,
|
||||||
onValueChanged: (Int) -> Unit,
|
onValueChanged: (Int) -> Unit,
|
||||||
enabled: Boolean = true
|
enabled: Boolean = true,
|
||||||
|
label: (@Composable (Int) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
SliderPreference(
|
SliderPreference(
|
||||||
title = title,
|
title = title,
|
||||||
@ -108,6 +114,45 @@ fun SliderPreference(
|
|||||||
step = step.toFloat(),
|
step = step.toFloat(),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
onValueChanged(it.roundToInt())
|
onValueChanged(it.roundToInt())
|
||||||
|
},
|
||||||
|
label = if (label == null) null else {
|
||||||
|
{ label(it.roundToInt()) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified T: Enum<T>> SliderPreference(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
value: T,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
labels: List<EnumLocalization<T>>? = null,
|
||||||
|
crossinline onValueChanged: (T) -> Unit
|
||||||
|
) {
|
||||||
|
val values = labels?.map { it.value }?.toTypedArray() ?: enumValues()
|
||||||
|
SliderPreference(
|
||||||
|
title = title,
|
||||||
|
icon = icon,
|
||||||
|
value = values.indexOf(value),
|
||||||
|
min = 0,
|
||||||
|
max = values.size - 1,
|
||||||
|
step = 1,
|
||||||
|
onValueChanged = {
|
||||||
|
onValueChanged(values[it])
|
||||||
|
},
|
||||||
|
enabled = enabled,
|
||||||
|
label = if (labels == null) null else {
|
||||||
|
{
|
||||||
|
val idx = labels.indexOfFirst { l -> l.value == values[it] }
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.width(68.dp).padding(start = 12.dp),
|
||||||
|
text = if (idx != -1) labels[idx].label else "",
|
||||||
|
style = MaterialTheme.typography.titleSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias EnumLocalization<T> = ListPreferenceItem<T>
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import de.mm20.launcher2.favorites.SavedSearchableRankInfo
|
|||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchResultOrdering
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.Ordering
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.SearchService
|
import de.mm20.launcher2.search.SearchService
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
@ -136,14 +137,23 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
.sortedBy { (it as? SavableSearchable) }
|
.sortedBy { (it as? SavableSearchable) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val relevance =
|
val relevance =
|
||||||
if (query.isNotEmpty() && settings.searchBar.searchResultOrdering == SearchResultOrdering.Relevance) {
|
if (query.isEmpty()) {
|
||||||
favoritesRepository.sortByRelevance(
|
|
||||||
resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
|
||||||
).first()
|
|
||||||
} else {
|
|
||||||
emptyList()
|
emptyList()
|
||||||
|
} else {
|
||||||
|
val keys = resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
||||||
|
when (settings.resultOrdering.ordering) {
|
||||||
|
|
||||||
|
Ordering.LaunchCount -> favoritesRepository.sortByRelevance(
|
||||||
|
keys
|
||||||
|
).first()
|
||||||
|
|
||||||
|
Ordering.Weighted -> favoritesRepository.sortByWeight(
|
||||||
|
keys
|
||||||
|
).first()
|
||||||
|
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultsList = resultsList.sortedWith { a, b ->
|
resultsList = resultsList.sortedWith { a, b ->
|
||||||
|
|||||||
@ -10,10 +10,13 @@ import de.mm20.launcher2.favorites.FavoritesRepository
|
|||||||
import de.mm20.launcher2.icons.IconRepository
|
import de.mm20.launcher2.icons.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
import de.mm20.launcher2.search.data.LauncherApp
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
package de.mm20.launcher2.ui.settings.favorites
|
package de.mm20.launcher2.ui.settings.favorites
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.Insights
|
||||||
|
import androidx.compose.material.icons.rounded.Sort
|
||||||
|
import androidx.compose.material.icons.rounded.SwapVert
|
||||||
|
import androidx.compose.material.icons.rounded.TableRows
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@ -8,7 +14,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
@ -28,6 +36,7 @@ fun FavoritesSettingsScreen() {
|
|||||||
Preference(
|
Preference(
|
||||||
title = stringResource(R.string.menu_item_edit_favs),
|
title = stringResource(R.string.menu_item_edit_favs),
|
||||||
summary = stringResource(R.string.preference_edit_favorites_summary),
|
summary = stringResource(R.string.preference_edit_favorites_summary),
|
||||||
|
icon = Icons.Rounded.Sort,
|
||||||
onClick = {
|
onClick = {
|
||||||
showEditSheet = true
|
showEditSheet = true
|
||||||
}
|
}
|
||||||
@ -43,7 +52,8 @@ fun FavoritesSettingsScreen() {
|
|||||||
value = frequentlyUsed == true,
|
value = frequentlyUsed == true,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.setFrequentlyUsed(it)
|
viewModel.setFrequentlyUsed(it)
|
||||||
}
|
},
|
||||||
|
icon = Icons.Rounded.Insights
|
||||||
)
|
)
|
||||||
val frequentlyUsedRows by viewModel.frequentlyUsedRows.observeAsState(1)
|
val frequentlyUsedRows by viewModel.frequentlyUsedRows.observeAsState(1)
|
||||||
SliderPreference(
|
SliderPreference(
|
||||||
@ -54,7 +64,20 @@ fun FavoritesSettingsScreen() {
|
|||||||
max = 4,
|
max = 4,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.setFrequentlyUsedRows(it)
|
viewModel.setFrequentlyUsedRows(it)
|
||||||
}
|
},
|
||||||
|
icon = Icons.Rounded.TableRows
|
||||||
|
)
|
||||||
|
val searchResultWeightFactor by viewModel.searchResultWeightFactor.observeAsState(WeightFactor.Default)
|
||||||
|
ListPreference(
|
||||||
|
title = stringResource(R.string.preference_search_result_ordering_weight_factor),
|
||||||
|
icon = Icons.Rounded.SwapVert,
|
||||||
|
value = searchResultWeightFactor,
|
||||||
|
items = listOf(
|
||||||
|
stringResource(R.string.preference_search_result_ordering_weight_factor_low) to WeightFactor.Low,
|
||||||
|
stringResource(R.string.preference_search_result_ordering_weight_factor_default) to WeightFactor.Default,
|
||||||
|
stringResource(R.string.preference_search_result_ordering_weight_factor_high) to WeightFactor.High
|
||||||
|
),
|
||||||
|
onValueChanged = { viewModel.setSearchResultWeightFactor(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +90,8 @@ fun FavoritesSettingsScreen() {
|
|||||||
value = editButton == true,
|
value = editButton == true,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.setEditButton(it)
|
viewModel.setEditButton(it)
|
||||||
}
|
},
|
||||||
|
icon = Icons.Rounded.Edit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -53,4 +54,18 @@ class FavoritesSettingsScreenVM: ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val searchResultWeightFactor = dataStore.data.map { it.resultOrdering.weightFactor }.asLiveData()
|
||||||
|
fun setSearchResultWeightFactor(searchResultWeightFactor: WeightFactor) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setResultOrdering(
|
||||||
|
it.resultOrdering.toBuilder()
|
||||||
|
.setWeightFactor(searchResultWeightFactor)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -207,16 +207,17 @@ fun SearchSettingsScreen() {
|
|||||||
)
|
)
|
||||||
val searchResultOrdering by viewModel.searchResultOrdering.observeAsState()
|
val searchResultOrdering by viewModel.searchResultOrdering.observeAsState()
|
||||||
ListPreference(
|
ListPreference(
|
||||||
title = stringResource(R.string.preference_search_bar_ordering),
|
title = stringResource(R.string.preference_search_result_ordering),
|
||||||
value = searchResultOrdering,
|
|
||||||
icon = Icons.Rounded.Sort,
|
|
||||||
items = listOf(
|
items = listOf(
|
||||||
stringResource(R.string.preference_search_bar_ordering_alphabetic) to Settings.SearchBarSettings.SearchResultOrdering.Alphabetic,
|
stringResource(R.string.preference_search_result_ordering_alphabetic) to Settings.SearchResultOrderingSettings.Ordering.Alphabetic,
|
||||||
stringResource(R.string.preference_search_bar_ordering_relevance) to Settings.SearchBarSettings.SearchResultOrdering.Relevance
|
stringResource(R.string.preference_search_result_ordering_launch_count) to Settings.SearchResultOrderingSettings.Ordering.LaunchCount,
|
||||||
|
stringResource(R.string.preference_search_result_ordering_weighted) to Settings.SearchResultOrderingSettings.Ordering.Weighted
|
||||||
),
|
),
|
||||||
|
value = searchResultOrdering,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
if (it != null) viewModel.setSearchResultOrdering(it)
|
if (it != null) viewModel.setSearchResultOrdering(it)
|
||||||
}
|
},
|
||||||
|
icon = Icons.Rounded.Sort
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchResultOrdering
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -21,29 +22,22 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setFavorites(favorites: Boolean) {
|
fun setFavorites(favorites: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setFavorites(
|
||||||
.setFavorites(
|
it.favorites.toBuilder().setEnabled(favorites)
|
||||||
it.favorites.toBuilder()
|
).build()
|
||||||
.setEnabled(favorites)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val hasContactsPermission =
|
val hasContactsPermission = permissionsManager.hasPermission(PermissionGroup.Contacts).asLiveData()
|
||||||
permissionsManager.hasPermission(PermissionGroup.Contacts).asLiveData()
|
|
||||||
val contacts = dataStore.data.map { it.contactsSearch.enabled }.asLiveData()
|
val contacts = dataStore.data.map { it.contactsSearch.enabled }.asLiveData()
|
||||||
fun setContacts(contacts: Boolean) {
|
fun setContacts(contacts: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setContactsSearch(
|
||||||
.setContactsSearch(
|
it.contactsSearch.toBuilder().setEnabled(contacts)
|
||||||
it.contactsSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(contacts)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,18 +46,14 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
|
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasCalendarPermission =
|
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar).asLiveData()
|
||||||
permissionsManager.hasPermission(PermissionGroup.Calendar).asLiveData()
|
|
||||||
val calendar = dataStore.data.map { it.calendarSearch.enabled }.asLiveData()
|
val calendar = dataStore.data.map { it.calendarSearch.enabled }.asLiveData()
|
||||||
fun setCalendar(calendar: Boolean) {
|
fun setCalendar(calendar: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setCalendarSearch(
|
||||||
.setCalendarSearch(
|
it.calendarSearch.toBuilder().setEnabled(calendar)
|
||||||
it.calendarSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(calendar)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,12 +66,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setCalculator(calculator: Boolean) {
|
fun setCalculator(calculator: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setCalculatorSearch(
|
||||||
.setCalculatorSearch(
|
it.calculatorSearch.toBuilder().setEnabled(calculator)
|
||||||
it.calculatorSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(calculator)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,12 +77,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setUnitConverter(unitConverter: Boolean) {
|
fun setUnitConverter(unitConverter: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setUnitConverterSearch(
|
||||||
.setUnitConverterSearch(
|
it.unitConverterSearch.toBuilder().setEnabled(unitConverter)
|
||||||
it.unitConverterSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(unitConverter)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,12 +88,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setWikipedia(wikipedia: Boolean) {
|
fun setWikipedia(wikipedia: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setWikipediaSearch(
|
||||||
.setWikipediaSearch(
|
it.wikipediaSearch.toBuilder().setEnabled(wikipedia)
|
||||||
it.wikipediaSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(wikipedia)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,12 +99,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setWebsites(websites: Boolean) {
|
fun setWebsites(websites: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setWebsiteSearch(
|
||||||
.setWebsiteSearch(
|
it.websiteSearch.toBuilder().setEnabled(websites)
|
||||||
it.websiteSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(websites)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,12 +110,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setWebSearch(webSearch: Boolean) {
|
fun setWebSearch(webSearch: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setWebSearch(
|
||||||
.setWebSearch(
|
it.webSearch.toBuilder().setEnabled(webSearch)
|
||||||
it.webSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(webSearch)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,12 +121,9 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setAutoFocus(autoFocus: Boolean) {
|
fun setAutoFocus(autoFocus: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setSearchBar(
|
||||||
.setSearchBar(
|
it.searchBar.toBuilder().setAutoFocus(autoFocus)
|
||||||
it.searchBar.toBuilder()
|
).build()
|
||||||
.setAutoFocus(autoFocus)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,43 +132,32 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun setLaunchOnEnter(launchOnEnter: Boolean) {
|
fun setLaunchOnEnter(launchOnEnter: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setSearchBar(
|
||||||
.setSearchBar(
|
it.searchBar.toBuilder().setLaunchOnEnter(launchOnEnter)
|
||||||
it.searchBar.toBuilder()
|
).build()
|
||||||
.setLaunchOnEnter(launchOnEnter)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchResultOrdering = dataStore.data.map { it.searchBar.searchResultOrdering }.asLiveData()
|
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts).asLiveData()
|
||||||
fun setSearchResultOrdering(searchResultOrdering: SearchResultOrdering) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
dataStore.updateData {
|
|
||||||
it.toBuilder()
|
|
||||||
.setSearchBar(
|
|
||||||
it.searchBar.toBuilder()
|
|
||||||
.setSearchResultOrdering(searchResultOrdering)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val hasAppShortcutPermission =
|
|
||||||
permissionsManager.hasPermission(PermissionGroup.AppShortcuts).asLiveData()
|
|
||||||
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }.asLiveData()
|
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }.asLiveData()
|
||||||
fun setAppShortcuts(appShortcuts: Boolean) {
|
fun setAppShortcuts(appShortcuts: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder().setAppShortcutSearch(
|
||||||
.setAppShortcutSearch(
|
it.appShortcutSearch.toBuilder().setEnabled(appShortcuts)
|
||||||
it.appShortcutSearch.toBuilder()
|
).build()
|
||||||
.setEnabled(appShortcuts)
|
}
|
||||||
)
|
}
|
||||||
.build()
|
}
|
||||||
|
|
||||||
|
val searchResultOrdering = dataStore.data.map { it.resultOrdering.ordering }.asLiveData()
|
||||||
|
fun setSearchResultOrdering(searchResultOrdering: Settings.SearchResultOrderingSettings.Ordering) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder().setResultOrdering(
|
||||||
|
it.resultOrdering.toBuilder().setOrdering(searchResultOrdering)
|
||||||
|
).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,499 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 22,
|
||||||
|
"identityHash": "5d3853b609231cdcab5fb3c681d05ebb",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "forecasts",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `temperature` REAL NOT NULL, `minTemp` REAL NOT NULL, `maxTemp` REAL NOT NULL, `pressure` REAL NOT NULL, `humidity` REAL NOT NULL, `icon` INTEGER NOT NULL, `condition` TEXT NOT NULL, `clouds` INTEGER NOT NULL, `windSpeed` REAL NOT NULL, `windDirection` REAL NOT NULL, `rain` REAL NOT NULL, `snow` REAL NOT NULL, `night` INTEGER NOT NULL, `location` TEXT NOT NULL, `provider` TEXT NOT NULL, `providerUrl` TEXT NOT NULL, `rainProbability` INTEGER NOT NULL, `snowProbability` INTEGER NOT NULL, `updateTime` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "temperature",
|
||||||
|
"columnName": "temperature",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minTemp",
|
||||||
|
"columnName": "minTemp",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "maxTemp",
|
||||||
|
"columnName": "maxTemp",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pressure",
|
||||||
|
"columnName": "pressure",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "humidity",
|
||||||
|
"columnName": "humidity",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "icon",
|
||||||
|
"columnName": "icon",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "condition",
|
||||||
|
"columnName": "condition",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "clouds",
|
||||||
|
"columnName": "clouds",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "windSpeed",
|
||||||
|
"columnName": "windSpeed",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "windDirection",
|
||||||
|
"columnName": "windDirection",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "precipitation",
|
||||||
|
"columnName": "rain",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "snow",
|
||||||
|
"columnName": "snow",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "night",
|
||||||
|
"columnName": "night",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "location",
|
||||||
|
"columnName": "location",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "provider",
|
||||||
|
"columnName": "provider",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "providerUrl",
|
||||||
|
"columnName": "providerUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "precipProbability",
|
||||||
|
"columnName": "rainProbability",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "snowProbability",
|
||||||
|
"columnName": "snowProbability",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "updateTime",
|
||||||
|
"columnName": "updateTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Searchable",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `type` TEXT NOT NULL, `searchable` TEXT NOT NULL, `launchCount` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `hidden` INTEGER NOT NULL, `weight` REAL NOT NULL DEFAULT 0.0, PRIMARY KEY(`key`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "key",
|
||||||
|
"columnName": "key",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serializedSearchable",
|
||||||
|
"columnName": "searchable",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "launchCount",
|
||||||
|
"columnName": "launchCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pinPosition",
|
||||||
|
"columnName": "pinned",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hidden",
|
||||||
|
"columnName": "hidden",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weight",
|
||||||
|
"columnName": "weight",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Currency",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`symbol` TEXT NOT NULL, `value` REAL NOT NULL, `lastUpdate` INTEGER NOT NULL, PRIMARY KEY(`symbol`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "symbol",
|
||||||
|
"columnName": "symbol",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastUpdate",
|
||||||
|
"columnName": "lastUpdate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"symbol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Icons",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `packageName` TEXT, `activityName` TEXT, `drawable` TEXT, `extras` TEXT, `iconPack` TEXT NOT NULL, `name` TEXT, `themed` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "packageName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "activityName",
|
||||||
|
"columnName": "activityName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "drawable",
|
||||||
|
"columnName": "drawable",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "extras",
|
||||||
|
"columnName": "extras",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "iconPack",
|
||||||
|
"columnName": "iconPack",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "themed",
|
||||||
|
"columnName": "themed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "IconPack",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `packageName` TEXT NOT NULL, `version` TEXT NOT NULL, `scale` REAL NOT NULL, `themed` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "packageName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "version",
|
||||||
|
"columnName": "version",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scale",
|
||||||
|
"columnName": "scale",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "themed",
|
||||||
|
"columnName": "themed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"packageName"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Widget",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `data` TEXT NOT NULL, `height` INTEGER NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data",
|
||||||
|
"columnName": "data",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "height",
|
||||||
|
"columnName": "height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "label",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "CustomAttributes",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `type` TEXT NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "key",
|
||||||
|
"columnName": "key",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "SearchAction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT, `label` TEXT, `icon` INTEGER, `color` INTEGER, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data",
|
||||||
|
"columnName": "data",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "label",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "icon",
|
||||||
|
"columnName": "icon",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "color",
|
||||||
|
"columnName": "color",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customIcon",
|
||||||
|
"columnName": "customIcon",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "options",
|
||||||
|
"columnName": "options",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5d3853b609231cdcab5fb3c681d05ebb')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,6 @@ import androidx.room.Database
|
|||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import de.mm20.launcher2.database.entities.*
|
import de.mm20.launcher2.database.entities.*
|
||||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
import de.mm20.launcher2.database.migrations.Migration_10_11
|
||||||
@ -21,6 +20,7 @@ import de.mm20.launcher2.database.migrations.Migration_17_18
|
|||||||
import de.mm20.launcher2.database.migrations.Migration_18_19
|
import de.mm20.launcher2.database.migrations.Migration_18_19
|
||||||
import de.mm20.launcher2.database.migrations.Migration_19_20
|
import de.mm20.launcher2.database.migrations.Migration_19_20
|
||||||
import de.mm20.launcher2.database.migrations.Migration_20_21
|
import de.mm20.launcher2.database.migrations.Migration_20_21
|
||||||
|
import de.mm20.launcher2.database.migrations.Migration_21_22
|
||||||
import de.mm20.launcher2.database.migrations.Migration_6_7
|
import de.mm20.launcher2.database.migrations.Migration_6_7
|
||||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||||
@ -35,8 +35,8 @@ import de.mm20.launcher2.database.migrations.Migration_9_10
|
|||||||
IconPackEntity::class,
|
IconPackEntity::class,
|
||||||
WidgetEntity::class,
|
WidgetEntity::class,
|
||||||
CustomAttributeEntity::class,
|
CustomAttributeEntity::class,
|
||||||
SearchActionEntity::class,
|
SearchActionEntity::class
|
||||||
], version = 21, exportSchema = true
|
], version = 22, exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@ -104,6 +104,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration_18_19(),
|
Migration_18_19(),
|
||||||
Migration_19_20(),
|
Migration_19_20(),
|
||||||
Migration_20_21(),
|
Migration_20_21(),
|
||||||
|
Migration_21_22()
|
||||||
).build()
|
).build()
|
||||||
if (_instance == null) _instance = instance
|
if (_instance == null) _instance = instance
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
@Dao
|
@Dao
|
||||||
interface SearchDao {
|
interface SearchDao {
|
||||||
|
|
||||||
@Insert()
|
@Insert
|
||||||
fun insertAll(items: List<SavedSearchableEntity>)
|
fun insertAll(items: List<SavedSearchableEntity>)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
@ -19,12 +19,13 @@ interface SearchDao {
|
|||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insertAllReplaceExisting(items: List<SavedSearchableEntity>)
|
fun insertAllReplaceExisting(items: List<SavedSearchableEntity>)
|
||||||
|
|
||||||
|
@Query(
|
||||||
@Query("SELECT * FROM Searchable " +
|
"SELECT * FROM Searchable " +
|
||||||
"WHERE ((:manuallySorted AND pinned > 1) OR " +
|
"WHERE ((:manuallySorted AND pinned > 1) OR " +
|
||||||
"(:automaticallySorted AND pinned = 1) OR" +
|
"(:automaticallySorted AND pinned = 1) OR" +
|
||||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
|
)
|
||||||
fun getFavorites(
|
fun getFavorites(
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
@ -32,12 +33,14 @@ interface SearchDao {
|
|||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<SavedSearchableEntity>>
|
): Flow<List<SavedSearchableEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Searchable " +
|
@Query(
|
||||||
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) IN (:includeTypes) AND (" +
|
"SELECT * FROM Searchable " +
|
||||||
"(:manuallySorted AND pinned > 1) OR " +
|
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) IN (:includeTypes) AND (" +
|
||||||
"(:automaticallySorted AND pinned = 1) OR" +
|
"(:manuallySorted AND pinned > 1) OR " +
|
||||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
"(:automaticallySorted AND pinned = 1) OR" +
|
||||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||||
|
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
|
)
|
||||||
fun getFavoritesWithTypes(
|
fun getFavoritesWithTypes(
|
||||||
includeTypes: List<String>,
|
includeTypes: List<String>,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
@ -46,12 +49,14 @@ interface SearchDao {
|
|||||||
limit: Int,
|
limit: Int,
|
||||||
): Flow<List<SavedSearchableEntity>>
|
): Flow<List<SavedSearchableEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Searchable " +
|
@Query(
|
||||||
"WHERE `type` NOT IN (:excludeTypes) AND (" +
|
"SELECT * FROM Searchable " +
|
||||||
"(:manuallySorted AND pinned > 1) OR " +
|
"WHERE `type` NOT IN (:excludeTypes) AND (" +
|
||||||
"(:automaticallySorted AND pinned = 1) OR" +
|
"(:manuallySorted AND pinned > 1) OR " +
|
||||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
"(:automaticallySorted AND pinned = 1) OR" +
|
||||||
") ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||||
|
") ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||||
|
)
|
||||||
fun getFavoritesWithoutTypes(
|
fun getFavoritesWithoutTypes(
|
||||||
excludeTypes: List<String>,
|
excludeTypes: List<String>,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
@ -114,8 +119,10 @@ interface SearchDao {
|
|||||||
fun incrementExistingLaunchCount(key: String)
|
fun incrementExistingLaunchCount(key: String)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun incrementLaunchCount(item: SavedSearchableEntity) {
|
fun incrementLaunchCount(item: SavedSearchableEntity, alpha: Double) {
|
||||||
incrementExistingLaunchCount(item.key)
|
incrementExistingLaunchCount(item.key)
|
||||||
|
increaseWeightWhere(item.key, alpha)
|
||||||
|
reduceWeightExcept(item.key, alpha)
|
||||||
insertSkipExisting(item)
|
insertSkipExisting(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +147,18 @@ interface SearchDao {
|
|||||||
@Query("UPDATE Searchable SET `pinned` = 0")
|
@Query("UPDATE Searchable SET `pinned` = 0")
|
||||||
fun unpinAll()
|
fun unpinAll()
|
||||||
|
|
||||||
@Query("UPDATE Searchable Set `pinned` = 0, `launchCount` = 0 WHERE `key` = :key")
|
@Query("UPDATE Searchable SET `pinned` = 0, `launchCount` = 0 WHERE `key` = :key")
|
||||||
suspend fun resetPinStatusAndLaunchCounter(key: String)
|
suspend fun resetPinStatusAndLaunchCounter(key: String)
|
||||||
|
|
||||||
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinned DESC")
|
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinned DESC")
|
||||||
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
|
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) ORDER BY `weight` DESC, pinned DESC")
|
||||||
|
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
|
@Query("UPDATE Searchable SET `weight` = `weight` * (1.0 - :alpha) WHERE `key` != :key")
|
||||||
|
fun reduceWeightExcept(key: String, alpha: Double)
|
||||||
|
|
||||||
|
@Query("UPDATE Searchable SET `weight` = `weight` + :alpha * (1.0 - `weight`) WHERE `key` == :key")
|
||||||
|
fun increaseWeightWhere(key: String, alpha: Double)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,5 +11,6 @@ data class SavedSearchableEntity(
|
|||||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||||
var launchCount: Int,
|
var launchCount: Int,
|
||||||
@ColumnInfo(name = "pinned") var pinPosition: Int,
|
@ColumnInfo(name = "pinned") var pinPosition: Int,
|
||||||
var hidden: Boolean
|
var hidden: Boolean,
|
||||||
|
@ColumnInfo(defaultValue = "0.0") var weight: Double
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
package de.mm20.launcher2.database.migrations
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.database.getIntOrNull
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration_21_22: Migration(21, 22) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("""
|
||||||
|
ALTER TABLE `Searchable`
|
||||||
|
ADD `weight` DOUBLE NOT NULL DEFAULT 0.0
|
||||||
|
""")
|
||||||
|
|
||||||
|
database.query("""
|
||||||
|
SELECT MAX(`launchCount`)
|
||||||
|
FROM `Searchable`
|
||||||
|
""")
|
||||||
|
.runCatching {
|
||||||
|
|
||||||
|
if (!this.moveToFirst()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getIntOrNull(0)
|
||||||
|
?.run {
|
||||||
|
database.execSQL("""
|
||||||
|
UPDATE `Searchable`
|
||||||
|
SET `weight` = `launchCount` / $this
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
}.onFailure {
|
||||||
|
Log.e("Migration_21_22", "Setting default values for weight failed", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -574,7 +574,12 @@
|
|||||||
<string name="preference_layout_fixed_rotation">Feste Bildschirmausrichtung</string>
|
<string name="preference_layout_fixed_rotation">Feste Bildschirmausrichtung</string>
|
||||||
<string name="preference_layout_fixed_rotation_summary">Porträtmodus erzwingen</string>
|
<string name="preference_layout_fixed_rotation_summary">Porträtmodus erzwingen</string>
|
||||||
<string name="icon_pack_dynamic_colors">Dynamische Farben</string>
|
<string name="icon_pack_dynamic_colors">Dynamische Farben</string>
|
||||||
<string name="preference_search_bar_ordering">Anordnung der Suchergebnisse</string>
|
<string name="preference_search_result_ordering">Sortierung der Suchergebnisse</string>
|
||||||
<string name="preference_search_bar_ordering_alphabetic">Alphabetisch</string>
|
<string name="preference_search_result_ordering_alphabetic">Alphabetisch</string>
|
||||||
<string name="preference_search_bar_ordering_relevance">Relevanz</string>
|
<string name="preference_search_result_ordering_launch_count">Aufrufhäufigkeit</string>
|
||||||
|
<string name="preference_search_result_ordering_weighted">Dynamisch</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor">Ranking-Flexibilität</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_low">Stabil</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_default">Ausgewogen</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_high">Variabel</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -761,7 +761,12 @@
|
|||||||
<string name="gesture_failed_message">You have performed a \"%1$s\" gesture. This gesture is currently set to trigger a \"%2$s\" action. However, the action could not be performed for the following reason:</string>
|
<string name="gesture_failed_message">You have performed a \"%1$s\" gesture. This gesture is currently set to trigger a \"%2$s\" action. However, the action could not be performed for the following reason:</string>
|
||||||
<string name="missing_permission_accessibility_gesture_failed">The launcher\'s accessibility service needs to be enabled to perform this action.</string>
|
<string name="missing_permission_accessibility_gesture_failed">The launcher\'s accessibility service needs to be enabled to perform this action.</string>
|
||||||
<string name="missing_permission_accessibility_gesture_settings">This action requires the launcher\'s accessibility service to be enabled.</string>
|
<string name="missing_permission_accessibility_gesture_settings">This action requires the launcher\'s accessibility service to be enabled.</string>
|
||||||
<string name="preference_search_bar_ordering">Search-result order</string>
|
<string name="preference_search_result_ordering">Order of search results</string>
|
||||||
<string name="preference_search_bar_ordering_alphabetic">Alphabetic</string>
|
<string name="preference_search_result_ordering_alphabetic">Alphabetic</string>
|
||||||
<string name="preference_search_bar_ordering_relevance">Relevance</string>
|
<string name="preference_search_result_ordering_launch_count">Launch count</string>
|
||||||
|
<string name="preference_search_result_ordering_weighted">Dynamic</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor">Ranking flexibility</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_low">Stable</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_default">Balanced</string>
|
||||||
|
<string name="preference_search_result_ordering_weight_factor_high">Variable</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -183,6 +183,11 @@ fun createFactorySettings(context: Context): Settings {
|
|||||||
.setSwipeLeft(Settings.GestureSettings.GestureAction.None)
|
.setSwipeLeft(Settings.GestureSettings.GestureAction.None)
|
||||||
.setSwipeRight(Settings.GestureSettings.GestureAction.None)
|
.setSwipeRight(Settings.GestureSettings.GestureAction.None)
|
||||||
)
|
)
|
||||||
|
.setResultOrdering(
|
||||||
|
Settings.SearchResultOrderingSettings.newBuilder()
|
||||||
|
.setOrdering(Settings.SearchResultOrderingSettings.Ordering.Weighted)
|
||||||
|
.setWeightFactor(Settings.SearchResultOrderingSettings.WeightFactor.Default)
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.mm20.launcher2.preferences.migrations
|
package de.mm20.launcher2.preferences.migrations
|
||||||
|
|
||||||
import de.mm20.launcher2.preferences.Settings
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||||
|
|
||||||
class Migration_12_13: VersionedMigration(12, 13) {
|
class Migration_12_13: VersionedMigration(12, 13) {
|
||||||
override suspend fun applyMigrations(builder: Settings.Builder): Settings.Builder {
|
override suspend fun applyMigrations(builder: Settings.Builder): Settings.Builder {
|
||||||
@ -10,5 +11,10 @@ class Migration_12_13: VersionedMigration(12, 13) {
|
|||||||
.setDatePart(true)
|
.setDatePart(true)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
.setResultOrdering(
|
||||||
|
builder.resultOrdering.toBuilder()
|
||||||
|
.setWeightFactor(WeightFactor.Default)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,11 +223,6 @@ message Settings {
|
|||||||
}
|
}
|
||||||
SearchBarColors color = 3;
|
SearchBarColors color = 3;
|
||||||
bool launch_on_enter = 4;
|
bool launch_on_enter = 4;
|
||||||
enum SearchResultOrdering {
|
|
||||||
Alphabetic = 0;
|
|
||||||
Relevance = 1;
|
|
||||||
}
|
|
||||||
SearchResultOrdering search_result_ordering = 5;
|
|
||||||
}
|
}
|
||||||
SearchBarSettings search_bar = 20;
|
SearchBarSettings search_bar = 20;
|
||||||
|
|
||||||
@ -332,4 +327,20 @@ message Settings {
|
|||||||
string long_press_app = 10;
|
string long_press_app = 10;
|
||||||
}
|
}
|
||||||
GestureSettings gestures = 28;
|
GestureSettings gestures = 28;
|
||||||
|
|
||||||
|
message SearchResultOrderingSettings {
|
||||||
|
enum Ordering {
|
||||||
|
Alphabetic = 0;
|
||||||
|
LaunchCount = 1;
|
||||||
|
Weighted = 2;
|
||||||
|
}
|
||||||
|
Ordering ordering = 1;
|
||||||
|
enum WeightFactor {
|
||||||
|
Low = 0;
|
||||||
|
Default = 1;
|
||||||
|
High = 2;
|
||||||
|
}
|
||||||
|
WeightFactor weight_factor = 2;
|
||||||
|
}
|
||||||
|
SearchResultOrderingSettings result_ordering = 29;
|
||||||
}
|
}
|
||||||
@ -6,6 +6,8 @@ import de.mm20.launcher2.crashreporter.CrashReporter
|
|||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -58,6 +60,8 @@ interface FavoritesRepository {
|
|||||||
*/
|
*/
|
||||||
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
|
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove this item from the Searchable database
|
* Remove this item from the Searchable database
|
||||||
*/
|
*/
|
||||||
@ -94,6 +98,7 @@ interface FavoritesRepository {
|
|||||||
internal class FavoritesRepositoryImpl(
|
internal class FavoritesRepositoryImpl(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val database: AppDatabase,
|
private val database: AppDatabase,
|
||||||
|
private val dataStore: LauncherDataStore
|
||||||
) : FavoritesRepository, KoinComponent {
|
) : FavoritesRepository, KoinComponent {
|
||||||
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
@ -160,7 +165,8 @@ internal class FavoritesRepositoryImpl(
|
|||||||
searchable = searchable,
|
searchable = searchable,
|
||||||
launchCount = databaseItem?.launchCount ?: 0,
|
launchCount = databaseItem?.launchCount ?: 0,
|
||||||
pinPosition = 1,
|
pinPosition = 1,
|
||||||
hidden = false
|
hidden = false,
|
||||||
|
weight = databaseItem?.weight ?: 0.0
|
||||||
)
|
)
|
||||||
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
||||||
}
|
}
|
||||||
@ -189,7 +195,8 @@ internal class FavoritesRepositoryImpl(
|
|||||||
searchable = searchable,
|
searchable = searchable,
|
||||||
launchCount = databaseItem?.launchCount ?: 0,
|
launchCount = databaseItem?.launchCount ?: 0,
|
||||||
pinPosition = 0,
|
pinPosition = 0,
|
||||||
hidden = true
|
hidden = true,
|
||||||
|
weight = databaseItem?.weight ?: 0.0
|
||||||
)
|
)
|
||||||
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
||||||
}
|
}
|
||||||
@ -207,10 +214,16 @@ internal class FavoritesRepositoryImpl(
|
|||||||
override fun incrementLaunchCounter(searchable: SavableSearchable) {
|
override fun incrementLaunchCounter(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val item = SavedSearchable(searchable.key, searchable, 0, 0, false)
|
val weightFactor =
|
||||||
|
when (dataStore.data.map { it.resultOrdering.weightFactor }.firstOrNull()) {
|
||||||
|
WeightFactor.Low -> 0.1
|
||||||
|
WeightFactor.High -> 0.5
|
||||||
|
else -> 0.2
|
||||||
|
}
|
||||||
|
val item = SavedSearchable(searchable.key, searchable, 0, 0, false, 0.0)
|
||||||
item.toDatabaseEntity()?.let {
|
item.toDatabaseEntity()?.let {
|
||||||
database.searchDao()
|
database.searchDao()
|
||||||
.incrementLaunchCount(it)
|
.incrementLaunchCount(it, weightFactor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,6 +262,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
launchCount = 0,
|
launchCount = 0,
|
||||||
pinPosition = 0,
|
pinPosition = 0,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
weight = 0.0
|
||||||
).toDatabaseEntity() ?: return@withContext
|
).toDatabaseEntity() ?: return@withContext
|
||||||
database.searchDao().insertSkipExisting(entity)
|
database.searchDao().insertSkipExisting(entity)
|
||||||
}
|
}
|
||||||
@ -271,6 +285,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
launchCount = 0,
|
launchCount = 0,
|
||||||
pinPosition = 0,
|
pinPosition = 0,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
weight = 0.0
|
||||||
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
||||||
entity.pinPosition = manuallySorted.size - index + 1
|
entity.pinPosition = manuallySorted.size - index + 1
|
||||||
entity
|
entity
|
||||||
@ -283,6 +298,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
launchCount = 0,
|
launchCount = 0,
|
||||||
pinPosition = 0,
|
pinPosition = 0,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
weight = 0.0
|
||||||
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
||||||
entity.pinPosition = 1
|
entity.pinPosition = 1
|
||||||
entity
|
entity
|
||||||
@ -300,6 +316,10 @@ internal class FavoritesRepositoryImpl(
|
|||||||
return database.searchDao().sortByRelevance(keys)
|
return database.searchDao().sortByRelevance(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun sortByWeight(keys: List<String>): Flow<List<String>> {
|
||||||
|
return database.searchDao().sortByWeight(keys)
|
||||||
|
}
|
||||||
|
|
||||||
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
||||||
val deserializer: SearchableDeserializer =
|
val deserializer: SearchableDeserializer =
|
||||||
getDeserializer(context, entity.type)
|
getDeserializer(context, entity.type)
|
||||||
@ -310,7 +330,8 @@ internal class FavoritesRepositoryImpl(
|
|||||||
searchable = searchable,
|
searchable = searchable,
|
||||||
launchCount = entity.launchCount,
|
launchCount = entity.launchCount,
|
||||||
pinPosition = entity.pinPosition,
|
pinPosition = entity.pinPosition,
|
||||||
hidden = entity.hidden
|
hidden = entity.hidden,
|
||||||
|
weight = entity.weight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +361,8 @@ internal class FavoritesRepositoryImpl(
|
|||||||
"hidden" to fav.hidden,
|
"hidden" to fav.hidden,
|
||||||
"launchCount" to fav.launchCount,
|
"launchCount" to fav.launchCount,
|
||||||
"pinPosition" to fav.pinPosition,
|
"pinPosition" to fav.pinPosition,
|
||||||
"searchable" to fav.serializedSearchable
|
"searchable" to fav.serializedSearchable,
|
||||||
|
"weight" to fav.weight,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -373,7 +395,8 @@ internal class FavoritesRepositoryImpl(
|
|||||||
serializedSearchable = json.getString("searchable"),
|
serializedSearchable = json.getString("searchable"),
|
||||||
launchCount = json.getInt("launchCount"),
|
launchCount = json.getInt("launchCount"),
|
||||||
hidden = json.getBoolean("hidden"),
|
hidden = json.getBoolean("hidden"),
|
||||||
pinPosition = json.getInt("pinPosition")
|
pinPosition = json.getInt("pinPosition"),
|
||||||
|
weight = json.optDouble("weight").takeIf { !it.isNaN() } ?: 0.0
|
||||||
)
|
)
|
||||||
favorites.add(entity)
|
favorites.add(entity)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val favoritesModule = module {
|
val favoritesModule = module {
|
||||||
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get()) }
|
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get(), get()) }
|
||||||
}
|
}
|
||||||
@ -11,7 +11,8 @@ data class SavedSearchable(
|
|||||||
val searchable: SavableSearchable?,
|
val searchable: SavableSearchable?,
|
||||||
var launchCount: Int,
|
var launchCount: Int,
|
||||||
var pinPosition: Int,
|
var pinPosition: Int,
|
||||||
var hidden: Boolean
|
var hidden: Boolean,
|
||||||
|
var weight: Double
|
||||||
) {
|
) {
|
||||||
fun toDatabaseEntity(): SavedSearchableEntity? {
|
fun toDatabaseEntity(): SavedSearchableEntity? {
|
||||||
val serializer = getSerializer(searchable)
|
val serializer = getSerializer(searchable)
|
||||||
@ -24,7 +25,8 @@ data class SavedSearchable(
|
|||||||
serializedSearchable = data,
|
serializedSearchable = data,
|
||||||
hidden = hidden,
|
hidden = hidden,
|
||||||
pinPosition = pinPosition,
|
pinPosition = pinPosition,
|
||||||
launchCount = launchCount
|
launchCount = launchCount,
|
||||||
|
weight = weight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,8 +182,13 @@ class BackupManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Format changelog:
|
||||||
|
* - 1.5: added `weight` to favorites
|
||||||
|
*/
|
||||||
|
|
||||||
private const val BackupFormatMajor = 1
|
private const val BackupFormatMajor = 1
|
||||||
private const val BackupFormatMinor = 4
|
private const val BackupFormatMinor = 5
|
||||||
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
|
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user