diff --git a/flexera/res/drawable/ic_flexera_hide.xml b/flexera/res/drawable/ic_flexera_hide.xml
new file mode 100644
index 0000000000..414ab1ea06
--- /dev/null
+++ b/flexera/res/drawable/ic_flexera_hide.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/flexera/res/values/flexera_strings.xml b/flexera/res/values/flexera_strings.xml
index 5a23abfc49..7d1e245415 100644
--- a/flexera/res/values/flexera_strings.xml
+++ b/flexera/res/values/flexera_strings.xml
@@ -93,6 +93,9 @@
Slide up or down along the alphabet\'s edge of the home screen to open the app list at a letter. Swipe up and hold does the same from anywhere.
+ Hide app
+
+ %1$s is hidden
Blur behind open folders
Blurs the home screen while a folder is open
Quick launch
diff --git a/flexera/src/app/flexera/drawer/HideApp.kt b/flexera/src/app/flexera/drawer/HideApp.kt
new file mode 100644
index 0000000000..6de740a78a
--- /dev/null
+++ b/flexera/src/app/flexera/drawer/HideApp.kt
@@ -0,0 +1,78 @@
+package app.flexera.drawer
+
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import app.lawnchair.LawnchairLauncher
+import app.lawnchair.preferences2.PreferenceManager2
+import app.lawnchair.preferences2.firstCached
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.R
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.views.Snackbar
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * "Hide app" in the menu a long press on an app opens in the app list.
+ *
+ * It puts the app on the hidden list Lawnchair already has: the one the app list and its search
+ * leave out, kept under Settings > App drawer > Hidden apps, where an app is brought back. Before
+ * this, hiding meant going through that settings page or through Customize. A message with Undo
+ * follows, because the app is gone from the list the moment it is hidden.
+ */
+object HideApp {
+
+ /**
+ * Offered for apps in the app list only: hiding does nothing to an icon on the home screen,
+ * so offering it there would promise something else. Not offered while the home screen is
+ * locked against changes, like Customize and Uninstall.
+ */
+ @JvmField
+ val SHORTCUT = SystemShortcut.Factory { launcher: LawnchairLauncher, itemInfo: ItemInfo, originalView: View ->
+ val component = itemInfo.targetComponent
+ when {
+ itemInfo.container != CONTAINER_ALL_APPS -> null
+ itemInfo.itemType != ITEM_TYPE_APPLICATION || component == null -> null
+ PreferenceManager2.getInstance(launcher).lockHomeScreen.firstCached() -> null
+ else -> Shortcut(launcher, itemInfo, originalView, ComponentKey(component, itemInfo.user))
+ }
+ }
+
+ private class Shortcut(
+ launcher: LawnchairLauncher,
+ itemInfo: ItemInfo,
+ originalView: View,
+ private val app: ComponentKey,
+ ) : SystemShortcut(
+ R.drawable.ic_flexera_hide,
+ R.string.flexera_hide_app,
+ launcher,
+ itemInfo,
+ originalView,
+ ) {
+ override fun onClick(view: View) {
+ val launcher = mTarget
+ val name = mItemInfo.title?.toString().orEmpty()
+ dismissTaskMenuView()
+ setHidden(launcher, app, hidden = true)
+ Snackbar.show(
+ launcher,
+ launcher.getString(R.string.flexera_app_hidden, name),
+ R.string.undo,
+ null,
+ ) { setHidden(launcher, app, hidden = false) }
+ }
+ }
+
+ private fun setHidden(launcher: LawnchairLauncher, app: ComponentKey, hidden: Boolean) {
+ val hiddenApps = PreferenceManager2.getInstance(launcher).hiddenApps
+ val key = app.toString()
+ launcher.lifecycleScope.launch {
+ val now = hiddenApps.get().first()
+ hiddenApps.set(if (hidden) now + key else now - key)
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
index 77264a141c..05a5acc0ca 100644
--- a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
+++ b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
@@ -283,7 +283,8 @@ class LawnchairLauncher : QuickstepLauncher() {
super.getSupportedShortcuts(container),
Stream.concat(
Stream.of(LawnchairShortcut.UNINSTALL, LawnchairShortcut.CUSTOMIZE, LawnchairShortcut.OPEN_IN_STORE),
- Stream.of(LawnchairShortcut.PAUSE_APPS),
+ // Flexera Launcher: "Hide app", which its factory offers in the app list only.
+ Stream.of(LawnchairShortcut.PAUSE_APPS, app.flexera.drawer.HideApp.SHORTCUT),
),
)
diff --git a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
index a048c7761d..f9ddae8435 100644
--- a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
+++ b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
@@ -919,10 +919,16 @@ class PreferenceManager2 @Inject constructor(
.onEach { cachedPreferences = it }
.launchIn(scope)
+ // Flexera Launcher: in the five collectors below, distinctUntilChanged comes before
+ // drop(1). Upstream has them the other way round, which lets the first emission after
+ // the dropped one through whatever its value: the first write of any preference after
+ // the launcher started then reloaded the model, rebuilt the grid three times and
+ // recreated the launcher. Hiding an app from the app list is such a write, made while
+ // the owner looks at the launcher.
initializeIconShape(iconShape.firstCached(this))
iconShape.get()
- .drop(1)
.distinctUntilChanged()
+ .drop(1)
.onEach { shape ->
initializeIconShape(shape)
L3ThemeManager.INSTANCE.get(context)
@@ -931,14 +937,14 @@ class PreferenceManager2 @Inject constructor(
.launchIn(scope)
hotseatMode.get()
- .drop(1)
.distinctUntilChanged()
+ .drop(1)
.onEach { reloadHelper.restart() }
.launchIn(scope)
isHotseatEnabled.get()
- .drop(1)
.distinctUntilChanged()
+ .drop(1)
.onEach {
reloadHelper.recreate()
reloadHelper.reloadGrid()
@@ -946,14 +952,14 @@ class PreferenceManager2 @Inject constructor(
.launchIn(scope)
hotseatQsbProvider.get()
- .drop(1)
.distinctUntilChanged()
+ .drop(1)
.onEach { reloadHelper.recreate() }
.launchIn(scope)
enableLabelInDock.get()
- .drop(1)
.distinctUntilChanged()
+ .drop(1)
.onEach { reloadHelper.reloadGrid() }
.launchIn(scope)
}