From 9fe1a37ed658003867782e5cf570610f31dff6c8 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Wed, 5 Mar 2025 00:31:43 +0100 Subject: [PATCH] implement #45: show pinned shortcuts in app list --- .../de/jrpie/android/launcher/Application.kt | 12 +-- .../de/jrpie/android/launcher/Functions.kt | 33 ++++++-- .../android/launcher/actions/AppAction.kt | 4 +- .../launcher/actions/ShortcutAction.kt | 2 +- .../android/launcher/apps/AbstractAppInfo.kt | 22 +++++ .../launcher/apps/AbstractDetailedAppInfo.kt | 42 ++++++++++ .../jrpie/android/launcher/apps/AppFilter.kt | 28 ++++--- .../de/jrpie/android/launcher/apps/AppInfo.kt | 18 +--- .../android/launcher/apps/DetailedAppInfo.kt | 63 ++++++++------ .../apps/DetailedPinnedShortcutInfo.kt | 66 +++++++++++++++ .../shortcuts => apps}/PinnedShortcutInfo.kt | 6 +- .../LauncherPreferences$Config.java | 12 +-- .../launcher/preferences/Preferences.kt | 12 +-- .../launcher/preferences/legacy/Version1.kt | 17 +++- .../launcher/preferences/legacy/Version2.kt | 2 +- .../launcher/preferences/legacy/Version3.kt | 83 +++++++++++++++++++ .../serialization/PreferenceSerializers.kt | 52 ++++++++---- .../launcher/ui/PinShortcutActivity.kt | 24 +++++- .../ui/list/apps/AppsRecyclerAdapter.kt | 37 +++++---- .../ui/list/apps/ContextMenuActions.kt | 52 +++++++----- .../main/res/layout/activity_pin_shortcut.xml | 16 +++- app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 1 + 23 files changed, 467 insertions(+), 138 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt rename app/src/main/java/de/jrpie/android/launcher/{actions/shortcuts => apps}/PinnedShortcutInfo.kt (93%) create mode 100644 app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt index 810fceb..09229ab 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -15,15 +15,15 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData import androidx.preference.PreferenceManager import de.jrpie.android.launcher.actions.TorchManager -import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.DetailedAppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion import de.jrpie.android.launcher.preferences.resetPreferences class Application : android.app.Application() { - val apps = MutableLiveData>() + val apps = MutableLiveData>() val privateSpaceLocked = MutableLiveData() private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { @@ -82,10 +82,12 @@ class Application : android.app.Application() { } var torchManager: TorchManager? = null - private var customAppNames: HashMap? = null + private var customAppNames: HashMap? = null private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref -> if (pref == getString(R.string.settings_apps_custom_names_key)) { customAppNames = LauncherPreferences.apps().customNames() + } else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) { + loadApps() } } @@ -143,7 +145,7 @@ class Application : android.app.Application() { loadApps() } - fun getCustomAppNames(): HashMap { + fun getCustomAppNames(): HashMap { return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap()) .also { customAppNames = it } } diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt index 8fc95a3..fc4d6f8 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -24,9 +24,12 @@ import androidx.annotation.RequiresApi import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.ShortcutAction -import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER +import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo +import de.jrpie.android.launcher.apps.DetailedPinnedShortcutInfo +import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.apps.getPrivateSpaceUser import de.jrpie.android.launcher.apps.isPrivateSpaceSupported import de.jrpie.android.launcher.preferences.LauncherPreferences @@ -99,9 +102,10 @@ fun removeUnusedShortcuts(context: Context) { } val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager - val boundActions: Set = + val boundActions: MutableSet = Gesture.entries.mapNotNull { Action.forGesture(it) as? ShortcutAction }.map { it.shortcut } - .toSet() + .toMutableSet() + boundActions.addAll(LauncherPreferences.apps().pinnedShortcuts()) try { userManager.userProfiles.filter { !userManager.isQuietModeEnabled(it) }.forEach { profile -> getShortcuts(profile)?.groupBy { it.`package` }?.forEach { (p, shortcuts) -> @@ -135,9 +139,12 @@ fun openTutorial(context: Context) { /** * Load all apps. */ -fun getApps(packageManager: PackageManager, context: Context): MutableList { - val start = System.currentTimeMillis() - val loadList = mutableListOf() +fun getApps( + packageManager: PackageManager, + context: Context +): MutableList { + var start = System.currentTimeMillis() + val loadList = mutableListOf() val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager @@ -174,7 +181,7 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList= Build.VERSION_CODES.N_MR1) { + start = System.currentTimeMillis() + LauncherPreferences.apps().pinnedShortcuts() + ?.mapNotNull { DetailedPinnedShortcutInfo.fromPinnedShortcutInfo(it, context) } + ?.let { + end = System.currentTimeMillis() + Log.i(LOG_TAG, "${it.size} shortcuts loaded (${end - start}ms)") + loadList.addAll(it) + } + } return loadList } diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt index 90145aa..1446b13 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt @@ -11,7 +11,7 @@ import android.graphics.drawable.Drawable import android.util.Log import de.jrpie.android.launcher.R import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER +import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.ui.list.apps.openSettings import kotlinx.serialization.SerialName @@ -67,7 +67,7 @@ class AppAction(val app: AppInfo) : Action { } override fun getIcon(context: Context): Drawable? { - return DetailedAppInfo.fromAppInfo(app, context)?.icon + return DetailedAppInfo.fromAppInfo(app, context)?.getIcon(context) } override fun isAvailable(context: Context): Boolean { diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt index 8517b1a..a89f9e2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt @@ -6,7 +6,7 @@ import android.content.pm.LauncherApps import android.graphics.Rect import android.graphics.drawable.Drawable import android.os.Build -import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo +import de.jrpie.android.launcher.apps.PinnedShortcutInfo import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt new file mode 100644 index 0000000..dd60752 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt @@ -0,0 +1,22 @@ +package de.jrpie.android.launcher.apps + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +/** + * This interface is implemented by [AppInfo] and [PinnedShortcutInfo]. + */ +@Serializable +sealed interface AbstractAppInfo { + fun serialize(): String { + return Json.encodeToString(this) + } + companion object { + const val INVALID_USER = -1 + + fun deserialize(serialized: String): AbstractAppInfo { + return Json.decodeFromString(serialized) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt new file mode 100644 index 0000000..9c7413d --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt @@ -0,0 +1,42 @@ +package de.jrpie.android.launcher.apps + +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.util.Log +import de.jrpie.android.launcher.Application +import de.jrpie.android.launcher.actions.Action +import de.jrpie.android.launcher.preferences.LauncherPreferences + +/** + * This interface is implemented by [DetailedAppInfo] and [DetailedPinnedShortcutInfo] + */ +sealed interface AbstractDetailedAppInfo { + fun getRawInfo(): AbstractAppInfo + fun getLabel(): String + fun getIcon(context: Context): Drawable + fun getUser(context: Context): UserHandle + fun isPrivate(): Boolean + fun isRemovable(): Boolean + fun getAction(): Action + + + fun getCustomLabel(context: Context): String { + val map = (context.applicationContext as? Application)?.getCustomAppNames() + return map?.get(getRawInfo()) ?: getLabel() + } + + + fun setCustomLabel(label: CharSequence?) { + Log.i("Launcher", "Setting custom label for ${this.getRawInfo()} to ${label}.") + val map = LauncherPreferences.apps().customNames() ?: HashMap() + + if (label.isNullOrEmpty()) { + map.remove(getRawInfo()) + } else { + map[getRawInfo()] = label.toString() + } + LauncherPreferences.apps().customNames(map) + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt index ecc7eaa..ca387c0 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt @@ -6,6 +6,7 @@ import android.os.Build import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.Gesture +import de.jrpie.android.launcher.actions.ShortcutAction import de.jrpie.android.launcher.preferences.LauncherPreferences import java.util.Locale import kotlin.text.Regex.Companion.escape @@ -18,13 +19,14 @@ class AppFilter( var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE ) { - operator fun invoke(apps: List): List { + operator fun invoke(apps: List): List { var apps = - apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) } + apps.sortedBy { app -> app.getCustomLabel(context).lowercase(Locale.ROOT) } val hidden = LauncherPreferences.apps().hidden() ?: setOf() val favorites = LauncherPreferences.apps().favorites() ?: setOf() - val private = apps.filter { it.isPrivateSpaceApp }.map { it.app }.toSet() + val private = apps.filter { it.isPrivate() } + .map { it.getRawInfo() }.toSet() apps = apps.filter { info -> favoritesVisibility.predicate(favorites, info) @@ -35,9 +37,13 @@ class AppFilter( if (LauncherPreferences.apps().hideBoundApps()) { val boundApps = Gesture.entries .filter(Gesture::isEnabled) - .mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app } + .mapNotNull { g -> Action.forGesture(g) } + .mapNotNull { + (it as? AppAction)?.app + ?: (it as? ShortcutAction)?.shortcut + } .toSet() - apps = apps.filterNot { info -> boundApps.contains(info.app) } + apps = apps.filterNot { info -> boundApps.contains(info.getRawInfo()) } } // normalize text for search @@ -57,11 +63,11 @@ class AppFilter( if (query.isEmpty()) { return apps } else { - val r: MutableList = ArrayList() - val appsSecondary: MutableList = ArrayList() + val r: MutableList = ArrayList() + val appsSecondary: MutableList = ArrayList() val normalizedQuery: String = normalize(query) for (item in apps) { - val itemLabel: String = normalize(item.getCustomLabel(context).toString()) + val itemLabel: String = normalize(item.getCustomLabel(context)) if (itemLabel.startsWith(normalizedQuery)) { r.add(item) @@ -77,11 +83,11 @@ class AppFilter( companion object { enum class AppSetVisibility( - val predicate: (set: Set, DetailedAppInfo) -> Boolean + val predicate: (set: Set, AbstractDetailedAppInfo) -> Boolean ) { VISIBLE({ _, _ -> true }), - HIDDEN({ set, appInfo -> !set.contains(appInfo.app) }), - EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }), + HIDDEN({ set, appInfo -> !set.contains(appInfo.getRawInfo()) }), + EXCLUSIVE({ set, appInfo -> set.contains(appInfo.getRawInfo()) }), ; } diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt index 21614f8..944cfaa 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt @@ -4,21 +4,18 @@ import android.app.Service import android.content.Context import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherApps +import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.getUserFromId +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json /** * Represents an app installed on the users device. * Contains the minimal amount of data required to identify the app. */ @Serializable -class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) { - - fun serialize(): String { - return Json.encodeToString(this) - } +@SerialName("app") +class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER): AbstractAppInfo { override fun equals(other: Any?): Boolean { if(other is AppInfo) { @@ -47,11 +44,4 @@ class AppInfo(val packageName: String, val activityName: String?, val user: Int return "AppInfo {package=$packageName, activity=$activityName, user=$user}" } - companion object { - const val INVALID_USER = -1 - - fun deserialize(serialized: String): AppInfo { - return Json.decodeFromString(serialized) - } - } } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt index d77bf93..76f7fbb 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt @@ -4,20 +4,21 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.LauncherActivityInfo import android.graphics.drawable.Drawable -import android.util.Log -import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.preferences.LauncherPreferences +import android.os.UserHandle +import de.jrpie.android.launcher.actions.Action +import de.jrpie.android.launcher.actions.AppAction +import de.jrpie.android.launcher.getUserFromId /** * Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows. */ class DetailedAppInfo( - val app: AppInfo, - val label: CharSequence, - val icon: Drawable, - val isPrivateSpaceApp: Boolean, - val isSystemApp: Boolean = false, -) { + private val app: AppInfo, + private val label: CharSequence, + private val icon: Drawable, + private val privateSpace: Boolean, + private val removable: Boolean = true, +): AbstractDetailedAppInfo { constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this( AppInfo( @@ -28,29 +29,41 @@ class DetailedAppInfo( activityInfo.label, activityInfo.getBadgedIcon(0), private, - activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0 + // App can be uninstalled iff it is not a system app + activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0 ) - fun getCustomLabel(context: Context): CharSequence { - val map = (context.applicationContext as? Application)?.getCustomAppNames() ?: return label - return map[app] ?: label + + override fun getLabel(): String { + return label.toString() } - fun setCustomLabel(label: CharSequence?) { - - Log.i("Launcher", "Setting custom label for ${this.app} to ${label}.") - val map = LauncherPreferences.apps().customNames() ?: HashMap() - - if (label.isNullOrEmpty()) { - map.remove(app) - } else { - map[app] = label.toString() - } - - LauncherPreferences.apps().customNames(map) + override fun getIcon(context: Context): Drawable { + return icon } + override fun getRawInfo(): AppInfo { + return app + } + + override fun getUser(context: Context): UserHandle { + return getUserFromId(app.user, context) + } + + override fun isPrivate(): Boolean { + return privateSpace + } + + override fun isRemovable(): Boolean { + return removable + } + + override fun getAction(): Action { + return AppAction(app) + } + + companion object { fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? { return appInfo.getLauncherActivityInfo(context)?.let { diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt new file mode 100644 index 0000000..f66034d --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt @@ -0,0 +1,66 @@ +package de.jrpie.android.launcher.apps + +import android.app.Service +import android.content.Context +import android.content.pm.LauncherApps +import android.content.pm.ShortcutInfo +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.UserHandle +import androidx.annotation.RequiresApi +import de.jrpie.android.launcher.actions.Action +import de.jrpie.android.launcher.actions.ShortcutAction +import de.jrpie.android.launcher.getUserFromId + +@RequiresApi(Build.VERSION_CODES.N_MR1) +class DetailedPinnedShortcutInfo( + private val shortcutInfo: PinnedShortcutInfo, + private val label: String, + private val icon: Drawable, + private val privateSpace: Boolean +) : AbstractDetailedAppInfo { + + constructor(context: Context, shortcut: ShortcutInfo) : this( + PinnedShortcutInfo(shortcut), + shortcut.longLabel.toString(), + (context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps) + .getShortcutBadgedIconDrawable(shortcut, 0), + shortcut.userHandle == getPrivateSpaceUser(context) + ) + + override fun getRawInfo(): AbstractAppInfo { + return shortcutInfo + } + + override fun getLabel(): String { + return label + } + + override fun getIcon(context: Context): Drawable { + return icon + } + + override fun getUser(context: Context): UserHandle { + return getUserFromId(shortcutInfo.user, context) + } + + override fun isPrivate(): Boolean { + return privateSpace + } + + override fun isRemovable(): Boolean { + return true + } + + override fun getAction(): Action { + return ShortcutAction(shortcutInfo) + } + + companion object { + fun fromPinnedShortcutInfo(shortcutInfo: PinnedShortcutInfo, context: Context): DetailedPinnedShortcutInfo? { + return shortcutInfo.getShortcutInfo(context)?.let { + DetailedPinnedShortcutInfo(context, it) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/shortcuts/PinnedShortcutInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt similarity index 93% rename from app/src/main/java/de/jrpie/android/launcher/actions/shortcuts/PinnedShortcutInfo.kt rename to app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt index 796c737..a2815e5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/shortcuts/PinnedShortcutInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt @@ -1,4 +1,4 @@ -package de.jrpie.android.launcher.actions.shortcuts +package de.jrpie.android.launcher.apps import android.app.Service import android.content.ComponentName @@ -9,17 +9,19 @@ import android.content.pm.ShortcutInfo import android.os.Build import androidx.annotation.RequiresApi import de.jrpie.android.launcher.getUserFromId +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @RequiresApi(Build.VERSION_CODES.N_MR1) @Serializable +@SerialName("shortcut") class PinnedShortcutInfo( val id: String, val packageName: String, val activityName: String, val user: Int -) { +): AbstractAppInfo { constructor(info: ShortcutInfo) : this(info.id, info.`package`, info.activity?.className ?: "", info.userHandle.hashCode()) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java index 19362c2..9be85ab 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java @@ -5,8 +5,9 @@ import java.util.Set; import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.actions.lock.LockMethod; -import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer; -import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.Font; @@ -25,9 +26,10 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @Preference(name = "version_code", type = int.class, defaultValue = "-1"), }), @PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = { - @Preference(name = "favorites", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class), - @Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class), - @Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class), + @Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), + @Preference(name = "hidden", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), + @Preference(name = "pinned_shortcuts", type = Set.class, serializer = SetPinnedShortcutInfoPreferenceSerializer.class), + @Preference(name = "custom_names", type = HashMap.class, serializer = MapAbstractAppInfoStringPreferenceSerializer.class), @Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), @Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"), @Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"), diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt index 9460125..caa39b3 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt @@ -5,6 +5,8 @@ import android.util.Log import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.apps.AppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2 @@ -15,7 +17,7 @@ import de.jrpie.android.launcher.ui.HomeActivity * Increase when breaking changes are introduced and write an appropriate case in * `migratePreferencesToNewVersion` */ -const val PREFERENCE_VERSION = 3 +const val PREFERENCE_VERSION = 4 const val UNKNOWN_PREFERENCE_VERSION = -1 private const val TAG = "Launcher - Preferences" @@ -66,16 +68,16 @@ fun resetPreferences(context: Context) { LauncherPreferences.internal().versionCode(PREFERENCE_VERSION) - val hidden: MutableSet = mutableSetOf() + val hidden: MutableSet = mutableSetOf() val launcher = DetailedAppInfo.fromAppInfo( AppInfo( BuildConfig.APPLICATION_ID, HomeActivity::class.java.name, - AppInfo.INVALID_USER + INVALID_USER ), context ) - launcher?.app?.let { hidden.add(it) } - Log.i(TAG,"Hiding ${launcher?.app}") + launcher?.getRawInfo()?.let { hidden.add(it) } + Log.i(TAG,"Hiding ${launcher?.getRawInfo()}") LauncherPreferences.apps().hidden(hidden) Action.resetToDefaultActions(context) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt index a61980a..6408b70 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt @@ -5,15 +5,26 @@ import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER +import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION -import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer +import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.json.JSONException import org.json.JSONObject + +@Serializable +private class LegacyMapEntry(val key: AppInfo, val value: String) + +private fun serializeMapAppInfo(value: Map?): Set? { + return value?.map { (key, value) -> + Json.encodeToString(LegacyMapEntry(key, value)) + }?.toSet() +} + + val oldLauncherActionIds: Map = mapOf( Pair("launcher:settings", LauncherAction.SETTINGS), @@ -77,7 +88,7 @@ private fun Action.Companion.legacyFromPreference(id: String): Action? { private fun migrateAppInfoStringMap(key: String) { val preferences = LauncherPreferences.getSharedPreferences() - MapAppInfoStringPreferenceSerializer().serialize( + serializeMapAppInfo( preferences.getStringSet(key, setOf())?.mapNotNull { entry -> try { val obj = JSONObject(entry) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt index bcac3ae..4e6eae1 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt @@ -12,9 +12,9 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION * (see [PREFERENCE_VERSION]) */ fun migratePreferencesFromVersion2() { - assert(PREFERENCE_VERSION == 3) assert(LauncherPreferences.internal().versionCode() == 2) // previously there was no setting for this Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE) LauncherPreferences.internal().versionCode(3) + migratePreferencesFromVersion3() } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt new file mode 100644 index 0000000..d3bf7af --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt @@ -0,0 +1,83 @@ +package de.jrpie.android.launcher.preferences.legacy + +import android.content.SharedPreferences +import android.content.SharedPreferences.Editor +import de.jrpie.android.launcher.apps.AppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.preferences.LauncherPreferences +import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION +import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer +import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import java.util.HashSet + +/** + * Migrate preferences from version 3 (used until version 0.0.23) to the current format + * (see [PREFERENCE_VERSION]) + */ + + +fun deserializeSet(value: Set?): Set? { + return value?.map { + Json.decodeFromString(it) + }?.toHashSet() +} + +fun deserializeMap(value: Set?): HashMap? { + return value?.associateTo(HashMap()) { + val entry = Json.decodeFromString(it) + Pair(entry.key, entry.value) + } +} + +@Serializable +private class MapEntry(val key: AppInfo, val value: String) + +private fun migrateSetAppInfo(key: String, preferences: SharedPreferences, editor: Editor) { + try { + val serializer = SetAbstractAppInfoPreferenceSerializer() + val set = HashSet() + + deserializeSet(preferences.getStringSet(key, null))?.let { + set.addAll(it) + } + editor.putStringSet( + key, + serializer.serialize(set as java.util.Set) as Set? + ) + } catch (_: Exception) { + editor.putStringSet(key, null) + } + +} +private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences, editor: Editor ) { + try { + val serializer = MapAbstractAppInfoStringPreferenceSerializer() + val map = HashMap() + + deserializeMap(preferences.getStringSet(key, null))?.let { + map.putAll(it) + } + editor.putStringSet(key, serializer.serialize(map) as Set?) + } catch (_: Exception) { + editor.putStringSet(key, null) + } +} + +fun migratePreferencesFromVersion3() { + assert(PREFERENCE_VERSION == 4) + assert(LauncherPreferences.internal().versionCode() == 3) + + val preferences = LauncherPreferences.getSharedPreferences() + val editor = preferences.edit() + migrateSetAppInfo(LauncherPreferences.apps().keys().favorites(), preferences, editor) + migrateSetAppInfo(LauncherPreferences.apps().keys().hidden(), preferences, editor) + migrateSetAppInfo(LauncherPreferences.apps().keys().customNames(), preferences, editor) + + editor.apply() + + + + LauncherPreferences.internal().versionCode(4) +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt index 041fe4d..3e19daf 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt @@ -2,7 +2,8 @@ package de.jrpie.android.launcher.preferences.serialization -import de.jrpie.android.launcher.apps.AppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.apps.PinnedShortcutInfo import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import kotlinx.serialization.Serializable @@ -10,40 +11,61 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -// Serializers for [LauncherPreference$Config] + @Suppress("UNCHECKED_CAST") -class SetAppInfoPreferenceSerializer : - PreferenceSerializer?, java.util.Set?> { +class SetAbstractAppInfoPreferenceSerializer : + PreferenceSerializer?, java.util.Set?> { @Throws(PreferenceSerializationException::class) - override fun serialize(value: java.util.Set?): java.util.Set { - return value?.map(AppInfo::serialize)?.toHashSet() as java.util.Set + override fun serialize(value: java.util.Set?): java.util.Set { + return value?.map(AbstractAppInfo::serialize) + ?.toHashSet() as java.util.Set } @Throws(PreferenceSerializationException::class) - override fun deserialize(value: java.util.Set?): java.util.Set? { - return value?.map (java.lang.String::toString)?.map(AppInfo::deserialize)?.toHashSet() as? java.util.Set + override fun deserialize(value: java.util.Set?): java.util.Set? { + return value?.map(java.lang.String::toString)?.map(AbstractAppInfo::deserialize) + ?.toHashSet() as? java.util.Set } } @Suppress("UNCHECKED_CAST") -class MapAppInfoStringPreferenceSerializer : - PreferenceSerializer?, java.util.Set?> { - - @Serializable - private class MapEntry(val key: AppInfo, val value: String) +class SetPinnedShortcutInfoPreferenceSerializer : + PreferenceSerializer?, java.util.Set?> { + @Throws(PreferenceSerializationException::class) + override fun serialize(value: java.util.Set?): java.util.Set { + return value?.map { Json.encodeToString(it) } + ?.toHashSet() as java.util.Set + } @Throws(PreferenceSerializationException::class) - override fun serialize(value: java.util.HashMap?): java.util.Set? { + override fun deserialize(value: java.util.Set?): java.util.Set? { + return value?.map(java.lang.String::toString) + ?.map { Json.decodeFromString(it) } + ?.toHashSet() as? java.util.Set + } +} + + +@Suppress("UNCHECKED_CAST") +class MapAbstractAppInfoStringPreferenceSerializer : + PreferenceSerializer?, java.util.Set?> { + + @Serializable + private class MapEntry(val key: AbstractAppInfo, val value: String) + + @Throws(PreferenceSerializationException::class) + override fun serialize(value: java.util.HashMap?): java.util.Set? { return value?.map { (key, value) -> Json.encodeToString(MapEntry(key, value)) }?.toHashSet() as? java.util.Set } @Throws(PreferenceSerializationException::class) - override fun deserialize(value: java.util.Set?): java.util.HashMap? { + override fun deserialize(value: java.util.Set?): java.util.HashMap? { return value?.associateTo(HashMap()) { val entry = Json.decodeFromString(it.toString()) Pair(entry.key, entry.value) } } } + diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt index d19fe04..4d5d700 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt @@ -21,7 +21,7 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.ShortcutAction -import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo +import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.databinding.ActivityPinShortcutBinding import de.jrpie.android.launcher.preferences.LauncherPreferences @@ -29,6 +29,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { private lateinit var binding: ActivityPinShortcutBinding private var isBound = false + private var request: PinItemRequest? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -46,6 +47,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { val launcherApps = getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val request = launcherApps.getPinItemRequest(intent) + this.request = request if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) { finish() return @@ -84,6 +86,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { } binding.pinShortcutClose.setOnClickListener { finish() } + binding.pinShortcutButtonOk.setOnClickListener { finish() } } override fun onStart() { @@ -91,6 +94,24 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { super.onStart() } + override fun onDestroy() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + super.onDestroy() + return + } + if(binding.pinShortcutSwitchVisible.isChecked) { + if(!isBound) { + request?.accept() + } + request?.shortcutInfo?.let { + val set = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf() + set.add(PinnedShortcutInfo(it)) + LauncherPreferences.apps().pinnedShortcuts(set) + } + } + super.onDestroy() + } + override fun getTheme(): Resources.Theme { return modifyTheme(super.getTheme()) } @@ -124,5 +145,6 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { override fun getItemCount(): Int { return gestures.size } + } } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt index 2d8e1eb..0c0407e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt @@ -16,11 +16,10 @@ import androidx.recyclerview.widget.RecyclerView import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.REQUEST_CHOOSE_APP -import de.jrpie.android.launcher.actions.AppAction +import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo -import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.ListLayout import de.jrpie.android.launcher.ui.list.ListActivity @@ -47,7 +46,7 @@ class AppsRecyclerAdapter( RecyclerView.Adapter() { private val apps = (activity.applicationContext as Application).apps - private val appsListDisplayed: MutableList = mutableListOf() + private val appsListDisplayed: MutableList = mutableListOf() // temporarily disable auto launch var disableAutoLaunch: Boolean = false @@ -83,11 +82,11 @@ class AppsRecyclerAdapter( if (layout.useBadgedText) { appLabel = activity.packageManager.getUserBadgedLabel( appLabel, - getUserFromId(appsListDisplayed[i].app.user, activity) + appsListDisplayed[i].getUser(activity) ).toString() } - val appIcon = appsListDisplayed[i].icon + val appIcon = appsListDisplayed[i].getIcon(activity) viewHolder.textView.text = appLabel viewHolder.img.setImageDrawable(appIcon) @@ -118,22 +117,26 @@ class AppsRecyclerAdapter( @Suppress("SameReturnValue") private fun showOptionsPopup( viewHolder: ViewHolder, - appInfo: DetailedAppInfo + appInfo: AbstractDetailedAppInfo ): Boolean { //create the popup menu val popup = PopupMenu(activity, viewHolder.img) popup.inflate(R.menu.menu_app) - if (appInfo.isSystemApp) { + if (!appInfo.isRemovable()) { popup.menu.findItem(R.id.app_menu_delete).setVisible(false) } - if (LauncherPreferences.apps().hidden()?.contains(appInfo.app) == true) { + if (appInfo !is DetailedAppInfo) { + popup.menu.findItem(R.id.app_menu_info).setVisible(false) + } + + if (LauncherPreferences.apps().hidden()?.contains(appInfo.getRawInfo()) == true) { popup.menu.findItem(R.id.app_menu_hidden).setTitle(R.string.list_app_hidden_remove) } - if (LauncherPreferences.apps().favorites()?.contains(appInfo.app) == true) { + if (LauncherPreferences.apps().favorites()?.contains(appInfo.getRawInfo()) == true) { popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove) } @@ -141,19 +144,19 @@ class AppsRecyclerAdapter( popup.setOnMenuItemClickListener { when (it.itemId) { R.id.app_menu_delete -> { - appInfo.app.uninstall(activity); true + appInfo.getRawInfo().uninstall(activity); true } R.id.app_menu_info -> { - appInfo.app.openSettings(activity); true + (appInfo.getRawInfo() as? AppInfo)?.openSettings(activity); true } R.id.app_menu_favorite -> { - appInfo.app.toggleFavorite(); true + appInfo.getRawInfo().toggleFavorite(); true } R.id.app_menu_hidden -> { - appInfo.app.toggleHidden(root); true + appInfo.getRawInfo().toggleHidden(root); true } R.id.app_menu_rename -> { @@ -188,12 +191,12 @@ class AppsRecyclerAdapter( val appInfo = appsListDisplayed[pos] when (intention) { ListActivity.ListActivityIntention.VIEW -> { - AppAction(appInfo.app).invoke(activity, rect) + appInfo.getAction().invoke(activity, rect) } ListActivity.ListActivityIntention.PICK -> { val returnIntent = Intent() - AppAction(appInfo.app).writeToIntent(returnIntent) + appInfo.getAction().writeToIntent(returnIntent) returnIntent.putExtra("forGesture", forGesture) activity.setResult(REQUEST_CHOOSE_APP, returnIntent) activity.finish() @@ -211,8 +214,8 @@ class AppsRecyclerAdapter( && !disableAutoLaunch && LauncherPreferences.functionality().searchAutoLaunch() ) { - val info = appsListDisplayed[0] - AppAction(info.app).invoke(activity) + val app = appsListDisplayed[0] + app.getAction().invoke(activity) val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt index 9636dc2..c1f3406 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt @@ -1,5 +1,6 @@ package de.jrpie.android.launcher.ui.list.apps +import android.app.Activity import android.app.Service import android.content.Context import android.content.Intent @@ -15,7 +16,10 @@ import com.google.android.material.snackbar.Snackbar import de.jrpie.android.launcher.R import de.jrpie.android.launcher.REQUEST_UNINSTALL import de.jrpie.android.launcher.apps.AppInfo +import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo +import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.preferences.LauncherPreferences @@ -32,27 +36,33 @@ fun AppInfo.openSettings( } } -fun AppInfo.uninstall(activity: android.app.Activity) { - val packageName = this.packageName - val userId = this.user +fun AbstractAppInfo.uninstall(activity: Activity) { + if (this is AppInfo) { + val packageName = this.packageName + val userId = this.user - Log.i(LOG_TAG, "uninstalling $this") + Log.i(LOG_TAG, "uninstalling $this") - val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) - intent.data = Uri.parse("package:$packageName") - getUserFromId(userId, activity).let { user -> - intent.putExtra(Intent.EXTRA_USER, user) + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) + intent.data = Uri.parse("package:$packageName") + getUserFromId(userId, activity).let { user -> + intent.putExtra(Intent.EXTRA_USER, user) + } + + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) + activity.startActivityForResult( + intent, + REQUEST_UNINSTALL + ) + } else if(this is PinnedShortcutInfo) { + val pinned = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf() + pinned.remove(this) + LauncherPreferences.apps().pinnedShortcuts(pinned) } - - intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) - activity.startActivityForResult( - intent, - REQUEST_UNINSTALL - ) } -fun AppInfo.toggleFavorite() { - val favorites: MutableSet = +fun AbstractAppInfo.toggleFavorite() { + val favorites: MutableSet = LauncherPreferences.apps().favorites() ?: mutableSetOf() if (favorites.contains(this)) { @@ -69,8 +79,8 @@ fun AppInfo.toggleFavorite() { /** * @param view: used to show a snackbar letting the user undo the action */ -fun AppInfo.toggleHidden(view: View) { - val hidden: MutableSet = +fun AbstractAppInfo.toggleHidden(view: View) { + val hidden: MutableSet = LauncherPreferences.apps().hidden() ?: mutableSetOf() if (hidden.contains(this)) { hidden.remove(this) @@ -87,9 +97,9 @@ fun AppInfo.toggleHidden(view: View) { LauncherPreferences.apps().hidden(hidden) } -fun DetailedAppInfo.showRenameDialog(context: Context) { +fun AbstractDetailedAppInfo.showRenameDialog(context: Context) { AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { - setTitle(context.getString(R.string.dialog_rename_title, label)) + setTitle(context.getString(R.string.dialog_rename_title, getLabel())) setView(R.layout.dialog_rename_app) setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() } setPositiveButton(R.string.dialog_rename_ok) { d, _ -> @@ -102,7 +112,7 @@ fun DetailedAppInfo.showRenameDialog(context: Context) { }.create().also { it.show() }.apply { val input = findViewById(R.id.dialog_rename_app_edit_text) input?.setText(getCustomLabel(context)) - input?.hint = label + input?.hint = getLabel() } } diff --git a/app/src/main/res/layout/activity_pin_shortcut.xml b/app/src/main/res/layout/activity_pin_shortcut.xml index c401b42..5e10118 100644 --- a/app/src/main/res/layout/activity_pin_shortcut.xml +++ b/app/src/main/res/layout/activity_pin_shortcut.xml @@ -80,7 +80,6 @@ android:minHeight="40dp" tools:drawableLeft="@drawable/baseline_settings_24" tools:text="Shortcut name" /> - + + + +