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:
Christoph 2023-02-28 20:45:25 +01:00 committed by GitHub
parent 600e319c99
commit 0b2aa716ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 158 additions and 32 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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
}
}
} }

View File

@ -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) {

View File

@ -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")
} }

View File

@ -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)
} }

View File

@ -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>>
} }

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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 =

View File

@ -0,0 +1,7 @@
package de.mm20.launcher2.favorites
data class SavedSearchableRankInfo(
val key: String,
val type: String,
var launchCount: Int
)