Refactor search result generation

This commit is contained in:
SuperDragonXD
2024-07-19 19:33:34 +08:00
parent 7cd01c0789
commit 0fc79b9d82
12 changed files with 223 additions and 233 deletions
@@ -12,7 +12,7 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import app.lawnchair.launcher
import app.lawnchair.search.adapter.SHORTCUT
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.util.runOnMainThread
import com.android.launcher3.BubbleTextView
@@ -11,7 +11,7 @@ import app.lawnchair.allapps.views.SearchResultView.Companion.FLAG_HIDE_SUBTITLE
import app.lawnchair.font.FontManager
import app.lawnchair.search.adapter.HISTORY
import app.lawnchair.search.adapter.SETTINGS
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.search.adapter.WEB_SUGGESTION
import com.android.app.search.LayoutType
@@ -10,7 +10,7 @@ import android.widget.TextView
import androidx.core.view.ViewCompat
import app.lawnchair.font.FontManager
import app.lawnchair.launcher
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.util.AppInfo
import app.lawnchair.util.AppInfoHelper
@@ -13,7 +13,7 @@ import app.lawnchair.search.LawnchairRecentSuggestionProvider
import app.lawnchair.search.adapter.HEADER_JUSTIFY
import app.lawnchair.search.adapter.SPACE
import app.lawnchair.search.adapter.SPACE_MINI
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.theme.color.tokens.ColorTokens
import com.android.launcher3.R
@@ -8,7 +8,7 @@ import app.lawnchair.search.adapter.CONTACT
import app.lawnchair.search.adapter.FILES
import app.lawnchair.search.adapter.MARKET_STORE
import app.lawnchair.search.adapter.START_PAGE
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.search.adapter.WEB_SUGGESTION
import com.android.app.search.LayoutType
@@ -8,7 +8,7 @@ import androidx.core.util.contains
import app.lawnchair.allapps.views.SearchItemDecorator
import app.lawnchair.allapps.views.SearchResultView
import app.lawnchair.search.adapter.SearchAdapterItem
import app.lawnchair.search.adapter.SearchResultActionCallBack
import app.lawnchair.search.model.SearchResultActionCallBack
import com.android.app.search.LayoutType
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
@@ -3,6 +3,7 @@ package app.lawnchair.search.adapter
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.graphics.BitmapFactory
import android.graphics.drawable.Icon
import android.net.Uri
@@ -28,28 +29,56 @@ import app.lawnchair.util.file2Uri
import app.lawnchair.util.mimeCompat
import com.android.app.search.LayoutType
import com.android.launcher3.R
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.PackageManagerHelper
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
import java.util.Locale
import okio.ByteString
class GenerateSearchTarget(private val context: Context) {
class SearchTargetFactory(
private val context: Context
) {
fun createAppSearchTarget(appInfo: AppInfo, asRow: Boolean = false): SearchTargetCompat {
val componentName = appInfo.componentName
val user = appInfo.user
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_APPLICATION,
if (asRow) LayoutType.SMALL_ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT,
generateHashKey(ComponentKey(componentName, user).toString()),
).apply {
setPackageName(componentName.packageName)
setUserHandle(user)
setExtras(bundleOf("class" to componentName.className))
}.build()
}
private val marketSearchComponent = resolveMarketSearchActivity()
fun createShortcutTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_SHORTCUT,
LayoutType.SMALL_ICON_HORIZONTAL_TEXT,
"shortcut_" + generateHashKey("${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}"),
).apply {
setShortcutInfo(shortcutInfo)
setUserHandle(shortcutInfo.userHandle)
setExtras(Bundle())
}.build()
}
internal fun getSuggestionTarget(suggestion: String, suggestionProvider: String): SearchTargetCompat {
fun createWebSuggestionsTarget(suggestion: String, suggestionProvider: String): SearchTargetCompat {
val url = WebSearchProvider.fromString(suggestionProvider).getSearchUrl(suggestion)
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
val id = suggestion + url
val action = SearchActionCompat.Builder(id, suggestion)
.setIcon(
val action = SearchActionCompat.Builder(id, suggestion).apply {
setIcon(
Icon.createWithResource(context, R.drawable.ic_allapps_search)
.setTint(ColorTokens.TextColorSecondary.resolveColor(context)),
)
.setIntent(browserIntent)
.build()
setIntent(browserIntent)
}.build()
return createSearchTarget(
id,
action,
@@ -59,7 +88,7 @@ class GenerateSearchTarget(private val context: Context) {
)
}
internal fun getCalculationTarget(
internal fun createCalculatorTarget(
calculation: Calculation,
): SearchTargetCompat {
val result = calculation.result
@@ -76,14 +105,35 @@ class GenerateSearchTarget(private val context: Context) {
val extras = bundleOf()
return createCalculatorTarget(
return createSearchTarget(
id,
action,
LayoutType.CALCULATOR,
SearchTargetCompat.RESULT_TYPE_CALCULATOR,
"",
extras,
)
}
internal fun getRecentKeywordTarget(recentKeyword: RecentKeyword, suggestionProvider: String): SearchTargetCompat {
fun createHeaderTarget(header: String, pkg: String = HEADER): SearchTargetCompat {
val id = "header_$header"
val action = SearchActionCompat.Builder(id, header)
.setIcon(
Icon.createWithResource(context, R.drawable.ic_allapps_search)
.setTint(ColorTokens.TextColorPrimary.resolveColor(context)),
)
.setIntent(Intent())
.build()
return createSearchTarget(
id,
action,
LayoutType.TEXT_HEADER,
SearchTargetCompat.RESULT_TYPE_SECTION_HEADER,
pkg,
)
}
fun createSearchHistoryTarget(recentKeyword: RecentKeyword, suggestionProvider: String): SearchTargetCompat {
val value = recentKeyword.getValueByKey("display1") ?: ""
val url = WebSearchProvider.fromString(suggestionProvider).getSearchUrl(value)
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
@@ -104,25 +154,7 @@ class GenerateSearchTarget(private val context: Context) {
)
}
internal fun getHeaderTarget(header: String, pkg: String = HEADER): SearchTargetCompat {
val id = "header_$header"
val action = SearchActionCompat.Builder(id, header)
.setIcon(
Icon.createWithResource(context, R.drawable.ic_allapps_search)
.setTint(ColorTokens.TextColorPrimary.resolveColor(context)),
)
.setIntent(Intent())
.build()
return createSearchTarget(
id,
action,
LayoutType.TEXT_HEADER,
SearchTargetCompat.RESULT_TYPE_SECTION_HEADER,
pkg,
)
}
internal fun getSettingSearchItem(info: SettingInfo): SearchTargetCompat? {
fun createSettingsTarget(info: SettingInfo): SearchTargetCompat? {
val id = "_${info.id}"
val intent = Intent(info.action)
@@ -135,12 +167,12 @@ class GenerateSearchTarget(private val context: Context) {
return null
}
if (!hasRequiredPermissions(intent)) {
if (!SettingsTarget.hasRequiredPermissions(context, intent)) {
Log.w("SettingSearch", "App does not have required permissions for intent: $intent")
return null
}
val actionBuilder = SearchActionCompat.Builder(id, formatSettingTitle(info.name))
val actionBuilder = SearchActionCompat.Builder(id, SettingsTarget.formatSettingTitle(info.name))
.setIcon(
Icon.createWithResource(context, R.drawable.ic_setting)
.setTint(ColorTokens.Accent1_600.resolveColor(context)),
@@ -157,24 +189,26 @@ class GenerateSearchTarget(private val context: Context) {
)
}
private fun hasRequiredPermissions(intent: Intent): Boolean {
val packageManager = context.packageManager
val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
return resolveInfo?.activityInfo?.exported == true
private fun createSearchLinksTarget(
id: String,
action: SearchActionCompat,
pkg: String,
extras: Bundle = Bundle(),
): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_REDIRECTION,
LayoutType.ICON_HORIZONTAL_TEXT,
generateHashKey(id),
)
.setPackageName(pkg)
.setUserHandle(Process.myUserHandle())
.setSearchAction(action)
.setExtras(extras)
.build()
}
private fun formatSettingTitle(rawTitle: String?): String {
return rawTitle?.replace('_', ' ')
?.replace("ACTION", "")
?.lowercase()
?.split(' ')
?.joinToString(" ") {
it.replaceFirstChar { char -> char.uppercase(Locale.ROOT) }
}.orEmpty()
}
internal fun getMarketSearchItem(query: String): SearchTargetCompat? {
if (marketSearchComponent == null) return null
internal fun createMarketSearchTarget(query: String): SearchTargetCompat? {
val marketSearchComponent = SearchLinksTarget.resolveMarketSearchActivity(context) ?: return null
val id = "marketSearch:$query"
val action = SearchActionCompat.Builder(
id,
@@ -187,10 +221,15 @@ class GenerateSearchTarget(private val context: Context) {
SearchResultView.EXTRA_ICON_COMPONENT_KEY to marketSearchComponent.toString(),
SearchResultView.EXTRA_HIDE_SUBTITLE to true,
)
return createSearchTarget(id, action, MARKET_STORE, extras)
return createSearchLinksTarget(
id,
action,
MARKET_STORE,
extras
)
}
internal fun getWebSearchItem(query: String, suggestionProvider: String): SearchTargetCompat {
internal fun createWebSearchTarget(query: String, suggestionProvider: String): SearchTargetCompat {
val webSearchProvider = WebSearchProvider.fromString(suggestionProvider)
val webSearchLabel = context.getString(webSearchProvider.label)
val url = webSearchProvider.getSearchUrl(query)
@@ -206,10 +245,10 @@ class GenerateSearchTarget(private val context: Context) {
val extras = bundleOf(
SearchResultView.EXTRA_HIDE_SUBTITLE to true,
)
return createSearchTarget(id, action, START_PAGE, extras)
return createSearchLinksTarget(id, action, START_PAGE, extras)
}
internal fun getContactSearchItem(info: ContactInfo): SearchTargetCompat {
fun createContactsTarget(info: ContactInfo): SearchTargetCompat {
val id = "contact:${info.contactId}${info.number}"
val contactUri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_URI,
@@ -218,7 +257,7 @@ class GenerateSearchTarget(private val context: Context) {
val contactIntent = Intent(Intent.ACTION_VIEW, contactUri)
val action = SearchActionCompat.Builder(id, info.name)
.setIcon(displayContactPhoto(context, info.name, Uri.parse(info.uri)))
.setIcon(ContactsTarget.displayContactPhoto(context, info.name, Uri.parse(info.uri)))
.setContentDescription(info.contactId)
.setSubtitle(info.number)
.setIntent(contactIntent)
@@ -234,7 +273,7 @@ class GenerateSearchTarget(private val context: Context) {
)
}
internal fun getFileInfoSearchItem(info: IFileInfo): SearchTargetCompat {
fun createFilesTarget(info: IFileInfo): SearchTargetCompat {
val fileUri = when (info) {
is FileInfo -> Uri.withAppendedPath(
MediaStore.Files.getContentUri("external"),
@@ -254,7 +293,7 @@ class GenerateSearchTarget(private val context: Context) {
.setDataAndType(fileUri, mimeType)
val action = SearchActionCompat.Builder(info.path, info.name)
.setIcon(getPreviewIcon(info))
.setIcon(FilesTarget.getPreviewIcon(context, info))
.setIntent(fileIntent)
.build()
@@ -268,7 +307,52 @@ class GenerateSearchTarget(private val context: Context) {
)
}
private fun displayContactPhoto(context: Context, name: String, contactUri: Uri): Icon {
companion object {
private const val HASH_ALGORITHM = "SHA-256"
// TODO find a way to properly provide tag/provide ids to search target
private val messageDigest by lazy { MessageDigest.getInstance(HASH_ALGORITHM) }
private fun generateHashKey(input: String): String =
ByteString.of(*messageDigest.digest(input.toByteArray())).hex()
fun createSearchTarget(
id: String,
action: SearchActionCompat,
layoutType: String,
targetCompat: Int,
pkg: String,
extras: Bundle = Bundle(),
): SearchTargetCompat {
return SearchTargetCompat.Builder(
targetCompat,
layoutType,
generateHashKey(id),
).apply {
setPackageName(pkg)
setUserHandle(Process.myUserHandle())
setSearchAction(action)
setExtras(extras)
}.build()
}
}
}
object FilesTarget {
fun getPreviewIcon(
context: Context,info: IFileInfo): Icon {
val fileInfo = info as? FileInfo
return if (fileInfo?.isImageType == true) {
Icon.createWithFilePath(fileInfo.path)
} else {
Icon.createWithResource(context, fileInfo?.iconRes ?: R.drawable.ic_folder)
}
}
}
object ContactsTarget {
fun displayContactPhoto(context: Context, name: String, contactUri: Uri): Icon {
var inputStream: InputStream? = null
try {
inputStream = context.contentResolver.openInputStream(contactUri)
@@ -282,26 +366,54 @@ class GenerateSearchTarget(private val context: Context) {
inputStream?.close()
}
// If contact photo not available, create an icon with the first letter of the name
// If contact photo is not available, create an icon with the first letter of the contact's name
val initial = if (name.isNotEmpty()) name[0].uppercaseChar().toString() else "U"
val textBitmap = createTextBitmap(context, initial)
return Icon.createWithBitmap(textBitmap)
}
}
private fun resolveMarketSearchActivity(): ComponentKey? {
object SearchLinksTarget {
fun resolveMarketSearchActivity(context: Context): ComponentKey? {
val intent = PackageManagerHelper.getMarketSearchIntent(context, "")
val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null
val packageName = resolveInfo.activityInfo.packageName
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return null
return ComponentKey(launchIntent.component, Process.myUserHandle())
}
}
private fun getPreviewIcon(info: IFileInfo): Icon {
val fileInfo = info as? FileInfo
return if (fileInfo?.isImageType == true) {
Icon.createWithFilePath(fileInfo.path)
} else {
Icon.createWithResource(context, fileInfo?.iconRes ?: R.drawable.ic_folder)
}
object SettingsTarget {
fun hasRequiredPermissions(context: Context, intent: Intent): Boolean {
val packageManager = context.packageManager
val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
return resolveInfo?.activityInfo?.exported == true
}
fun formatSettingTitle(rawTitle: String?): String {
return rawTitle?.replace('_', ' ')
?.replace("ACTION", "")
?.lowercase()
?.split(' ')
?.joinToString(" ") {
it.replaceFirstChar { char -> char.uppercase(Locale.ROOT) }
}.orEmpty()
}
}
// keys used in `pkg` param
const val START_PAGE = "startpage"
const val MARKET_STORE = "marketstore"
const val WEB_SUGGESTION = "suggestion"
const val HEADER = "header"
const val CONTACT = "contact"
const val FILES = "files"
const val SPACE = "space"
const val SPACE_MINI = "space_mini"
const val LOADING = "loading"
const val ERROR = "error"
const val SETTINGS = "setting"
const val SHORTCUT = "shortcut"
const val HISTORY = "recent_keyword"
const val HEADER_JUSTIFY = "header_justify"
const val CALCULATOR = "calculator"
@@ -1,125 +0,0 @@
package app.lawnchair.search.adapter
import android.content.pm.ShortcutInfo
import android.os.Bundle
import android.os.Process
import androidx.core.os.bundleOf
import com.android.app.search.LayoutType
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.util.ComponentKey
import java.security.MessageDigest
import okio.ByteString
// We're generate hash key as alt in id, so we can avoid
// the wrong action when click,
// TODO remove when we properly manage creating search target
private fun generateHashKey(input: String): String =
ByteString.of(*MessageDigest.getInstance("SHA-256").digest(input.toByteArray())).hex()
fun createSearchTarget(appInfo: AppInfo, asRow: Boolean = false): SearchTargetCompat {
val componentName = appInfo.componentName
val user = appInfo.user
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_APPLICATION,
if (asRow) LayoutType.SMALL_ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT,
generateHashKey(ComponentKey(componentName, user).toString()),
)
.setPackageName(componentName.packageName)
.setUserHandle(user)
.setExtras(bundleOf("class" to componentName.className))
.build()
}
fun createSearchTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_SHORTCUT,
LayoutType.SMALL_ICON_HORIZONTAL_TEXT,
"shortcut_" + generateHashKey("${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}"),
)
.setShortcutInfo(shortcutInfo)
.setUserHandle(shortcutInfo.userHandle)
.setExtras(Bundle())
.build()
}
fun createSearchTarget(
id: String,
action: SearchActionCompat,
pkg: String,
extras: Bundle = Bundle(),
): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_REDIRECTION,
LayoutType.ICON_HORIZONTAL_TEXT,
generateHashKey(id),
)
.setPackageName(pkg)
.setUserHandle(Process.myUserHandle())
.setSearchAction(action)
.setExtras(extras)
.build()
}
fun createCalculatorTarget(
id: String,
action: SearchActionCompat,
extras: Bundle = Bundle(),
): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_CALCULATOR,
LayoutType.CALCULATOR,
generateHashKey(id),
)
.setPackageName("")
.setUserHandle(Process.myUserHandle())
.setSearchAction(action)
.setExtras(extras)
.build()
}
fun createSearchTarget(
id: String,
action: SearchActionCompat,
layoutType: String,
targetCompat: Int,
pkg: String,
extras: Bundle = Bundle(),
): SearchTargetCompat {
return SearchTargetCompat.Builder(
targetCompat,
layoutType,
generateHashKey(id),
)
.setPackageName(pkg)
.setUserHandle(Process.myUserHandle())
.setSearchAction(action)
.setExtras(extras)
.build()
}
fun createDividerTarget(): SearchTargetCompat {
return SearchTargetCompat.Builder(
SearchTargetCompat.RESULT_TYPE_SHORTCUT,
LayoutType.DIVIDER,
"divider",
)
.setPackageName("")
.setUserHandle(Process.myUserHandle())
.build()
}
const val START_PAGE = "startpage"
const val MARKET_STORE = "marketstore"
const val WEB_SUGGESTION = "suggestion"
const val HEADER = "header"
const val CONTACT = "contact"
const val FILES = "files"
const val SPACE = "space"
const val SPACE_MINI = "space_mini"
const val LOADING = "loading"
const val ERROR = "error"
const val SETTINGS = "setting"
const val SHORTCUT = "shortcut"
const val HISTORY = "recent_keyword"
const val HEADER_JUSTIFY = "header_justify"
const val CALCULATOR = "calculator"
@@ -3,10 +3,9 @@ package app.lawnchair.search.algorithms
import android.content.Context
import android.os.Handler
import app.lawnchair.preferences2.PreferenceManager2
import app.lawnchair.search.adapter.GenerateSearchTarget
import app.lawnchair.search.adapter.SearchTargetFactory
import app.lawnchair.search.adapter.SPACE
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.search.adapter.createSearchTarget
import app.lawnchair.util.isDefaultLauncher
import com.android.launcher3.LauncherAppState
import com.android.launcher3.allapps.BaseAllAppsAdapter
@@ -25,7 +24,8 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
private val appState = LauncherAppState.getInstance(context)
private val resultHandler = Handler(Executors.MAIN_EXECUTOR.looper)
private val generateSearchTarget = GenerateSearchTarget(context)
// todo maybe use D.I.?
private val searchTargetFactory = SearchTargetFactory(context)
private var hiddenApps: Set<String> = setOf()
@@ -33,21 +33,21 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
private var enableFuzzySearch = false
private var maxResultsCount = 5
private val pref2 = PreferenceManager2.getInstance(context)
private val prefs2 = PreferenceManager2.getInstance(context)
val coroutineScope = CoroutineScope(context = Dispatchers.IO)
init {
pref2.enableFuzzySearch.onEach(launchIn = coroutineScope) {
prefs2.enableFuzzySearch.onEach(launchIn = coroutineScope) {
enableFuzzySearch = it
}
pref2.hiddenApps.onEach(launchIn = coroutineScope) {
prefs2.hiddenApps.onEach(launchIn = coroutineScope) {
hiddenApps = it
}
pref2.hiddenAppsInSearch.onEach(launchIn = coroutineScope) {
prefs2.hiddenAppsInSearch.onEach(launchIn = coroutineScope) {
hiddenAppsInSearch = it
}
pref2.maxAppSearchResultCount.onEach(launchIn = coroutineScope) {
prefs2.maxAppSearchResultCount.onEach(launchIn = coroutineScope) {
maxResultsCount = it
}
}
@@ -86,22 +86,22 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c
if (appResults.isNotEmpty()) {
if (appResults.size == 1) {
val singleAppResult = appResults.firstOrNull()
singleAppResult?.let { searchTargets.add(createSearchTarget(it, true)) }
singleAppResult?.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) }
if (context.isDefaultLauncher()) {
val shortcuts = singleAppResult?.let { searchUtils.getShortcuts(it, context) }
if (shortcuts != null) {
if (shortcuts.isNotEmpty()) {
searchTargets.addAll(shortcuts.map(::createSearchTarget))
searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget))
}
}
}
} else {
appResults.mapTo(searchTargets, ::createSearchTarget)
appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget)
}
searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE))
searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE))
}
generateSearchTarget.getMarketSearchItem(query)?.let { searchTargets.add(it) }
searchTargetFactory.createMarketSearchTarget(query)?.let { searchTargets.add(it) }
setFirstItemQuickLaunch(searchTargets)
val adapterItems = transformSearchResults(searchTargets)
@@ -8,16 +8,15 @@ import app.lawnchair.search.adapter.CALCULATOR
import app.lawnchair.search.adapter.CONTACT
import app.lawnchair.search.adapter.ERROR
import app.lawnchair.search.adapter.FILES
import app.lawnchair.search.adapter.GenerateSearchTarget
import app.lawnchair.search.adapter.HEADER_JUSTIFY
import app.lawnchair.search.adapter.HISTORY
import app.lawnchair.search.adapter.LOADING
import app.lawnchair.search.adapter.SETTINGS
import app.lawnchair.search.adapter.SPACE
import app.lawnchair.search.adapter.SearchResult
import app.lawnchair.search.model.SearchResult
import app.lawnchair.search.adapter.SearchTargetCompat
import app.lawnchair.search.adapter.SearchTargetFactory
import app.lawnchair.search.adapter.WEB_SUGGESTION
import app.lawnchair.search.adapter.createSearchTarget
import app.lawnchair.search.algorithms.data.Calculation
import app.lawnchair.search.algorithms.data.ContactInfo
import app.lawnchair.search.algorithms.data.IFileInfo
@@ -58,7 +57,7 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
private val appState = LauncherAppState.getInstance(context)
private val resultHandler = Handler(Executors.MAIN_EXECUTOR.looper)
private val generateSearchTarget = GenerateSearchTarget(context)
private val searchTargetFactory = SearchTargetFactory(context)
private var hiddenApps: Set<String> = emptySet()
@@ -180,6 +179,8 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
}
}
private fun getAppSearchResults(
apps: MutableList<AppInfo>,
query: String,
@@ -208,16 +209,18 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
): Flow<List<BaseAllAppsAdapter.AdapterItem>> = flow {
val searchTargets = mutableListOf<SearchTargetCompat>()
searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE))
searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE))
if (useWebSuggestions) {
withContext(Dispatchers.IO) {
searchTargets.add(generateSearchTarget.getWebSearchItem(query, webSuggestionsProvider))
searchTargets.add(searchTargetFactory.createWebSearchTarget(query, webSuggestionsProvider))
}
}
generateSearchTarget.getMarketSearchItem(query)?.let { searchTargets.add(it) }
searchTargetFactory.createMarketSearchTarget(query)?.let { searchTargets.add(it) }
emit(transformSearchResults(searchTargets))
}
private fun parseAppSearchResults(
appResults: List<AppInfo>,
searchTargets: MutableList<SearchTargetCompat>,
@@ -225,19 +228,19 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
if (appResults.isNotEmpty()) {
if (appResults.size == 1) {
val singleAppResult = appResults.firstOrNull()
singleAppResult?.let { searchTargets.add(createSearchTarget(it, true)) }
singleAppResult?.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) }
if (context.isDefaultLauncher()) {
val shortcuts = singleAppResult?.let { searchUtils.getShortcuts(it, context) }
if (shortcuts != null) {
if (shortcuts.isNotEmpty()) {
searchTargets.addAll(shortcuts.map(::createSearchTarget))
searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget))
}
}
}
} else {
appResults.mapTo(searchTargets, ::createSearchTarget)
appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget)
}
searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE))
searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE))
}
}
@@ -249,11 +252,11 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
val suggestions = filterByType(localSearchResults, WEB_SUGGESTION)
if (suggestions.isNotEmpty()) {
val suggestionsHeader =
generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_suggestions))
searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_suggestions))
searchTargets.add(suggestionsHeader)
searchTargets.addAll(
suggestions.map {
generateSearchTarget.getSuggestionTarget(it.resultData as String, suggestionProvider)
searchTargetFactory.createWebSuggestionsTarget(it.resultData as String, suggestionProvider)
},
)
}
@@ -262,46 +265,46 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
val calcData = calculator?.resultData as? Calculation
if (calcData != null && calcData.isValid) {
val calculatorHeader =
generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_calculator))
searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_calculator))
searchTargets.add(calculatorHeader)
searchTargets.add(
generateSearchTarget.getCalculationTarget(calcData),
searchTargetFactory.createCalculatorTarget(calcData),
)
}
val contacts = filterByType(localSearchResults, CONTACT)
if (contacts.isNotEmpty()) {
val contactsHeader =
generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_contacts_from_device))
searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_contacts_from_device))
searchTargets.add(contactsHeader)
searchTargets.addAll(contacts.map { generateSearchTarget.getContactSearchItem(it.resultData as ContactInfo) })
searchTargets.addAll(contacts.map { searchTargetFactory.createContactsTarget(it.resultData as ContactInfo) })
}
val settings = filterByType(localSearchResults, SETTINGS)
if (settings.isNotEmpty()) {
val settingsHeader =
generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_settings_entry_from_device))
searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_settings_entry_from_device))
searchTargets.add(settingsHeader)
searchTargets.addAll(settings.mapNotNull { generateSearchTarget.getSettingSearchItem(it.resultData as SettingInfo) })
searchTargets.addAll(settings.mapNotNull { searchTargetFactory.createSettingsTarget(it.resultData as SettingInfo) })
}
// todo refactor to only show when search is first clicked
val recentKeyword = filterByType(localSearchResults, HISTORY)
if (recentKeyword.isNotEmpty()) {
val recentKeywordHeader = generateSearchTarget.getHeaderTarget(
val recentKeywordHeader = searchTargetFactory.createHeaderTarget(
context.getString(R.string.search_pref_result_history_title),
HEADER_JUSTIFY,
)
searchTargets.add(recentKeywordHeader)
searchTargets.addAll(recentKeyword.map { generateSearchTarget.getRecentKeywordTarget(it.resultData as RecentKeyword, suggestionProvider) })
searchTargets.addAll(recentKeyword.map { searchTargetFactory.createSearchHistoryTarget(it.resultData as RecentKeyword, suggestionProvider) })
}
val files = filterByType(localSearchResults, FILES)
if (files.isNotEmpty()) {
val filesHeader =
generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_files))
searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_files))
searchTargets.add(filesHeader)
searchTargets.addAll(files.map { generateSearchTarget.getFileInfoSearchItem(it.resultData as IFileInfo) })
searchTargets.addAll(files.map { searchTargetFactory.createFilesTarget(it.resultData as IFileInfo) })
}
}
@@ -1,4 +1,4 @@
package app.lawnchair.search.adapter
package app.lawnchair.search.model
data class SearchResult(
val resultType: String,
@@ -1,4 +1,4 @@
package app.lawnchair.search.adapter
package app.lawnchair.search.model
interface SearchResultActionCallBack {
fun action()