From fb078151bf1a232a4ca68ad04e5bae7a71a7e5c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 21:55:09 +0000 Subject: [PATCH 1/3] Label the third-party Pixel Search provider as third-party Google now ships its own Pixel Search app, which Expressive cannot use because it only accepts Google-signed launchers. The existing provider opens rk.android.app.pixelsearch, so name it accordingly. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0165eR1KvBjhULmeV7mqDjbP --- android/lawnchair/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/lawnchair/res/values/strings.xml b/android/lawnchair/res/values/strings.xml index 348e43eac28..79b72271bd7 100644 --- a/android/lawnchair/res/values/strings.xml +++ b/android/lawnchair/res/values/strings.xml @@ -930,7 +930,7 @@ Brave GitHub YouTube - Pixel Search + Pixel Search (third-party) Yandex Fennec Firefox From d141c7603ef475e4a01cb85c46df8c8fcc99f16b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 21:56:46 +0000 Subject: [PATCH 2/3] Show drawer search results before slow providers finish combine() waited for every provider, including network web suggestions, before rendering anything, so typing in the drawer showed nothing until suggestions arrived or timed out. Seed each provider with an empty result so apps, shortcuts and calculations render immediately and the other sections fill in as they arrive, like Pixel search. Also compute the calculator and action rows once per query instead of per emission, and turn the calculator on by default. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0165eR1KvBjhULmeV7mqDjbP --- .../preferences/PreferenceManager.kt | 2 +- .../LawnchairLocalSearchAlgorithm.kt | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/android/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt b/android/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt index d8de1ecf975..b632fd5c0ed 100644 --- a/android/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt +++ b/android/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt @@ -141,7 +141,7 @@ class PreferenceManager @Inject constructor( val searchResultPeople = BoolPref("pref_searchResultPeople", false, recreate) val searchResultPixelTips = BoolPref("pref_searchResultPixelTips", false) val searchResultSettings = BoolPref("pref_searchResultSettings", false) - val searchResultCalculator = BoolPref("pref_searchResultCalculator", false) + val searchResultCalculator = BoolPref("pref_searchResultCalculator", true) val searchResultApps = BoolPref("pref_searchResultApps", true, recreate) val searchResultFilesToggle = BoolPref("pref_searchResultFiles", false, recreate) diff --git a/android/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt b/android/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt index 520dccb82ad..eb2812dce71 100644 --- a/android/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt +++ b/android/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt @@ -38,8 +38,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -68,17 +70,22 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm currentJob?.cancel() currentJob = coroutineScope.launch { + val calcResult = CalculatorSearchProvider.search(context, query) + .firstOrNull() + .orEmpty() + val actionResults = generateActionResults(query) + + // Seed every provider with an empty result so apps, shortcuts and calculations + // show as soon as they are ready. Without the seed, combine() waits for the + // slowest provider (web suggestions go over the network) before the first frame. val nonAppProvidersFlow = combine( - searchProviders.map { it.search(context, query) }, + searchProviders.map { it.search(context, query).onStart { emit(emptyList()) } }, ) { resultsArray -> resultsArray.toList().flatten() } - nonAppProvidersFlow.collect { nonAppResults -> - val calcResult = CalculatorSearchProvider.search(context, query) - .firstOrNull() - - val allResults = appResults + shortcutResults + (calcResult ?: emptyList()) + nonAppResults + generateActionResults(query) + nonAppProvidersFlow.collectLatest { nonAppResults -> + val allResults = appResults + shortcutResults + calcResult + nonAppResults + actionResults val searchTargets = translateToSearchTargets(query, allResults) setFirstItemQuickLaunch(searchTargets) From 82dce3d0a16553217916318c174dfb94e68cdb07 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 22:47:37 +0000 Subject: [PATCH 3/3] Keep the drawer search pill visible while focused The pill background faded to transparent as soon as the field gained focus or had text, so the search bar seemed to disappear when tapped. Keep it at full opacity, as Pixel does, and drop the focus tracking that only drove that fade. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0165eR1KvBjhULmeV7mqDjbP --- .../lawnchair/allapps/AllAppsSearchInput.kt | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/android/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt b/android/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt index dc979699ba8..e56ece54834 100644 --- a/android/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt +++ b/android/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt @@ -15,12 +15,9 @@ import android.util.AttributeSet import android.view.KeyEvent import android.view.MotionEvent import android.view.ViewTreeObserver -import android.view.ViewTreeObserver.OnGlobalFocusChangeListener import android.view.animation.DecelerateInterpolator import android.widget.FrameLayout import android.widget.TextView -import androidx.compose.animation.core.animateIntAsState -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf @@ -203,26 +200,6 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) : setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow) setContent { - var isFocused by remember(input) { mutableStateOf(input.hasFocus()) } - - // Yes, this is a bit hacky, but it's the only way to ensure that - // we can check if the input has focus in Compose without wrestling - // with multiple global variables or state changes - DisposableEffect(input) { - val focusListener = OnGlobalFocusChangeListener { _, _ -> - isFocused = input.hasFocus() - } - - val observer = input.viewTreeObserver - observer.addOnGlobalFocusChangeListener(focusListener) - - onDispose { - if (observer.isAlive) { - observer.removeOnGlobalFocusChangeListener(focusListener) - } - } - } - val searchProviderPref by prefs2.hotseatQsbProvider.asState() val searchProvider = remember(searchProviderPref, context) { getSearchProvider(context, searchProviderPref) @@ -253,9 +230,8 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) : ColorTokens.SearchboxHighlight.resolveColor(context) } - val backgroundAlpha by animateIntAsState( - if (isFocused || !queryEmpty) 0 else 100, - ) + // Keep the pill visible while focused or typing, as Pixel does. + val backgroundAlpha = 100 // Ignore other theme attributes to preserve existing behavior val style = buildQsbStyle(