diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchAdapterItem.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchAdapterItem.kt index f9fb3b1712..a956901d88 100644 --- a/lawnchair/src/app/lawnchair/search/adapter/SearchAdapterItem.kt +++ b/lawnchair/src/app/lawnchair/search/adapter/SearchAdapterItem.kt @@ -20,6 +20,12 @@ data class SearchAdapterItem( return other is SearchAdapterItem && other.searchTarget.id == searchTarget.id } + override fun isContentSame(other: BaseAllAppsAdapter.AdapterItem): Boolean { + if (other !is SearchAdapterItem) return false + return other.searchTarget == searchTarget && + other.viewType == viewType + } + fun setRippleEffect(child: View) { val shape = RoundRectShape(background?.cornerRadii, null, null) val shapeDrawable = ShapeDrawable(shape) diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt index 6735906dbb..2ccc03ad6b 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt @@ -84,18 +84,20 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c val searchTargets = mutableListOf() if (appResults.isNotEmpty()) { - appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget) - if (appResults.size == 1 && context.isDefaultLauncher()) { val singleAppResult = appResults.firstOrNull() val shortcuts = singleAppResult?.let { SearchUtils.getShortcuts(it, context) } - if (shortcuts != null) { - if (shortcuts.isNotEmpty()) { - searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) - singleAppResult.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) } - searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget)) - } + if (shortcuts != null && shortcuts.isNotEmpty()) { + // Show app as a row alongside its shortcuts (no duplicate icon) + singleAppResult.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) } + searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget)) + } else { + // No shortcuts: show as icon + appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget) } + } else { + // Multiple results: show as icons + appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget) } searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) } diff --git a/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt b/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt index e3a393e0bb..176d4ade2f 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/engine/SectionBuilder.kt @@ -199,6 +199,8 @@ data object AppsAndShortcutsSectionBuilder : SectionBuilder { if (appResultCount == 1 && shortcuts.isNotEmpty()) { val singleApp = apps.first() + targets.add(factory.createAppSearchTarget(singleApp.data, asRow = false)) + targets.add(factory.createHeaderTarget(SPACE)) targets.add(factory.createAppSearchTarget(singleApp.data, asRow = true)) targets.addAll(shortcuts.map { factory.createShortcutTarget(it.data) }) } else { diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/search/FileSearchProvider.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/search/FileSearchProvider.kt index f3a096e32a..e6f31cbee2 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/search/FileSearchProvider.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/search/FileSearchProvider.kt @@ -53,6 +53,7 @@ import app.lawnchair.ui.preferences.components.controls.SliderPreference import app.lawnchair.ui.preferences.components.controls.SwitchPreference import app.lawnchair.ui.preferences.components.layout.ExpandAndShrink import app.lawnchair.ui.preferences.components.layout.PreferenceGroup +import app.lawnchair.ui.preferences.components.layout.PreferenceGroupScope import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate import app.lawnchair.ui.theme.dividerColor import app.lawnchair.ui.util.isPlayStoreFlavor @@ -143,7 +144,6 @@ fun FileSearchProvider( onPermissionRequest = viewModel::refreshAccessStates, alwaysEnabled = allFilesAccessAdapter.state.value && allFilesAccessState == FileAccessState.Full, ) - GenericAccessSetting( adapter = prefs.searchResultAudio.getAdapter(), requiredPermission = android.Manifest.permission.READ_MEDIA_AUDIO, @@ -173,7 +173,7 @@ fun FileSearchProvider( @RequiresApi(Build.VERSION_CODES.R) @Composable -private fun ManageExternalStorageSetting( +private fun PreferenceGroupScope.ManageExternalStorageSetting( accessState: FileAccessState, adapter: PreferenceAdapter, onPermissionRequest: () -> Unit, @@ -182,31 +182,35 @@ private fun ManageExternalStorageSetting( var showPermissionDialog by remember { mutableStateOf(false) } if (accessState == FileAccessState.Full) { - SwitchPreference( - adapter = adapter, - label = stringResource(R.string.search_pref_result_all_files_title), - modifier = modifier, - ) + Item { + SwitchPreference( + adapter = adapter, + label = stringResource(R.string.search_pref_result_all_files_title), + modifier = modifier, + ) + } } else { - TwoTargetSwitchPreference( - label = stringResource(R.string.search_pref_result_all_files_title), - description = stringResource(R.string.permissions_needed), - checked = false, - onCheckedChange = { - if (accessState == FileAccessState.Denied) { - showPermissionDialog = true - } else { - adapter.onChange(it) - } - }, - onClick = { - if (accessState == FileAccessState.Denied) { - showPermissionDialog = true - } - }, - switchEnabled = !isPlayStoreFlavor() && (accessState != FileAccessState.Denied), - modifier = modifier, - ) + Item { + TwoTargetSwitchPreference( + label = stringResource(R.string.search_pref_result_all_files_title), + description = stringResource(R.string.permissions_needed), + checked = false, + onCheckedChange = { + if (accessState == FileAccessState.Denied) { + showPermissionDialog = true + } else { + adapter.onChange(it) + } + }, + onClick = { + if (accessState == FileAccessState.Denied) { + showPermissionDialog = true + } + }, + switchEnabled = !isPlayStoreFlavor() && (accessState != FileAccessState.Denied), + modifier = modifier, + ) + } } if (showPermissionDialog) { @@ -220,7 +224,7 @@ private fun ManageExternalStorageSetting( @RequiresApi(Build.VERSION_CODES.TIRAMISU) @OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3ExpressiveApi::class) @Composable -private fun VisualMediaSetting( +private fun PreferenceGroupScope.VisualMediaSetting( accessState: FileAccessState, adapter: PreferenceAdapter, onPermissionRequest: () -> Unit, @@ -240,33 +244,37 @@ private fun VisualMediaSetting( var showPartialAccessDialog by remember { mutableStateOf(false) } if (alwaysEnabled || accessState == FileAccessState.Full) { - SwitchPreference( - checked = alwaysEnabled || adapter.state.value, - onCheckedChange = adapter::onChange, - label = stringResource(R.string.search_pref_result_visual_media_title), - enabled = !alwaysEnabled, - ) + Item { + SwitchPreference( + checked = alwaysEnabled || adapter.state.value, + onCheckedChange = adapter::onChange, + label = stringResource(R.string.search_pref_result_visual_media_title), + enabled = !alwaysEnabled, + ) + } } else { - TwoTargetSwitchPreference( - label = stringResource(R.string.search_pref_result_visual_media_title), - description = stringResource(R.string.permissions_needed), - checked = (adapter.state.value && accessState != FileAccessState.Denied), - onCheckedChange = { - if (accessState == FileAccessState.Denied) { - showPermissionDialog = true - } else { - adapter.onChange(it) - } - }, - onClick = { - if (accessState == FileAccessState.Denied) { - showPermissionDialog = true - } else if (accessState == FileAccessState.Partial) { - showPartialAccessDialog = true - } - }, - switchEnabled = accessState != FileAccessState.Denied, - ) + Item { + TwoTargetSwitchPreference( + label = stringResource(R.string.search_pref_result_visual_media_title), + description = stringResource(R.string.permissions_needed), + checked = (adapter.state.value && accessState != FileAccessState.Denied), + onCheckedChange = { + if (accessState == FileAccessState.Denied) { + showPermissionDialog = true + } else { + adapter.onChange(it) + } + }, + onClick = { + if (accessState == FileAccessState.Denied) { + showPermissionDialog = true + } else if (accessState == FileAccessState.Partial) { + showPartialAccessDialog = true + } + }, + switchEnabled = accessState != FileAccessState.Denied, + ) + } } if (showPermissionDialog) { @@ -318,7 +326,7 @@ private fun VisualMediaSetting( @OptIn(ExperimentalPermissionsApi::class) @Composable -private fun GenericAccessSetting( +private fun PreferenceGroupScope.GenericAccessSetting( adapter: PreferenceAdapter, requiredPermission: String, switchEnabled: (PermissionState) -> Boolean, @@ -336,23 +344,27 @@ private fun GenericAccessSetting( val context = LocalContext.current if (!alwaysEnabled && !switchEnabled(permission)) { - TwoTargetSwitchPreference( - label = label, - description = stringResource(R.string.permissions_needed), - switchEnabled = false, - checked = false, - onCheckedChange = {}, - onClick = { - showPermissionDialog = true - }, - ) + Item { + TwoTargetSwitchPreference( + label = label, + description = stringResource(R.string.permissions_needed), + switchEnabled = false, + checked = false, + onCheckedChange = {}, + onClick = { + showPermissionDialog = true + }, + ) + } } else { - SwitchPreference( - label = label, - checked = alwaysEnabled || adapter.state.value, - onCheckedChange = adapter::onChange, - enabled = !alwaysEnabled, - ) + Item { + SwitchPreference( + label = label, + checked = alwaysEnabled || adapter.state.value, + onCheckedChange = adapter::onChange, + enabled = !alwaysEnabled, + ) + } } if (showPermissionDialog) { diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index 0cb49d79db..98787c5252 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -1724,6 +1724,13 @@ public class ActivityAllAppsContainerView new ViewGroupFocusHelper(mRecyclerView)) : new FocusedItemDecorator( mRecyclerView); mRecyclerView.addItemDecoration(focusedItemDecorator); + // LC-Note: This is needed for highlight focused app decoration, this has some problem of it own but purely visual. + if (isSearch()) { + RecyclerView.ItemDecoration searchDecorator = getMainAdapterProvider().getDecorator(); + if (searchDecorator != null) { + mRecyclerView.addItemDecoration(searchDecorator); + } + } mOnFocusChangeListener = focusedItemDecorator.getFocusListener(); mAdapter.setIconFocusListener(mOnFocusChangeListener); applyPadding();