Search: reorder results by launchCount (#267)
* add app search result ordering by launchCount * add reordering for other domains * add preference in settings * bruh moment - database optimizaiton - fixing obvious fallacy while reordering * no reordering on empty queries && reordering early return * avoiding deserialization * listpreference * search settings screen icons & categories * regrouping * early-return tweak * Change ranking algorithm * Pepega * Prioritize launchCount over pinned for relevance sorting --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
parent
600e319c99
commit
0b2aa716ec
@ -105,12 +105,10 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val wallpaperBlur = dataStore.data.map { it.appearance.blurWallpaper }.asLiveData()
|
val wallpaperBlur = dataStore.data.map { it.appearance.blurWallpaper }.asLiveData()
|
||||||
|
|
||||||
val fillClockHeight = dataStore.data.map { it.clockWidget.fillHeight }.asLiveData()
|
val fillClockHeight = dataStore.data.map { it.clockWidget.fillHeight }.asLiveData()
|
||||||
val searchBarColor = dataStore.data.map { it.searchBar.color }.asLiveData()
|
val searchBarColor = dataStore.data.map { it.searchBar.color }.asLiveData()
|
||||||
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
|
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
|
||||||
|
|
||||||
|
|
||||||
val gestureState: StateFlow<GestureState> = dataStore
|
val gestureState: StateFlow<GestureState> = dataStore
|
||||||
.data.map { it.gestures }
|
.data.map { it.gestures }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
@ -155,7 +153,6 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
)
|
)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
|
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
|
||||||
|
|
||||||
|
|
||||||
var failedGestureState by mutableStateOf<FailedGesture?>(null)
|
var failedGestureState by mutableStateOf<FailedGesture?>(null)
|
||||||
fun handleGesture(context: Context, gesture: Gesture): Boolean {
|
fun handleGesture(context: Context, gesture: Gesture): Boolean {
|
||||||
val action = when (gesture) {
|
val action = when (gesture) {
|
||||||
|
|||||||
@ -198,6 +198,7 @@ fun SearchColumn(
|
|||||||
highlightedItem = bestMatch as? SavableSearchable
|
highlightedItem = bestMatch as? SavableSearchable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GridResults(
|
GridResults(
|
||||||
items = if ((showWorkProfileApps || apps.isEmpty()) && workApps.isNotEmpty()) workApps.toImmutableList() else apps.toImmutableList(),
|
items = if ((showWorkProfileApps || apps.isEmpty()) && workApps.isNotEmpty()) workApps.toImmutableList() else apps.toImmutableList(),
|
||||||
columns = columns,
|
columns = columns,
|
||||||
|
|||||||
@ -6,9 +6,11 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||||
|
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.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
|
||||||
@ -29,6 +31,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
@ -103,19 +106,19 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
hideFavorites.value = query.isNotEmpty()
|
hideFavorites.value = query.isNotEmpty()
|
||||||
searchJob = viewModelScope.launch {
|
searchJob = viewModelScope.launch {
|
||||||
dataStore.data.collectLatest {
|
dataStore.data.collectLatest { settings ->
|
||||||
searchService.search(
|
searchService.search(
|
||||||
query,
|
query,
|
||||||
calculator = it.calculatorSearch,
|
calculator = settings.calculatorSearch,
|
||||||
unitConverter = it.unitConverterSearch,
|
unitConverter = settings.unitConverterSearch,
|
||||||
calendars = it.calendarSearch,
|
calendars = settings.calendarSearch,
|
||||||
contacts = it.contactsSearch,
|
contacts = settings.contactsSearch,
|
||||||
files = it.fileSearch,
|
files = settings.fileSearch,
|
||||||
shortcuts = it.appShortcutSearch,
|
shortcuts = settings.appShortcutSearch,
|
||||||
websites = it.websiteSearch,
|
websites = settings.websiteSearch,
|
||||||
wikipedia = it.wikipediaSearch,
|
wikipedia = settings.wikipediaSearch,
|
||||||
).collectLatest { results ->
|
).collectLatest { results ->
|
||||||
val resultsList = withContext(Dispatchers.Default) {
|
var resultsList = withContext(Dispatchers.Default) {
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
results.apps,
|
results.apps,
|
||||||
results.other,
|
results.other,
|
||||||
@ -129,10 +132,42 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
results.unitConverters,
|
results.unitConverters,
|
||||||
results.searchActions,
|
results.searchActions,
|
||||||
).flatten()
|
).flatten()
|
||||||
.sortedBy { (it as? SavableSearchable) }
|
|
||||||
.distinctBy { if (it is SavableSearchable) it.key else it }
|
.distinctBy { if (it is SavableSearchable) it.key else it }
|
||||||
|
.sortedBy { (it as? SavableSearchable) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val relevance =
|
||||||
|
if (query.isNotEmpty() && settings.searchBar.searchResultOrdering == SearchResultOrdering.Relevance) {
|
||||||
|
favoritesRepository.sortByRelevance(
|
||||||
|
resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
||||||
|
).first()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsList = resultsList.sortedWith { a, b ->
|
||||||
|
when {
|
||||||
|
a is SavableSearchable && b !is SavableSearchable -> -1
|
||||||
|
a !is SavableSearchable && b is SavableSearchable -> 1
|
||||||
|
a is SavableSearchable && b is SavableSearchable -> {
|
||||||
|
val aKey = a.key
|
||||||
|
val bKey = b.key
|
||||||
|
val aRank = relevance.indexOf(aKey)
|
||||||
|
val bRank = relevance.indexOf(bKey)
|
||||||
|
when {
|
||||||
|
aRank != -1 && bRank != -1 -> aRank.compareTo(bRank)
|
||||||
|
aRank == -1 && bRank != -1 -> 1
|
||||||
|
aRank != -1 && bRank == -1 -> -1
|
||||||
|
else -> a.compareTo(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
hiddenItemKeys.collectLatest { hiddenKeys ->
|
hiddenItemKeys.collectLatest { hiddenKeys ->
|
||||||
val hidden = mutableListOf<SavableSearchable>()
|
val hidden = mutableListOf<SavableSearchable>()
|
||||||
val apps = mutableListOf<LauncherApp>()
|
val apps = mutableListOf<LauncherApp>()
|
||||||
@ -151,6 +186,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
r is SavableSearchable && hiddenKeys.contains(r.key) -> {
|
r is SavableSearchable && hiddenKeys.contains(r.key) -> {
|
||||||
hidden.add(r)
|
hidden.add(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
r is LauncherApp && !r.isMainProfile -> workApps.add(r)
|
r is LauncherApp && !r.isMainProfile -> workApps.add(r)
|
||||||
r is LauncherApp -> apps.add(r)
|
r is LauncherApp -> apps.add(r)
|
||||||
r is AppShortcut -> shortcuts.add(r)
|
r is AppShortcut -> shortcuts.add(r)
|
||||||
@ -165,7 +201,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.isNotEmpty() && launchOnEnter.value) {
|
if (query.isNotEmpty() && launchOnEnter.value) {
|
||||||
bestMatch.value = listOf(
|
bestMatch.value = listOf(
|
||||||
apps,
|
apps,
|
||||||
workApps,
|
workApps,
|
||||||
@ -274,4 +310,20 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T : SavableSearchable> MutableList<T>.reorderByRanks(ranks: List<SavedSearchableRankInfo>) {
|
||||||
|
if (this.size < 2) // one element does not need reordering
|
||||||
|
return
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
for (item in ranks) {
|
||||||
|
val idx = this.indexOfFirst { it.key == item.key }
|
||||||
|
if (idx == -1) continue
|
||||||
|
|
||||||
|
this.add(i++, this.removeAt(idx))
|
||||||
|
|
||||||
|
if (i >= this.size) break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +53,6 @@ abstract class SearchableItemVM(
|
|||||||
return customAttributesRepository.getTags(searchable)
|
return customAttributesRepository.getTags(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun launch(context: Context, bounds: Rect? = null): Boolean {
|
open fun launch(context: Context, bounds: Rect? = null): Boolean {
|
||||||
val view = (context as? AppCompatActivity)?.window?.decorView
|
val view = (context as? AppCompatActivity)?.window?.decorView
|
||||||
val options = if (bounds != null && view != null) {
|
val options = if (bounds != null && view != null) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
import de.mm20.launcher2.ui.component.preferences.*
|
import de.mm20.launcher2.ui.component.preferences.*
|
||||||
@ -100,7 +101,10 @@ fun SearchSettingsScreen() {
|
|||||||
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.observeAsState()
|
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.observeAsState()
|
||||||
AnimatedVisibility(hasAppShortcutsPermission == false) {
|
AnimatedVisibility(hasAppShortcutsPermission == false) {
|
||||||
MissingPermissionBanner(
|
MissingPermissionBanner(
|
||||||
text = stringResource(R.string.missing_permission_appshortcuts_search_settings, stringResource(R.string.app_name)),
|
text = stringResource(
|
||||||
|
R.string.missing_permission_appshortcuts_search_settings,
|
||||||
|
stringResource(R.string.app_name)
|
||||||
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
|
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
|
||||||
},
|
},
|
||||||
@ -180,28 +184,48 @@ fun SearchSettingsScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val autoFocus by viewModel.autoFocus.observeAsState()
|
|
||||||
val launchOnEnter by viewModel.launchOnEnter.observeAsState()
|
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
|
val autoFocus by viewModel.autoFocus.observeAsState()
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_search_bar_auto_focus),
|
title = stringResource(R.string.preference_search_bar_auto_focus),
|
||||||
summary = stringResource(R.string.preference_search_bar_auto_focus_summary),
|
summary = stringResource(R.string.preference_search_bar_auto_focus_summary),
|
||||||
|
icon = Icons.Rounded.Keyboard,
|
||||||
value = autoFocus == true,
|
value = autoFocus == true,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.setAutoFocus(it)
|
viewModel.setAutoFocus(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
val launchOnEnter by viewModel.launchOnEnter.observeAsState()
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_search_bar_launch_on_enter),
|
title = stringResource(R.string.preference_search_bar_launch_on_enter),
|
||||||
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
|
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
|
||||||
|
icon = Icons.Rounded.ArrowRightAlt,
|
||||||
value = launchOnEnter == true,
|
value = launchOnEnter == true,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
viewModel.setLaunchOnEnter(it)
|
viewModel.setLaunchOnEnter(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
val searchResultOrdering by viewModel.searchResultOrdering.observeAsState()
|
||||||
|
ListPreference(
|
||||||
|
title = stringResource(R.string.preference_search_bar_ordering),
|
||||||
|
value = searchResultOrdering,
|
||||||
|
icon = Icons.Rounded.Sort,
|
||||||
|
items = listOf(
|
||||||
|
stringResource(R.string.preference_search_bar_ordering_alphabetic) to Settings.SearchBarSettings.SearchResultOrdering.Alphabetic,
|
||||||
|
stringResource(R.string.preference_search_bar_ordering_relevance) to Settings.SearchBarSettings.SearchResultOrdering.Relevance
|
||||||
|
),
|
||||||
|
onValueChanged = {
|
||||||
|
if (it != null) viewModel.setSearchResultOrdering(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
Preference(
|
Preference(
|
||||||
title = stringResource(R.string.preference_hidden_items),
|
title = stringResource(R.string.preference_hidden_items),
|
||||||
summary = stringResource(R.string.preference_hidden_items_summary),
|
summary = stringResource(R.string.preference_hidden_items_summary),
|
||||||
|
icon = Icons.Rounded.VisibilityOff,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController?.navigate("settings/search/hiddenitems")
|
navController?.navigate("settings/search/hiddenitems")
|
||||||
}
|
}
|
||||||
@ -209,6 +233,7 @@ fun SearchSettingsScreen() {
|
|||||||
Preference(
|
Preference(
|
||||||
title = stringResource(R.string.preference_screen_tags),
|
title = stringResource(R.string.preference_screen_tags),
|
||||||
summary = stringResource(R.string.preference_screen_tags_summary),
|
summary = stringResource(R.string.preference_screen_tags_summary),
|
||||||
|
icon = Icons.Rounded.Tag,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController?.navigate("settings/search/tags")
|
navController?.navigate("settings/search/tags")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ 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 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
|
||||||
@ -31,7 +32,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val hasContactsPermission = permissionsManager.hasPermission(PermissionGroup.Contacts).asLiveData()
|
val hasContactsPermission =
|
||||||
|
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 {
|
||||||
@ -45,11 +47,13 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestContactsPermission(activity: AppCompatActivity) {
|
fun requestContactsPermission(activity: AppCompatActivity) {
|
||||||
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
|
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar).asLiveData()
|
val hasCalendarPermission =
|
||||||
|
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 {
|
||||||
@ -63,6 +67,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestCalendarPermission(activity: AppCompatActivity) {
|
fun requestCalendarPermission(activity: AppCompatActivity) {
|
||||||
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
|
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
|
||||||
}
|
}
|
||||||
@ -165,8 +170,23 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val searchResultOrdering = dataStore.data.map { it.searchBar.searchResultOrdering }.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 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 {
|
||||||
@ -180,6 +200,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
|
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
|
||||||
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
|
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,12 +63,9 @@ interface SearchDao {
|
|||||||
@Query("SELECT `key` FROM Searchable WHERE hidden = 1 AND type = 'calendar'")
|
@Query("SELECT `key` FROM Searchable WHERE hidden = 1 AND type = 'calendar'")
|
||||||
fun getHiddenCalendarEventKeys(): Flow<List<String>>
|
fun getHiddenCalendarEventKeys(): Flow<List<String>>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Query("DELETE FROM Searchable WHERE `key` IN (:keys)")
|
@Query("DELETE FROM Searchable WHERE `key` IN (:keys)")
|
||||||
fun deleteAll(keys: List<String>)
|
fun deleteAll(keys: List<String>)
|
||||||
|
|
||||||
|
|
||||||
@Query("UPDATE Searchable SET pinned = 1, hidden = 0 WHERE `key` = :key")
|
@Query("UPDATE Searchable SET pinned = 1, hidden = 0 WHERE `key` = :key")
|
||||||
fun pinExistingItem(key: String)
|
fun pinExistingItem(key: String)
|
||||||
|
|
||||||
@ -145,4 +142,7 @@ interface SearchDao {
|
|||||||
|
|
||||||
@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")
|
||||||
|
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -574,4 +574,7 @@
|
|||||||
<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_bar_ordering_alphabetic">Alphabetisch</string>
|
||||||
|
<string name="preference_search_bar_ordering_relevance">Relevanz</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -761,4 +761,7 @@
|
|||||||
<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_bar_ordering_alphabetic">Alphabetic</string>
|
||||||
|
<string name="preference_search_bar_ordering_relevance">Relevance</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -223,6 +223,11 @@ 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;
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,13 @@ interface FavoritesRepository {
|
|||||||
fun getHiddenItems(): Flow<List<SavableSearchable>>
|
fun getHiddenItems(): Flow<List<SavableSearchable>>
|
||||||
fun getHiddenItemKeys(): Flow<List<String>>
|
fun getHiddenItemKeys(): Flow<List<String>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given keys sorted by relevance.
|
||||||
|
* The first item in the list is the most relevant.
|
||||||
|
* Unknown keys will not be included in the result.
|
||||||
|
*/
|
||||||
|
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove this item from the Searchable database
|
* Remove this item from the Searchable database
|
||||||
*/
|
*/
|
||||||
@ -107,6 +114,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = frequentlyUsed,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
includeTypes != null && excludeTypes == null -> {
|
includeTypes != null && excludeTypes == null -> {
|
||||||
dao.getFavoritesWithTypes(
|
dao.getFavoritesWithTypes(
|
||||||
includeTypes = includeTypes,
|
includeTypes = includeTypes,
|
||||||
@ -116,6 +124,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
excludeTypes != null && includeTypes == null -> {
|
excludeTypes != null && includeTypes == null -> {
|
||||||
dao.getFavoritesWithoutTypes(
|
dao.getFavoritesWithoutTypes(
|
||||||
excludeTypes = excludeTypes,
|
excludeTypes = excludeTypes,
|
||||||
@ -125,6 +134,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("You can either use includeTypes or excludeTypes, not both")
|
else -> throw IllegalArgumentException("You can either use includeTypes or excludeTypes, not both")
|
||||||
}
|
}
|
||||||
return entities.map {
|
return entities.map {
|
||||||
@ -137,13 +147,13 @@ internal class FavoritesRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
||||||
return AppDatabase.getInstance(context).searchDao().isPinned(searchable.key)
|
return database.searchDao().isPinned(searchable.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pinItem(searchable: SavableSearchable) {
|
override fun pinItem(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val dao = AppDatabase.getInstance(context).searchDao()
|
val dao = database.searchDao()
|
||||||
val databaseItem = dao.getFavorite(searchable.key)
|
val databaseItem = dao.getFavorite(searchable.key)
|
||||||
val savedSearchable = SavedSearchable(
|
val savedSearchable = SavedSearchable(
|
||||||
key = searchable.key,
|
key = searchable.key,
|
||||||
@ -160,19 +170,19 @@ internal class FavoritesRepositoryImpl(
|
|||||||
override fun unpinItem(searchable: SavableSearchable) {
|
override fun unpinItem(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
AppDatabase.getInstance(context).searchDao().unpinFavorite(searchable.key)
|
database.searchDao().unpinFavorite(searchable.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||||
return AppDatabase.getInstance(context).searchDao().isHidden(searchable.key)
|
return database.searchDao().isHidden(searchable.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideItem(searchable: SavableSearchable) {
|
override fun hideItem(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val dao = AppDatabase.getInstance(context).searchDao()
|
val dao = database.searchDao()
|
||||||
val databaseItem = dao.getFavorite(searchable.key)
|
val databaseItem = dao.getFavorite(searchable.key)
|
||||||
val savedSearchable = SavedSearchable(
|
val savedSearchable = SavedSearchable(
|
||||||
key = searchable.key,
|
key = searchable.key,
|
||||||
@ -189,7 +199,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
override fun unhideItem(searchable: SavableSearchable) {
|
override fun unhideItem(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
AppDatabase.getInstance(context).searchDao().unhideItem(searchable.key)
|
database.searchDao().unhideItem(searchable.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +209,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val item = SavedSearchable(searchable.key, searchable, 0, 0, false)
|
val item = SavedSearchable(searchable.key, searchable, 0, 0, false)
|
||||||
item.toDatabaseEntity()?.let {
|
item.toDatabaseEntity()?.let {
|
||||||
AppDatabase.getInstance(context).searchDao()
|
database.searchDao()
|
||||||
.incrementLaunchCount(it)
|
.incrementLaunchCount(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,6 +296,9 @@ internal class FavoritesRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun sortByRelevance(keys: List<String>): Flow<List<String>> {
|
||||||
|
return database.searchDao().sortByRelevance(keys)
|
||||||
|
}
|
||||||
|
|
||||||
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
||||||
val deserializer: SearchableDeserializer =
|
val deserializer: SearchableDeserializer =
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
package de.mm20.launcher2.favorites
|
||||||
|
|
||||||
|
data class SavedSearchableRankInfo(
|
||||||
|
val key: String,
|
||||||
|
val type: String,
|
||||||
|
var launchCount: Int
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user