feat(search): Implement and encapsulate AppSearchProvider

This commit is contained in:
SuperDragonXD
2025-07-24 15:42:32 +08:00
parent a7e65845a5
commit 4f6b42454f

View File

@@ -0,0 +1,81 @@
package app.lawnchair.search.engine.provider
import android.content.Context
import android.content.pm.ShortcutInfo
import app.lawnchair.launcher
import app.lawnchair.preferences2.PreferenceManager2
import app.lawnchair.search.algorithms.filterHiddenApps
import app.lawnchair.search.engine.SearchResult
import com.android.launcher3.model.AllAppsList
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.popup.PopupPopulator
import com.android.launcher3.search.StringMatcherUtility
import com.android.launcher3.shortcuts.ShortcutRequest
import com.patrykmichalik.opto.core.firstBlocking
import java.util.Locale
import me.xdrop.fuzzywuzzy.FuzzySearch
import me.xdrop.fuzzywuzzy.algorithms.WeightedRatio
object AppSearchProvider {
fun search(context: Context, query: String, allApps: AllAppsList): List<SearchResult> {
val prefs = PreferenceManager2.getInstance(context)
val hiddenApps = prefs.hiddenApps.firstBlocking()
val hiddenAppsInSearch = prefs.hiddenAppsInSearch.firstBlocking()
val maxAppResults = prefs.maxAppSearchResultCount.firstBlocking()
val enableFuzzySearch = prefs.enableFuzzySearch.firstBlocking()
val appResults = if (enableFuzzySearch) {
fuzzySearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch)
} else {
normalSearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch)
}
val finalResults = mutableListOf<SearchResult>()
appResults.mapTo(finalResults) { SearchResult.App(data = it) }
if (appResults.size == 1) {
val singleApp = appResults.first()
val shortcuts = getShortcuts(singleApp, context)
shortcuts.mapTo(finalResults) { SearchResult.Shortcut(data = it) }
}
return finalResults
}
private fun normalSearch(apps: List<AppInfo>, query: String, maxResultsCount: Int, hiddenApps: Set<String>, hiddenAppsInSearch: String): List<AppInfo> {
// Do an intersection of the words in the query and each title, and filter out all the
// apps that don't match all of the words in the query.
val queryTextLower = query.lowercase(Locale.getDefault())
val matcher = StringMatcherUtility.StringMatcher.getInstance()
return apps.asSequence()
.filter { StringMatcherUtility.matches(queryTextLower, it.title.toString(), matcher) }
.filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch)
.take(maxResultsCount)
.toList()
}
private fun fuzzySearch(apps: List<AppInfo>, query: String, maxResultsCount: Int, hiddenApps: Set<String>, hiddenAppsInSearch: String): List<AppInfo> {
val queryTextLower = query.lowercase(Locale.getDefault())
val filteredApps = apps.asSequence()
.filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch)
.toList()
val matches = FuzzySearch.extractSorted(
queryTextLower,
filteredApps,
{ it.sectionName + it.title },
WeightedRatio(),
65,
)
return matches.take(maxResultsCount)
.map { it.referent }
}
private fun getShortcuts(app: AppInfo, context: Context): List<ShortcutInfo> {
val shortcuts = ShortcutRequest(context.launcher, app.user)
.withContainer(app.targetComponent)
.query(ShortcutRequest.PUBLISHED)
return PopupPopulator.sortAndFilterShortcuts(shortcuts)
}
}