diff --git a/lawnchair/res/layout/search_result_empty_state.xml b/lawnchair/res/layout/search_result_empty_state.xml
new file mode 100644
index 0000000000..cfa14be56b
--- /dev/null
+++ b/lawnchair/res/layout/search_result_empty_state.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lawnchair/res/layout/search_result_search_settings.xml b/lawnchair/res/layout/search_result_search_settings.xml
new file mode 100644
index 0000000000..77d8917109
--- /dev/null
+++ b/lawnchair/res/layout/search_result_search_settings.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml
index 6ca35d67cb..6f5fe61575 100644
--- a/lawnchair/res/values/strings.xml
+++ b/lawnchair/res/values/strings.xml
@@ -725,7 +725,7 @@
-->
- Search
+ Search apps, web, and more
Search apps
No apps found matching \"%1$s\"
@@ -808,4 +808,8 @@
To search your files, grant storage permissions to Lawnchair
Grant permissions
Custom search engine name
+
+ Start searching
+ Find apps, contacts, and more. Your recent searches will appear here.
+ Find apps, contacts, and more.
diff --git a/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt b/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt
index c2e81c1a79..9e42123ad3 100644
--- a/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt
+++ b/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt
@@ -138,6 +138,10 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) :
with(input) {
addTextChangedListener {
+ if (input.text.toString().isEmpty()) {
+ searchAlgorithm?.doZeroStateSearch(this@AllAppsSearchInput)
+ }
+
actionButton.isVisible = !it.isNullOrEmpty()
micIcon.isVisible = shouldShowIcons && voiceIntent != null && it.isNullOrEmpty()
lensIcon.isVisible = shouldShowIcons && supportsLens && lensIntent != null && it.isNullOrEmpty()
diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultEmptyState.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultEmptyState.kt
new file mode 100644
index 0000000000..688394846a
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultEmptyState.kt
@@ -0,0 +1,48 @@
+package app.lawnchair.allapps.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.ViewCompat
+import app.lawnchair.search.adapter.SearchTargetCompat
+import app.lawnchair.search.model.SearchResultActionCallBack
+import app.lawnchair.theme.color.tokens.ColorTokens
+import com.android.launcher3.R
+import com.android.launcher3.util.Themes
+
+class SearchResultEmptyState(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs),
+ SearchResultView {
+
+ private lateinit var icon: ImageView
+ private lateinit var title: TextView
+ private lateinit var subtitle: TextView
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ title = ViewCompat.requireViewById(this, R.id.empty_state_title)
+ subtitle = ViewCompat.requireViewById(this, R.id.empty_state_subtitle)
+ icon = ViewCompat.requireViewById(this, R.id.empty_state_icon)
+
+ subtitle.setTextColor(Themes.getAttrColor(context, android.R.attr.textColorTertiary))
+ icon.setColorFilter(ColorTokens.ColorAccent.resolveColor(context))
+ }
+
+ override val isQuickLaunch: Boolean = false
+ override fun launch(): Boolean = false
+
+ override fun bind(
+ target: SearchTargetCompat,
+ shortcuts: List,
+ callBack: SearchResultActionCallBack?,
+ ) {
+ val extras = target.extras
+ val titleRes = extras.getInt("titleRes")
+ val subtitleRes = extras.getInt("subtitleRes")
+
+ if (titleRes != 0) title.setText(titleRes)
+ if (subtitleRes != 0) subtitle.setText(subtitleRes)
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultSearchSettings.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultSearchSettings.kt
new file mode 100644
index 0000000000..28080d39bb
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultSearchSettings.kt
@@ -0,0 +1,39 @@
+package app.lawnchair.allapps.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import androidx.core.view.ViewCompat
+import app.lawnchair.search.adapter.SearchTargetCompat
+import app.lawnchair.search.model.SearchResultActionCallBack
+import app.lawnchair.ui.preferences.PreferenceActivity
+import app.lawnchair.ui.preferences.destinations.SearchRoute
+import app.lawnchair.ui.preferences.navigation.Search
+import com.android.launcher3.R
+
+class SearchResultSearchSettings(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs),
+ SearchResultView {
+
+ private lateinit var iconButton: ImageButton
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ iconButton = ViewCompat.requireViewById(this, R.id.search_settings)
+ iconButton.setOnClickListener {
+ context.startActivity(PreferenceActivity.createIntent(context, Search(SearchRoute.DRAWER_SEARCH)))
+ }
+ }
+
+ override val isQuickLaunch: Boolean = false
+ override fun launch(): Boolean = false
+
+ override fun bind(
+ target: SearchTargetCompat,
+ shortcuts: List,
+ callBack: SearchResultActionCallBack?,
+ ) {
+ // no-op
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt b/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt
index 855e9e9874..128b134cc9 100644
--- a/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt
+++ b/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt
@@ -36,6 +36,8 @@ class LawnchairSearchAdapterProvider(
append(SEARCH_RESULT_SETTINGS_TILE, R.layout.search_result_small_icon_row)
append(SEARCH_RESULT_RECENT_TILE, R.layout.search_result_small_icon_row)
append(SEARCH_RESULT_CALCULATOR, R.layout.search_result_tall_icon_row_calculator)
+ append(SEARCH_RESULT_EMPTY_STATE, R.layout.search_result_empty_state)
+ append(SEARCH_RESULT_SEARCH_SETTINGS, R.layout.search_result_search_settings)
}
private var quickLaunchItem: SearchResultView? = null
set(value) {
@@ -108,6 +110,8 @@ class LawnchairSearchAdapterProvider(
private const val SEARCH_RESULT_SETTINGS_TILE = 1 shl 18
private const val SEARCH_RESULT_RECENT_TILE = 1 shl 19
private const val SEARCH_RESULT_CALCULATOR = 1 shl 20
+ private const val SEARCH_RESULT_EMPTY_STATE = 1 shl 21
+ private const val SEARCH_RESULT_SEARCH_SETTINGS = 1 shl 22
val viewTypeMap = mapOf(
LayoutType.ICON_SINGLE_VERTICAL_TEXT to SEARCH_RESULT_ICON,
@@ -121,6 +125,8 @@ class LawnchairSearchAdapterProvider(
LayoutType.ICON_SLICE to SEARCH_RESULT_SETTINGS_TILE,
LayoutType.WIDGET_LIVE to SEARCH_RESULT_RECENT_TILE,
LayoutType.CALCULATOR to SEARCH_RESULT_CALCULATOR,
+ LayoutType.EMPTY_STATE to SEARCH_RESULT_EMPTY_STATE,
+ LayoutType.SEARCH_SETTINGS to SEARCH_RESULT_SEARCH_SETTINGS,
)
}
}
diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetCompat.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetCompat.kt
index a7489193b9..4609dec880 100644
--- a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetCompat.kt
+++ b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetCompat.kt
@@ -275,6 +275,8 @@ data class SearchTargetCompat(
RESULT_TYPE_FILE_TILE,
RESULT_TYPE_SETTING_TILE,
RESULT_TYPE_CALCULATOR,
+ RESULT_TYPE_EMPTY_RESULT,
+ RESULT_TYPE_SEARCH_SETTINGS,
],
)
@Retention(AnnotationRetention.SOURCE)
@@ -292,6 +294,8 @@ data class SearchTargetCompat(
const val RESULT_TYPE_FILE_TILE = 1 shl 8
const val RESULT_TYPE_SETTING_TILE = 1 shl 9
const val RESULT_TYPE_CALCULATOR = 1 shl 10
+ const val RESULT_TYPE_EMPTY_RESULT = 1 shl 11
+ const val RESULT_TYPE_SEARCH_SETTINGS = 1 shl 12
fun wrap(target: SearchTarget): SearchTargetCompat = SearchTargetCompat(target)
}
diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt
index 8d6ad8fe93..1abe6de13d 100644
--- a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt
+++ b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt
@@ -13,6 +13,7 @@ import android.provider.ContactsContract
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import app.lawnchair.allapps.views.SearchResultView
@@ -313,6 +314,53 @@ class SearchTargetFactory(
)
}
+ fun createEmptyStateTarget(
+ @StringRes titleRes: Int,
+ @StringRes subtitleRes: Int,
+ ): SearchTargetCompat {
+ val id = "empty_state:$titleRes"
+ // The action doesn't do anything, it's just for display.
+ val action = SearchActionCompat.Builder(id, "")
+ .setIntent(Intent())
+ .build()
+
+ return SearchTargetCompat.Builder(
+ SearchTargetCompat.RESULT_TYPE_EMPTY_RESULT,
+ LayoutType.EMPTY_STATE,
+ id,
+ ).apply {
+ setPackageName(EMPTY_STATE)
+ setUserHandle(Process.myUserHandle())
+ setSearchAction(action)
+ setExtras(
+ bundleOf(
+ "titleRes" to titleRes,
+ "subtitleRes" to subtitleRes,
+ ),
+ )
+ }.build()
+ }
+
+ fun createSearchSettingsTarget(): SearchTargetCompat {
+ val id = "action:search_settings"
+
+ // The action doesn't do anything, it's just for display.
+ val action = SearchActionCompat.Builder(id, "")
+ .setIntent(Intent())
+ .build()
+
+ return SearchTargetCompat.Builder(
+ SearchTargetCompat.RESULT_TYPE_SEARCH_SETTINGS,
+ LayoutType.SEARCH_SETTINGS,
+ id,
+ ).apply {
+ setPackageName(SEARCH_SETTINGS)
+ setUserHandle(Process.myUserHandle())
+ setSearchAction(action)
+ setExtras(Bundle())
+ }.build()
+ }
+
companion object {
private const val HASH_ALGORITHM = "SHA-256"
@@ -423,3 +471,5 @@ const val SHORTCUT = "shortcut"
const val HISTORY = "recent_keyword"
const val HEADER_JUSTIFY = "header_justify"
const val CALCULATOR = "calculator"
+const val EMPTY_STATE = "empty_state"
+const val SEARCH_SETTINGS = "search_settings"
diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairNewLocalSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairNewLocalSearchAlgorithm.kt
index 2a3365fea1..d0972c1e4d 100644
--- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairNewLocalSearchAlgorithm.kt
+++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairNewLocalSearchAlgorithm.kt
@@ -10,10 +10,12 @@ import app.lawnchair.search.algorithms.engine.ActionsSectionBuilder
import app.lawnchair.search.algorithms.engine.AppsAndShortcutsSectionBuilder
import app.lawnchair.search.algorithms.engine.CalculationSectionBuilder
import app.lawnchair.search.algorithms.engine.ContactsSectionBuilder
+import app.lawnchair.search.algorithms.engine.EmptyStateSectionBuilder
import app.lawnchair.search.algorithms.engine.FilesSectionBuilder
import app.lawnchair.search.algorithms.engine.HistorySectionBuilder
import app.lawnchair.search.algorithms.engine.SearchProvider
import app.lawnchair.search.algorithms.engine.SearchResult
+import app.lawnchair.search.algorithms.engine.SearchSettingsSectionBuilder
import app.lawnchair.search.algorithms.engine.SectionBuilder
import app.lawnchair.search.algorithms.engine.SettingsSectionBuilder
import app.lawnchair.search.algorithms.engine.WebSuggestionsSectionBuilder
@@ -26,7 +28,9 @@ import app.lawnchair.search.algorithms.engine.provider.SettingsSearchProvider
import app.lawnchair.search.algorithms.engine.provider.ShortcutSearchProvider
import app.lawnchair.search.algorithms.engine.provider.web.CustomWebSearchProvider
import app.lawnchair.search.algorithms.engine.provider.web.WebSuggestionProvider
+import com.android.internal.R.id.actions
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.R
import com.android.launcher3.allapps.BaseAllAppsAdapter
import com.android.launcher3.search.SearchCallback
import com.patrykmichalik.opto.core.firstBlocking
@@ -89,12 +93,40 @@ class LawnchairNewLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgori
override fun doZeroStateSearch(callback: SearchCallback) {
currentJob?.cancel()
currentJob = coroutineScope.launch {
+ val prefs = PreferenceManager.getInstance(context)
val prefs2 = PreferenceManager2.getInstance(context)
+
+ val historyEnabled = prefs.searchResulRecentSuggestion.get()
val maxHistory = prefs2.maxRecentResultCount.firstBlocking()
- val historyResults = historySearchProvider.getRecentKeywords(context, maxHistory)
+ val historyResults = if (historyEnabled) {
+ historySearchProvider.getRecentKeywords(context, maxHistory)
+ } else {
+ emptyList()
+ }
- val searchTargets = translateToSearchTargets(historyResults)
+ val resultsToTranslate = if (historyResults.isNotEmpty()) {
+ historyResults + listOf(SearchResult.Action.SearchSettings)
+ } else {
+ listOf(
+ if (historyEnabled) {
+ // State A: No History
+ SearchResult.Action.EmptyState(
+ titleRes = R.string.search_empty_state_title,
+ subtitleRes = R.string.search_empty_state_no_history_subtitle,
+ )
+ } else {
+ // State B: History Disabled
+ SearchResult.Action.EmptyState(
+ titleRes = R.string.search_empty_state_title,
+ subtitleRes = R.string.search_empty_state_history_disabled_subtitle,
+ )
+ },
+ SearchResult.Action.SearchSettings,
+ )
+ }
+
+ val searchTargets = translateToSearchTargets(resultsToTranslate)
val adapterItems = transformSearchResults(searchTargets)
withContext(Dispatchers.Main) {
callback.onSearchResult("", ArrayList(adapterItems))
@@ -111,8 +143,6 @@ class LawnchairNewLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgori
val prefs = PreferenceManager.getInstance(context)
val prefs2 = PreferenceManager2.getInstance(context)
- actions.add(SearchResult.Action.Divider())
-
if (prefs.searchResultStartPageSuggestion.get()) {
val provider = prefs2.webSuggestionProvider.firstBlocking()
val webProvider = provider.configure(context)
@@ -137,6 +167,9 @@ class LawnchairNewLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgori
actions.add(SearchResult.Action.MarketSearch(query = query))
}
+ actions.add(SearchResult.Action.SearchSettings)
+ actions.add(SearchResult.Action.SearchSettings)
+
return actions
}
@@ -149,6 +182,8 @@ class LawnchairNewLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgori
SettingsSectionBuilder,
HistorySectionBuilder,
ActionsSectionBuilder,
+ EmptyStateSectionBuilder,
+ SearchSettingsSectionBuilder,
)
private fun translateToSearchTargets(
diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt
index ced4184fd2..049a794c7c 100644
--- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt
+++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt
@@ -12,11 +12,13 @@ import app.lawnchair.search.adapter.SearchTargetCompat.Companion.RESULT_TYPE_APP
import app.lawnchair.search.adapter.SearchTargetCompat.Companion.RESULT_TYPE_SHORTCUT
import com.android.app.search.LayoutType.CALCULATOR
import com.android.app.search.LayoutType.EMPTY_DIVIDER
+import com.android.app.search.LayoutType.EMPTY_STATE
import com.android.app.search.LayoutType.HORIZONTAL_MEDIUM_TEXT
import com.android.app.search.LayoutType.ICON_HORIZONTAL_TEXT
import com.android.app.search.LayoutType.ICON_SINGLE_VERTICAL_TEXT
import com.android.app.search.LayoutType.ICON_SLICE
import com.android.app.search.LayoutType.PEOPLE_TILE
+import com.android.app.search.LayoutType.SEARCH_SETTINGS
import com.android.app.search.LayoutType.SMALL_ICON_HORIZONTAL_TEXT
import com.android.app.search.LayoutType.TEXT_HEADER
import com.android.app.search.LayoutType.THUMBNAIL
@@ -32,6 +34,12 @@ sealed class LawnchairSearchAlgorithm(
protected val context: Context,
) : SearchAlgorithm {
+ private val transparentBackground = SearchItemBackground(
+ context,
+ showBackground = false,
+ roundBottom = false,
+ roundTop = false,
+ )
private val iconBackground = SearchItemBackground(
context,
showBackground = false,
@@ -169,6 +177,8 @@ sealed class LawnchairSearchAlgorithm(
layoutType == ICON_SLICE -> getGroupedBackground(index, settingIndices)
layoutType == WIDGET_LIVE -> getGroupedBackground(index, recentIndices)
layoutType == CALCULATOR && calculator.isNotEmpty() -> normalBackground
+ layoutType == EMPTY_STATE -> transparentBackground
+ layoutType == SEARCH_SETTINGS -> transparentBackground
isFirst && isLast -> normalBackground
isFirst -> topBackground
isLast -> bottomBackground
diff --git a/lawnchair/src/app/lawnchair/search/algorithms/engine/SearchResult.kt b/lawnchair/src/app/lawnchair/search/algorithms/engine/SearchResult.kt
index 870cf80393..a38e45d8c6 100644
--- a/lawnchair/src/app/lawnchair/search/algorithms/engine/SearchResult.kt
+++ b/lawnchair/src/app/lawnchair/search/algorithms/engine/SearchResult.kt
@@ -1,9 +1,8 @@
package app.lawnchair.search.algorithms.engine
-import android.annotation.DrawableRes
import android.content.pm.ShortcutInfo
-import app.lawnchair.search.adapter.HEADER
-import app.lawnchair.search.adapter.SPACE
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import app.lawnchair.search.algorithms.data.ContactInfo
import app.lawnchair.search.algorithms.data.IFileInfo
import app.lawnchair.search.algorithms.data.RecentKeyword
@@ -32,7 +31,10 @@ sealed class SearchResult {
val searchUrl: String,
@DrawableRes val providerIconRes: Int,
) : Action()
- data class Header(val title: String, val pkg: String = HEADER) : Action()
- data class Divider(val pkg: String = SPACE) : Action()
+ data class EmptyState(
+ @StringRes val titleRes: Int,
+ @StringRes val subtitleRes: Int,
+ ) : Action()
+ data object SearchSettings : Action()
}
}
diff --git a/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt b/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt
index 289e83a92c..448303eba2 100644
--- a/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt
+++ b/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt
@@ -209,3 +209,42 @@ object AppsAndShortcutsSectionBuilder : SectionBuilder {
return targets
}
}
+
+object EmptyStateSectionBuilder : SectionBuilder {
+ override fun build(
+ context: Context,
+ factory: SearchTargetFactory,
+ results: List,
+ ): List {
+ val result = results.filterIsInstance()
+ val targets = mutableListOf()
+
+ result.firstOrNull()?.let {
+ targets.add(
+ factory.createEmptyStateTarget(
+ it.titleRes,
+ it.subtitleRes,
+ ),
+ )
+ }
+ return targets
+ }
+}
+
+object SearchSettingsSectionBuilder : SectionBuilder {
+ override fun build(
+ context: Context,
+ factory: SearchTargetFactory,
+ results: List,
+ ): List {
+ val result = results.filterIsInstance()
+ val targets = mutableListOf()
+
+ result.firstOrNull()?.let {
+ targets.add(
+ factory.createSearchSettingsTarget(),
+ )
+ }
+ return targets
+ }
+}
diff --git a/platform_frameworks_libs_systemui b/platform_frameworks_libs_systemui
index cc37c52766..87a0e7b617 160000
--- a/platform_frameworks_libs_systemui
+++ b/platform_frameworks_libs_systemui
@@ -1 +1 @@
-Subproject commit cc37c52766cd935a2440062492fe13017e7b3487
+Subproject commit 87a0e7b6176bd9d69cc68a70ffac511b0fb7ff20