Compare commits

..

1 commit

Author SHA1 Message Date
Hendika N.
f525d3a37d
Merge 941b06b258 into 8948b34243 2025-03-03 15:52:00 +01:00
31 changed files with 155 additions and 527 deletions

4
.github/FUNDING.yml vendored
View file

@ -1,3 +1,3 @@
# How you can support jrpie/Launcher # How you can support finnmglas/Launcher
custom: https://s.jrpie.de/launcher-donate custom: sponsor.finnmglas.com

View file

@ -15,15 +15,15 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import de.jrpie.android.launcher.actions.TorchManager import de.jrpie.android.launcher.actions.TorchManager
import de.jrpie.android.launcher.apps.AbstractAppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
import de.jrpie.android.launcher.preferences.resetPreferences import de.jrpie.android.launcher.preferences.resetPreferences
class Application : android.app.Application() { class Application : android.app.Application() {
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>() val apps = MutableLiveData<List<DetailedAppInfo>>()
val privateSpaceLocked = MutableLiveData<Boolean>() val privateSpaceLocked = MutableLiveData<Boolean>()
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
@ -82,12 +82,10 @@ class Application : android.app.Application() {
} }
var torchManager: TorchManager? = null var torchManager: TorchManager? = null
private var customAppNames: HashMap<AbstractAppInfo, String>? = null private var customAppNames: HashMap<AppInfo, String>? = null
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref -> private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref ->
if (pref == getString(R.string.settings_apps_custom_names_key)) { if (pref == getString(R.string.settings_apps_custom_names_key)) {
customAppNames = LauncherPreferences.apps().customNames() customAppNames = LauncherPreferences.apps().customNames()
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
loadApps()
} }
} }
@ -145,7 +143,7 @@ class Application : android.app.Application() {
loadApps() loadApps()
} }
fun getCustomAppNames(): HashMap<AbstractAppInfo, String> { fun getCustomAppNames(): HashMap<AppInfo, String> {
return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap()) return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap())
.also { customAppNames = it } .also { customAppNames = it }
} }

View file

@ -24,12 +24,9 @@ import androidx.annotation.RequiresApi
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.ShortcutAction import de.jrpie.android.launcher.actions.ShortcutAction
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo 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.getPrivateSpaceUser
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -102,10 +99,9 @@ fun removeUnusedShortcuts(context: Context) {
} }
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
val boundActions: MutableSet<PinnedShortcutInfo> = val boundActions: Set<PinnedShortcutInfo> =
Gesture.entries.mapNotNull { Action.forGesture(it) as? ShortcutAction }.map { it.shortcut } Gesture.entries.mapNotNull { Action.forGesture(it) as? ShortcutAction }.map { it.shortcut }
.toMutableSet() .toSet()
LauncherPreferences.apps().pinnedShortcuts()?.let { boundActions.addAll(it) }
try { try {
userManager.userProfiles.filter { !userManager.isQuietModeEnabled(it) }.forEach { profile -> userManager.userProfiles.filter { !userManager.isQuietModeEnabled(it) }.forEach { profile ->
getShortcuts(profile)?.groupBy { it.`package` }?.forEach { (p, shortcuts) -> getShortcuts(profile)?.groupBy { it.`package` }?.forEach { (p, shortcuts) ->
@ -139,12 +135,9 @@ fun openTutorial(context: Context) {
/** /**
* Load all apps. * Load all apps.
*/ */
fun getApps( fun getApps(packageManager: PackageManager, context: Context): MutableList<DetailedAppInfo> {
packageManager: PackageManager, val start = System.currentTimeMillis()
context: Context val loadList = mutableListOf<DetailedAppInfo>()
): MutableList<AbstractDetailedAppInfo> {
var start = System.currentTimeMillis()
val loadList = mutableListOf<AbstractDetailedAppInfo>()
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
@ -181,7 +174,7 @@ fun getApps(
i.addCategory(Intent.CATEGORY_LAUNCHER) i.addCategory(Intent.CATEGORY_LAUNCHER)
val allApps = packageManager.queryIntentActivities(i, 0) val allApps = packageManager.queryIntentActivities(i, 0)
for (ri in allApps) { for (ri in allApps) {
val app = AppInfo(ri.activityInfo.packageName, null, INVALID_USER) val app = AppInfo(ri.activityInfo.packageName, null, AppInfo.INVALID_USER)
val detailedAppInfo = DetailedAppInfo( val detailedAppInfo = DetailedAppInfo(
app, app,
ri.loadLabel(packageManager), ri.loadLabel(packageManager),
@ -193,18 +186,8 @@ fun getApps(
} }
loadList.sortBy { it.getCustomLabel(context).toString() } loadList.sortBy { it.getCustomLabel(context).toString() }
var end = System.currentTimeMillis() val end = System.currentTimeMillis()
Log.i(LOG_TAG, "${loadList.size} apps loaded (${end - start}ms)") Log.i(LOG_TAG, "${loadList.size} apps loaded (${end - start}ms)")
if (Build.VERSION.SDK_INT >= 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 return loadList
} }

View file

@ -11,7 +11,7 @@ import android.graphics.drawable.Drawable
import android.util.Log import android.util.Log
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.ui.list.apps.openSettings import de.jrpie.android.launcher.ui.list.apps.openSettings
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -67,7 +67,7 @@ class AppAction(val app: AppInfo) : Action {
} }
override fun getIcon(context: Context): Drawable? { override fun getIcon(context: Context): Drawable? {
return DetailedAppInfo.fromAppInfo(app, context)?.getIcon(context) return DetailedAppInfo.fromAppInfo(app, context)?.icon
} }
override fun isAvailable(context: Context): Boolean { override fun isAvailable(context: Context): Boolean {

View file

@ -6,7 +6,7 @@ import android.content.pm.LauncherApps
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package de.jrpie.android.launcher.apps package de.jrpie.android.launcher.actions.shortcuts
import android.app.Service import android.app.Service
import android.content.ComponentName import android.content.ComponentName
@ -9,19 +9,17 @@ import android.content.pm.ShortcutInfo
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.getUserFromId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@RequiresApi(Build.VERSION_CODES.N_MR1) @RequiresApi(Build.VERSION_CODES.N_MR1)
@Serializable @Serializable
@SerialName("shortcut")
class PinnedShortcutInfo( class PinnedShortcutInfo(
val id: String, val id: String,
val packageName: String, val packageName: String,
val activityName: String, val activityName: String,
val user: Int val user: Int
): AbstractAppInfo { ) {
constructor(info: ShortcutInfo) : this(info.id, info.`package`, info.activity?.className ?: "", info.userHandle.hashCode()) constructor(info: ShortcutInfo) : this(info.id, info.`package`, info.activity?.className ?: "", info.userHandle.hashCode())

View file

@ -1,22 +0,0 @@
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)
}
}
}

View file

@ -1,42 +0,0 @@
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<AbstractAppInfo, String>()
if (label.isNullOrEmpty()) {
map.remove(getRawInfo())
} else {
map[getRawInfo()] = label.toString()
}
LauncherPreferences.apps().customNames(map)
}
}

View file

@ -6,7 +6,6 @@ import android.os.Build
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.ShortcutAction
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import java.util.Locale import java.util.Locale
import kotlin.text.Regex.Companion.escape import kotlin.text.Regex.Companion.escape
@ -19,14 +18,13 @@ class AppFilter(
var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE
) { ) {
operator fun invoke(apps: List<AbstractDetailedAppInfo>): List<AbstractDetailedAppInfo> { operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> {
var apps = var apps =
apps.sortedBy { app -> app.getCustomLabel(context).lowercase(Locale.ROOT) } apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) }
val hidden = LauncherPreferences.apps().hidden() ?: setOf() val hidden = LauncherPreferences.apps().hidden() ?: setOf()
val favorites = LauncherPreferences.apps().favorites() ?: setOf() val favorites = LauncherPreferences.apps().favorites() ?: setOf()
val private = apps.filter { it.isPrivate() } val private = apps.filter { it.isPrivateSpaceApp }.map { it.app }.toSet()
.map { it.getRawInfo() }.toSet()
apps = apps.filter { info -> apps = apps.filter { info ->
favoritesVisibility.predicate(favorites, info) favoritesVisibility.predicate(favorites, info)
@ -37,13 +35,9 @@ class AppFilter(
if (LauncherPreferences.apps().hideBoundApps()) { if (LauncherPreferences.apps().hideBoundApps()) {
val boundApps = Gesture.entries val boundApps = Gesture.entries
.filter(Gesture::isEnabled) .filter(Gesture::isEnabled)
.mapNotNull { g -> Action.forGesture(g) } .mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app }
.mapNotNull {
(it as? AppAction)?.app
?: (it as? ShortcutAction)?.shortcut
}
.toSet() .toSet()
apps = apps.filterNot { info -> boundApps.contains(info.getRawInfo()) } apps = apps.filterNot { info -> boundApps.contains(info.app) }
} }
// normalize text for search // normalize text for search
@ -63,11 +57,11 @@ class AppFilter(
if (query.isEmpty()) { if (query.isEmpty()) {
return apps return apps
} else { } else {
val r: MutableList<AbstractDetailedAppInfo> = ArrayList() val r: MutableList<DetailedAppInfo> = ArrayList()
val appsSecondary: MutableList<AbstractDetailedAppInfo> = ArrayList() val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
val normalizedQuery: String = normalize(query) val normalizedQuery: String = normalize(query)
for (item in apps) { for (item in apps) {
val itemLabel: String = normalize(item.getCustomLabel(context)) val itemLabel: String = normalize(item.getCustomLabel(context).toString())
if (itemLabel.startsWith(normalizedQuery)) { if (itemLabel.startsWith(normalizedQuery)) {
r.add(item) r.add(item)
@ -83,11 +77,11 @@ class AppFilter(
companion object { companion object {
enum class AppSetVisibility( enum class AppSetVisibility(
val predicate: (set: Set<AbstractAppInfo>, AbstractDetailedAppInfo) -> Boolean val predicate: (set: Set<AppInfo>, DetailedAppInfo) -> Boolean
) { ) {
VISIBLE({ _, _ -> true }), VISIBLE({ _, _ -> true }),
HIDDEN({ set, appInfo -> !set.contains(appInfo.getRawInfo()) }), HIDDEN({ set, appInfo -> !set.contains(appInfo.app) }),
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.getRawInfo()) }), EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }),
; ;
} }

View file

@ -4,18 +4,21 @@ import android.app.Service
import android.content.Context import android.content.Context
import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps import android.content.pm.LauncherApps
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.getUserFromId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/** /**
* Represents an app installed on the users device. * Represents an app installed on the users device.
* Contains the minimal amount of data required to identify the app. * Contains the minimal amount of data required to identify the app.
*/ */
@Serializable @Serializable
@SerialName("app") class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) {
class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER): AbstractAppInfo {
fun serialize(): String {
return Json.encodeToString(this)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other is AppInfo) { if(other is AppInfo) {
@ -44,4 +47,11 @@ class AppInfo(val packageName: String, val activityName: String?, val user: Int
return "AppInfo {package=$packageName, activity=$activityName, user=$user}" return "AppInfo {package=$packageName, activity=$activityName, user=$user}"
} }
companion object {
const val INVALID_USER = -1
fun deserialize(serialized: String): AppInfo {
return Json.decodeFromString(serialized)
}
}
} }

View file

@ -4,21 +4,20 @@ import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherActivityInfo
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.UserHandle import android.util.Log
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.getUserFromId
/** /**
* Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows. * Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows.
*/ */
class DetailedAppInfo( class DetailedAppInfo(
private val app: AppInfo, val app: AppInfo,
private val label: CharSequence, val label: CharSequence,
private val icon: Drawable, val icon: Drawable,
private val privateSpace: Boolean, val isPrivateSpaceApp: Boolean,
private val removable: Boolean = true, val isSystemApp: Boolean = false,
): AbstractDetailedAppInfo { ) {
constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this( constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this(
AppInfo( AppInfo(
@ -29,41 +28,29 @@ class DetailedAppInfo(
activityInfo.label, activityInfo.label,
activityInfo.getBadgedIcon(0), activityInfo.getBadgedIcon(0),
private, private,
// App can be uninstalled iff it is not a system app activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0
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()
} }
override fun getIcon(context: Context): Drawable { fun setCustomLabel(label: CharSequence?) {
return icon
}
override fun getRawInfo(): AppInfo { Log.i("Launcher", "Setting custom label for ${this.app} to ${label}.")
return app val map = LauncherPreferences.apps().customNames() ?: HashMap<AppInfo, String>()
}
override fun getUser(context: Context): UserHandle { if (label.isNullOrEmpty()) {
return getUserFromId(app.user, context) map.remove(app)
} } else {
map[app] = label.toString()
}
override fun isPrivate(): Boolean { LauncherPreferences.apps().customNames(map)
return privateSpace
} }
override fun isRemovable(): Boolean {
return removable
}
override fun getAction(): Action {
return AppAction(app)
}
companion object { companion object {
fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? { fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? {
return appInfo.getLauncherActivityInfo(context)?.let { return appInfo.getLauncherActivityInfo(context)?.let {

View file

@ -1,66 +0,0 @@
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)
}
}
}
}

View file

@ -5,9 +5,8 @@ import java.util.Set;
import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.R;
import de.jrpie.android.launcher.actions.lock.LockMethod; import de.jrpie.android.launcher.actions.lock.LockMethod;
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer;
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer;
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font; import de.jrpie.android.launcher.preferences.theme.Font;
@ -26,17 +25,15 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
@Preference(name = "version_code", type = int.class, defaultValue = "-1"), @Preference(name = "version_code", type = int.class, defaultValue = "-1"),
}), }),
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = { @PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), @Preference(name = "favorites", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
@Preference(name = "hidden", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), @Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
@Preference(name = "pinned_shortcuts", type = Set.class, serializer = SetPinnedShortcutInfoPreferenceSerializer.class), @Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class),
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAbstractAppInfoStringPreferenceSerializer.class),
@Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), @Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"),
@Preference(name = "hide_paused_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"), @Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"),
}), }),
@PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = { @PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = {
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT"), @Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT")
@Preference(name = "reverse_layout", type = boolean.class, defaultValue = "false")
}), }),
@PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = { @PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = {
}), }),

View file

@ -5,12 +5,9 @@ import android.util.Log
import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.apps.AppInfo 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.apps.DetailedAppInfo
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
import de.jrpie.android.launcher.ui.HomeActivity import de.jrpie.android.launcher.ui.HomeActivity
@ -18,7 +15,7 @@ import de.jrpie.android.launcher.ui.HomeActivity
* Increase when breaking changes are introduced and write an appropriate case in * Increase when breaking changes are introduced and write an appropriate case in
* `migratePreferencesToNewVersion` * `migratePreferencesToNewVersion`
*/ */
const val PREFERENCE_VERSION = 4 const val PREFERENCE_VERSION = 3
const val UNKNOWN_PREFERENCE_VERSION = -1 const val UNKNOWN_PREFERENCE_VERSION = -1
private const val TAG = "Launcher - Preferences" private const val TAG = "Launcher - Preferences"
@ -47,10 +44,6 @@ fun migratePreferencesToNewVersion(context: Context) {
migratePreferencesFromVersion2() migratePreferencesFromVersion2()
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).") Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
} }
3 -> {
migratePreferencesFromVersion3()
Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).")
}
else -> { else -> {
Log.w( Log.w(
@ -73,16 +66,16 @@ fun resetPreferences(context: Context) {
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION) LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf() val hidden: MutableSet<AppInfo> = mutableSetOf()
val launcher = DetailedAppInfo.fromAppInfo( val launcher = DetailedAppInfo.fromAppInfo(
AppInfo( AppInfo(
BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID,
HomeActivity::class.java.name, HomeActivity::class.java.name,
INVALID_USER AppInfo.INVALID_USER
), context ), context
) )
launcher?.getRawInfo()?.let { hidden.add(it) } launcher?.app?.let { hidden.add(it) }
Log.i(TAG,"Hiding ${launcher?.getRawInfo()}") Log.i(TAG,"Hiding ${launcher?.app}")
LauncherPreferences.apps().hidden(hidden) LauncherPreferences.apps().hidden(hidden)
Action.resetToDefaultActions(context) Action.resetToDefaultActions(context)

View file

@ -5,26 +5,15 @@ import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.actions.LauncherAction
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
import kotlinx.serialization.Serializable import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@Serializable
private class LegacyMapEntry(val key: AppInfo, val value: String)
private fun serializeMapAppInfo(value: Map<AppInfo, String>?): Set<String>? {
return value?.map { (key, value) ->
Json.encodeToString(LegacyMapEntry(key, value))
}?.toSet()
}
val oldLauncherActionIds: Map<String, LauncherAction> = val oldLauncherActionIds: Map<String, LauncherAction> =
mapOf( mapOf(
Pair("launcher:settings", LauncherAction.SETTINGS), Pair("launcher:settings", LauncherAction.SETTINGS),
@ -88,7 +77,7 @@ private fun Action.Companion.legacyFromPreference(id: String): Action? {
private fun migrateAppInfoStringMap(key: String) { private fun migrateAppInfoStringMap(key: String) {
val preferences = LauncherPreferences.getSharedPreferences() val preferences = LauncherPreferences.getSharedPreferences()
serializeMapAppInfo( MapAppInfoStringPreferenceSerializer().serialize(
preferences.getStringSet(key, setOf())?.mapNotNull { entry -> preferences.getStringSet(key, setOf())?.mapNotNull { entry ->
try { try {
val obj = JSONObject(entry) val obj = JSONObject(entry)

View file

@ -12,9 +12,9 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
* (see [PREFERENCE_VERSION]) * (see [PREFERENCE_VERSION])
*/ */
fun migratePreferencesFromVersion2() { fun migratePreferencesFromVersion2() {
assert(PREFERENCE_VERSION == 3)
assert(LauncherPreferences.internal().versionCode() == 2) assert(LauncherPreferences.internal().versionCode() == 2)
// previously there was no setting for this // previously there was no setting for this
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE) Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
LauncherPreferences.internal().versionCode(3) LauncherPreferences.internal().versionCode(3)
migratePreferencesFromVersion3()
} }

View file

@ -1,85 +0,0 @@
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<String>?): Set<AppInfo>? {
return value?.map {
Json.decodeFromString<AppInfo>(it)
}?.toHashSet()
}
fun deserializeMap(value: Set<String>?): HashMap<AppInfo, String>? {
return value?.associateTo(HashMap()) {
val entry = Json.decodeFromString<MapEntry>(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<AbstractAppInfo>()
deserializeSet(preferences.getStringSet(key, null))?.let {
set.addAll(it)
}
editor.putStringSet(
key,
serializer.serialize(set as java.util.Set<AbstractAppInfo>) as Set<String>?
)
} catch (e: Exception) {
e.printStackTrace()
editor.putStringSet(key, null)
}
}
private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences, editor: Editor ) {
try {
val serializer = MapAbstractAppInfoStringPreferenceSerializer()
val map = HashMap<AbstractAppInfo, String>()
deserializeMap(preferences.getStringSet(key, null))?.let {
map.putAll(it)
}
editor.putStringSet(key, serializer.serialize(map) as Set<String>?)
} catch (e: Exception) {
e.printStackTrace()
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)
migrateMapAppInfoString(LauncherPreferences.apps().keys().customNames(), preferences, editor)
editor.apply()
LauncherPreferences.internal().versionCode(4)
}

View file

@ -2,8 +2,7 @@
package de.jrpie.android.launcher.preferences.serialization package de.jrpie.android.launcher.preferences.serialization
import de.jrpie.android.launcher.apps.AbstractAppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -11,61 +10,40 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
// Serializers for [LauncherPreference$Config]
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class SetAbstractAppInfoPreferenceSerializer : class SetAppInfoPreferenceSerializer :
PreferenceSerializer<java.util.Set<AbstractAppInfo>?, java.util.Set<java.lang.String>?> { PreferenceSerializer<java.util.Set<AppInfo>?, java.util.Set<java.lang.String>?> {
@Throws(PreferenceSerializationException::class) @Throws(PreferenceSerializationException::class)
override fun serialize(value: java.util.Set<AbstractAppInfo>?): java.util.Set<java.lang.String> { override fun serialize(value: java.util.Set<AppInfo>?): java.util.Set<java.lang.String> {
return value?.map(AbstractAppInfo::serialize) return value?.map(AppInfo::serialize)?.toHashSet() as java.util.Set<java.lang.String>
?.toHashSet() as java.util.Set<java.lang.String>
} }
@Throws(PreferenceSerializationException::class) @Throws(PreferenceSerializationException::class)
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<AbstractAppInfo>? { override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<AppInfo>? {
return value?.map(java.lang.String::toString)?.map(AbstractAppInfo::deserialize) return value?.map (java.lang.String::toString)?.map(AppInfo::deserialize)?.toHashSet() as? java.util.Set<AppInfo>
?.toHashSet() as? java.util.Set<AbstractAppInfo>
} }
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class SetPinnedShortcutInfoPreferenceSerializer : class MapAppInfoStringPreferenceSerializer :
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> { PreferenceSerializer<java.util.HashMap<AppInfo, String>?, java.util.Set<java.lang.String>?> {
@Throws(PreferenceSerializationException::class)
override fun serialize(value: java.util.Set<PinnedShortcutInfo>?): java.util.Set<java.lang.String> {
return value?.map { Json.encodeToString<PinnedShortcutInfo>(it) }
?.toHashSet() as java.util.Set<java.lang.String>
}
@Throws(PreferenceSerializationException::class)
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<PinnedShortcutInfo>? {
return value?.map(java.lang.String::toString)
?.map { Json.decodeFromString<PinnedShortcutInfo>(it) }
?.toHashSet() as? java.util.Set<PinnedShortcutInfo>
}
}
@Suppress("UNCHECKED_CAST")
class MapAbstractAppInfoStringPreferenceSerializer :
PreferenceSerializer<java.util.HashMap<AbstractAppInfo, String>?, java.util.Set<java.lang.String>?> {
@Serializable @Serializable
private class MapEntry(val key: AbstractAppInfo, val value: String) private class MapEntry(val key: AppInfo, val value: String)
@Throws(PreferenceSerializationException::class) @Throws(PreferenceSerializationException::class)
override fun serialize(value: java.util.HashMap<AbstractAppInfo, String>?): java.util.Set<java.lang.String>? { override fun serialize(value: java.util.HashMap<AppInfo, String>?): java.util.Set<java.lang.String>? {
return value?.map { (key, value) -> return value?.map { (key, value) ->
Json.encodeToString(MapEntry(key, value)) Json.encodeToString(MapEntry(key, value))
}?.toHashSet() as? java.util.Set<java.lang.String> }?.toHashSet() as? java.util.Set<java.lang.String>
} }
@Throws(PreferenceSerializationException::class) @Throws(PreferenceSerializationException::class)
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.HashMap<AbstractAppInfo, String>? { override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.HashMap<AppInfo, String>? {
return value?.associateTo(HashMap()) { return value?.associateTo(HashMap()) {
val entry = Json.decodeFromString<MapEntry>(it.toString()) val entry = Json.decodeFromString<MapEntry>(it.toString())
Pair(entry.key, entry.value) Pair(entry.key, entry.value)
} }
} }
} }

View file

@ -21,7 +21,7 @@ import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.ShortcutAction import de.jrpie.android.launcher.actions.ShortcutAction
import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.actions.shortcuts.PinnedShortcutInfo
import de.jrpie.android.launcher.databinding.ActivityPinShortcutBinding import de.jrpie.android.launcher.databinding.ActivityPinShortcutBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -29,7 +29,6 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
private lateinit var binding: ActivityPinShortcutBinding private lateinit var binding: ActivityPinShortcutBinding
private var isBound = false private var isBound = false
private var request: PinItemRequest? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super<AppCompatActivity>.onCreate(savedInstanceState) super<AppCompatActivity>.onCreate(savedInstanceState)
@ -47,7 +46,6 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
val launcherApps = getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val launcherApps = getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
val request = launcherApps.getPinItemRequest(intent) val request = launcherApps.getPinItemRequest(intent)
this.request = request
if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) { if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
finish() finish()
return return
@ -86,7 +84,6 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
} }
binding.pinShortcutClose.setOnClickListener { finish() } binding.pinShortcutClose.setOnClickListener { finish() }
binding.pinShortcutButtonOk.setOnClickListener { finish() }
} }
override fun onStart() { override fun onStart() {
@ -94,24 +91,6 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
super<UIObject>.onStart() super<UIObject>.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 { override fun getTheme(): Resources.Theme {
return modifyTheme(super.getTheme()) return modifyTheme(super.getTheme())
} }
@ -145,6 +124,5 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return gestures.size return gestures.size
} }
} }
} }

View file

@ -16,10 +16,11 @@ import androidx.recyclerview.widget.RecyclerView
import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo 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.LauncherPreferences
import de.jrpie.android.launcher.preferences.ListLayout import de.jrpie.android.launcher.preferences.ListLayout
import de.jrpie.android.launcher.ui.list.ListActivity import de.jrpie.android.launcher.ui.list.ListActivity
@ -46,7 +47,7 @@ class AppsRecyclerAdapter(
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() { RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
private val apps = (activity.applicationContext as Application).apps private val apps = (activity.applicationContext as Application).apps
private val appsListDisplayed: MutableList<AbstractDetailedAppInfo> = mutableListOf() private val appsListDisplayed: MutableList<DetailedAppInfo> = mutableListOf()
// temporarily disable auto launch // temporarily disable auto launch
var disableAutoLaunch: Boolean = false var disableAutoLaunch: Boolean = false
@ -82,11 +83,11 @@ class AppsRecyclerAdapter(
if (layout.useBadgedText) { if (layout.useBadgedText) {
appLabel = activity.packageManager.getUserBadgedLabel( appLabel = activity.packageManager.getUserBadgedLabel(
appLabel, appLabel,
appsListDisplayed[i].getUser(activity) getUserFromId(appsListDisplayed[i].app.user, activity)
).toString() ).toString()
} }
val appIcon = appsListDisplayed[i].getIcon(activity) val appIcon = appsListDisplayed[i].icon
viewHolder.textView.text = appLabel viewHolder.textView.text = appLabel
viewHolder.img.setImageDrawable(appIcon) viewHolder.img.setImageDrawable(appIcon)
@ -117,26 +118,22 @@ class AppsRecyclerAdapter(
@Suppress("SameReturnValue") @Suppress("SameReturnValue")
private fun showOptionsPopup( private fun showOptionsPopup(
viewHolder: ViewHolder, viewHolder: ViewHolder,
appInfo: AbstractDetailedAppInfo appInfo: DetailedAppInfo
): Boolean { ): Boolean {
//create the popup menu //create the popup menu
val popup = PopupMenu(activity, viewHolder.img) val popup = PopupMenu(activity, viewHolder.img)
popup.inflate(R.menu.menu_app) popup.inflate(R.menu.menu_app)
if (!appInfo.isRemovable()) { if (appInfo.isSystemApp) {
popup.menu.findItem(R.id.app_menu_delete).setVisible(false) popup.menu.findItem(R.id.app_menu_delete).setVisible(false)
} }
if (appInfo !is DetailedAppInfo) { if (LauncherPreferences.apps().hidden()?.contains(appInfo.app) == true) {
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) popup.menu.findItem(R.id.app_menu_hidden).setTitle(R.string.list_app_hidden_remove)
} }
if (LauncherPreferences.apps().favorites()?.contains(appInfo.getRawInfo()) == true) { if (LauncherPreferences.apps().favorites()?.contains(appInfo.app) == true) {
popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove) popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove)
} }
@ -144,19 +141,19 @@ class AppsRecyclerAdapter(
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.app_menu_delete -> { R.id.app_menu_delete -> {
appInfo.getRawInfo().uninstall(activity); true appInfo.app.uninstall(activity); true
} }
R.id.app_menu_info -> { R.id.app_menu_info -> {
(appInfo.getRawInfo() as? AppInfo)?.openSettings(activity); true appInfo.app.openSettings(activity); true
} }
R.id.app_menu_favorite -> { R.id.app_menu_favorite -> {
appInfo.getRawInfo().toggleFavorite(); true appInfo.app.toggleFavorite(); true
} }
R.id.app_menu_hidden -> { R.id.app_menu_hidden -> {
appInfo.getRawInfo().toggleHidden(root); true appInfo.app.toggleHidden(root); true
} }
R.id.app_menu_rename -> { R.id.app_menu_rename -> {
@ -191,12 +188,12 @@ class AppsRecyclerAdapter(
val appInfo = appsListDisplayed[pos] val appInfo = appsListDisplayed[pos]
when (intention) { when (intention) {
ListActivity.ListActivityIntention.VIEW -> { ListActivity.ListActivityIntention.VIEW -> {
appInfo.getAction().invoke(activity, rect) AppAction(appInfo.app).invoke(activity, rect)
} }
ListActivity.ListActivityIntention.PICK -> { ListActivity.ListActivityIntention.PICK -> {
val returnIntent = Intent() val returnIntent = Intent()
appInfo.getAction().writeToIntent(returnIntent) AppAction(appInfo.app).writeToIntent(returnIntent)
returnIntent.putExtra("forGesture", forGesture) returnIntent.putExtra("forGesture", forGesture)
activity.setResult(REQUEST_CHOOSE_APP, returnIntent) activity.setResult(REQUEST_CHOOSE_APP, returnIntent)
activity.finish() activity.finish()
@ -214,8 +211,8 @@ class AppsRecyclerAdapter(
&& !disableAutoLaunch && !disableAutoLaunch
&& LauncherPreferences.functionality().searchAutoLaunch() && LauncherPreferences.functionality().searchAutoLaunch()
) { ) {
val app = appsListDisplayed[0] val info = appsListDisplayed[0]
app.getAction().invoke(activity) AppAction(info.app).invoke(activity)
val inputMethodManager = val inputMethodManager =
activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager

View file

@ -1,6 +1,5 @@
package de.jrpie.android.launcher.ui.list.apps package de.jrpie.android.launcher.ui.list.apps
import android.app.Activity
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -16,10 +15,7 @@ import com.google.android.material.snackbar.Snackbar
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.REQUEST_UNINSTALL import de.jrpie.android.launcher.REQUEST_UNINSTALL
import de.jrpie.android.launcher.apps.AppInfo 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.DetailedAppInfo
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.getUserFromId
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -36,33 +32,27 @@ fun AppInfo.openSettings(
} }
} }
fun AbstractAppInfo.uninstall(activity: Activity) { fun AppInfo.uninstall(activity: android.app.Activity) {
if (this is AppInfo) { val packageName = this.packageName
val packageName = this.packageName val userId = this.user
val userId = this.user
Log.i(LOG_TAG, "uninstalling $this") Log.i(LOG_TAG, "uninstalling $this")
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE)
intent.data = Uri.parse("package:$packageName") intent.data = Uri.parse("package:$packageName")
getUserFromId(userId, activity).let { user -> getUserFromId(userId, activity).let { user ->
intent.putExtra(Intent.EXTRA_USER, 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 AbstractAppInfo.toggleFavorite() { fun AppInfo.toggleFavorite() {
val favorites: MutableSet<AbstractAppInfo> = val favorites: MutableSet<AppInfo> =
LauncherPreferences.apps().favorites() ?: mutableSetOf() LauncherPreferences.apps().favorites() ?: mutableSetOf()
if (favorites.contains(this)) { if (favorites.contains(this)) {
@ -79,8 +69,8 @@ fun AbstractAppInfo.toggleFavorite() {
/** /**
* @param view: used to show a snackbar letting the user undo the action * @param view: used to show a snackbar letting the user undo the action
*/ */
fun AbstractAppInfo.toggleHidden(view: View) { fun AppInfo.toggleHidden(view: View) {
val hidden: MutableSet<AbstractAppInfo> = val hidden: MutableSet<AppInfo> =
LauncherPreferences.apps().hidden() ?: mutableSetOf() LauncherPreferences.apps().hidden() ?: mutableSetOf()
if (hidden.contains(this)) { if (hidden.contains(this)) {
hidden.remove(this) hidden.remove(this)
@ -97,9 +87,9 @@ fun AbstractAppInfo.toggleHidden(view: View) {
LauncherPreferences.apps().hidden(hidden) LauncherPreferences.apps().hidden(hidden)
} }
fun AbstractDetailedAppInfo.showRenameDialog(context: Context) { fun DetailedAppInfo.showRenameDialog(context: Context) {
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
setTitle(context.getString(R.string.dialog_rename_title, getLabel())) setTitle(context.getString(R.string.dialog_rename_title, label))
setView(R.layout.dialog_rename_app) setView(R.layout.dialog_rename_app)
setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() } setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() }
setPositiveButton(R.string.dialog_rename_ok) { d, _ -> setPositiveButton(R.string.dialog_rename_ok) { d, _ ->
@ -112,7 +102,7 @@ fun AbstractDetailedAppInfo.showRenameDialog(context: Context) {
}.create().also { it.show() }.apply { }.create().also { it.show() }.apply {
val input = findViewById<EditText>(R.id.dialog_rename_app_edit_text) val input = findViewById<EditText>(R.id.dialog_rename_app_edit_text)
input?.setText(getCustomLabel(context)) input?.setText(getCustomLabel(context))
input?.hint = getLabel() input?.hint = label
} }
} }

View file

@ -9,8 +9,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.databinding.ListAppsBinding import de.jrpie.android.launcher.databinding.ListAppsBinding
@ -81,18 +79,11 @@ class ListFragmentApps : Fragment(), UIObject {
layout = LauncherPreferences.list().layout() layout = LauncherPreferences.list().layout()
) )
// set up the list / recycler // set up the list / recycler
binding.listAppsRview.apply { binding.listAppsRview.apply {
// improve performance (since content changes don't change the layout size) // improve performance (since content changes don't change the layout size)
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = LauncherPreferences.list().layout().layoutManager(context) layoutManager = LauncherPreferences.list().layout().layoutManager(context)
.also {
if (LauncherPreferences.list().reverseLayout()) {
(it as? LinearLayoutManager)?.reverseLayout = true
(it as? GridLayoutManager)?.reverseLayout = true
}
}
adapter = appsRecyclerAdapter adapter = appsRecyclerAdapter
} }

View file

@ -80,6 +80,7 @@
android:minHeight="40dp" android:minHeight="40dp"
tools:drawableLeft="@drawable/baseline_settings_24" tools:drawableLeft="@drawable/baseline_settings_24"
tools:text="Shortcut name" /> tools:text="Shortcut name" />
<!--
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="10dp" /> android:layout_height="10dp" />
@ -89,8 +90,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?android:textColor" android:textColor="?android:textColor"
android:checked="true"
android:text="@string/pin_shortcut_switch_visible" /> android:text="@string/pin_shortcut_switch_visible" />
-->
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
@ -102,21 +103,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/pin_shortcut_button_bind" /> android:text="@string/pin_shortcut_button_bind" />
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<Button
android:id="@+id/pin_shortcut_button_ok"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pin_shortcut_button_ok"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -40,7 +40,8 @@
android:src="@drawable/baseline_settings_24" android:src="@drawable/baseline_settings_24"
custom:layout_constraintBottom_toBottomOf="parent" custom:layout_constraintBottom_toBottomOf="parent"
custom:layout_constraintStart_toStartOf="parent" custom:layout_constraintStart_toStartOf="parent"
custom:layout_constraintTop_toTopOf="parent" /> custom:layout_constraintTop_toTopOf="parent"
custom:type="solid" />
<TextView <TextView
android:id="@+id/list_heading" android:id="@+id/list_heading"
@ -69,7 +70,8 @@
android:src="@drawable/baseline_close_24" android:src="@drawable/baseline_close_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
custom:type="solid" />
<ImageView <ImageView
android:id="@+id/list_lock" android:id="@+id/list_lock"
android:visibility="gone" android:visibility="gone"
@ -83,7 +85,8 @@
android:src="@drawable/baseline_lock_24" android:src="@drawable/baseline_lock_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/list_close" app:layout_constraintEnd_toStartOf="@id/list_close"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
custom:type="solid" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout

View file

@ -47,7 +47,8 @@
android:src="@drawable/baseline_close_24" android:src="@drawable/baseline_close_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
custom:type="solid" />
<ImageView <ImageView
android:id="@+id/settings_system" android:id="@+id/settings_system"
@ -62,7 +63,8 @@
android:src="@drawable/baseline_settings_applications_24" android:src="@drawable/baseline_settings_applications_24"
custom:layout_constraintBottom_toBottomOf="parent" custom:layout_constraintBottom_toBottomOf="parent"
custom:layout_constraintStart_toStartOf="parent" custom:layout_constraintStart_toStartOf="parent"
custom:layout_constraintTop_toTopOf="parent" /> custom:layout_constraintTop_toTopOf="parent"
custom:type="solid" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout

View file

@ -281,32 +281,4 @@
<string name="list_other_list_private_space">Espaço privado</string> <string name="list_other_list_private_space">Espaço privado</string>
<string name="tooltip_lock_private_space">Trancar espaço privado</string> <string name="tooltip_lock_private_space">Trancar espaço privado</string>
<string name="tooltip_unlock_private_space">Liberar espaço privado</string> <string name="tooltip_unlock_private_space">Liberar espaço privado</string>
<string name="settings_gesture_swipe_larger_reverse"><![CDATA[> (Reverse)]]></string> </resources>
<string name="settings_gesture_description_swipe_larger_reverse">Canto inferior esquerdo -&gt; centro direito -&gt; canto superior esquerdo</string>
<string name="settings_gesture_description_swipe_smaller">Canto superior direito -&gt; centro direito -&gt; canto inferior direito</string>
<string name="settings_gesture_swipe_smaller_reverse"><![CDATA[< (Reverse)]]></string>
<string name="settings_gesture_description_swipe_v">Canto superior esquerdo -&gt; centro médio -&gt; canto superior direito</string>
<string name="settings_gesture_description_swipe_v_reverse">Canto superior direito -&gt; centro médio -&gt; canto superior esquerdo</string>
<string name="settings_gesture_swipe_v_reverse">V (invertido)</string>
<string name="settings_gesture_description_swipe_lambda">Inferior esquerdo -&gt; superior médio -&gt; inferior direito</string>
<string name="settings_gesture_swipe_lambda_reverse">Λ (invertido)</string>
<string name="settings_gesture_swipe_larger"><![CDATA[>]]></string>
<string name="settings_gesture_swipe_smaller"><![CDATA[<]]></string>
<string name="settings_gesture_swipe_v">V</string>
<string name="settings_gesture_swipe_lambda">Λ</string>
<string name="settings_gesture_description_swipe_larger">Canto superior esquerdo -&gt; centro direito -&gt; canto inferior esquerdo</string>
<string name="settings_gesture_tap_up">Toque + pra cima</string>
<string name="settings_gesture_description_tap_up">Toque e deslize pra cima</string>
<string name="settings_gesture_tap_down">Toque + pra baixo</string>
<string name="settings_gesture_description_tap_down">Toque e deslize pra baixo</string>
<string name="settings_gesture_tap_left">Toque + esquerda</string>
<string name="settings_gesture_description_tap_left">Toque e deslize pra esquerda</string>
<string name="settings_gesture_tap_right">Toque + direita</string>
<string name="settings_gesture_description_tap_right">Toque e deslize pra direita</string>
<string name="pin_shortcut_title">Adicionar atalho</string>
<string name="pin_shortcut_button_bind">Vincular ao gesto</string>
<string name="pin_shortcut_switch_visible">Mostrar na lista de apps</string>
<string name="list_other_track_play_pause">Música: Reproduzir / Pausar</string>
<string name="settings_gesture_description_swipe_smaller_reverse">Canto inferior direito -&gt; centro esquerdo -&gt; canto superior direito</string>
<string name="settings_gesture_description_swipe_lambda_reverse">Inferior direito -&gt; superior médio -&gt; inferior esquerdo</string>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This declares attributes for the FontAwesome TextView -->
<resources>
<declare-styleable name="FontAwesome">
<attr name="type" format="string" />
</declare-styleable>
</resources>

View file

@ -11,13 +11,11 @@
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string> <string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string> <string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string> <string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>
<string name="settings_apps_custom_names_key" translatable="false">apps.custom_names</string> <string name="settings_apps_custom_names_key" translatable="false">apps.custom_names</string>
<string name="settings_apps_hide_bound_apps_key" translatable="false">apps.hide_bound_apps</string> <string name="settings_apps_hide_bound_apps_key" translatable="false">apps.hide_bound_apps</string>
<string name="settings_apps_hide_paused_apps_key" translatable="false">apps.hide_paused_apps</string> <string name="settings_apps_hide_paused_apps_key" translatable="false">apps.hide_paused_apps</string>
<string name="settings_apps_hide_private_space_apps_key" translatable="false">apps.hide_private_space_apps</string> <string name="settings_apps_hide_private_space_apps_key" translatable="false">apps.hide_private_space_apps</string>
<string name="settings_list_layout_key" translatable="false">list.layout</string> <string name="settings_list_layout_key" translatable="false">list.layout</string>
<string name="settings_list_reverse_layout_key" translatable="false">list.reverse_layout</string>
<string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string> <string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string>
<!-- values of de.jrpie.android.launcher.preferences.ListLayout --> <!-- values of de.jrpie.android.launcher.preferences.ListLayout -->

View file

@ -179,7 +179,6 @@
<string name="settings_apps_hide_paused_apps">Hide paused apps</string> <string name="settings_apps_hide_paused_apps">Hide paused apps</string>
<string name="settings_apps_hide_private_space_apps">Hide private space from app list</string> <string name="settings_apps_hide_private_space_apps">Hide private space from app list</string>
<string name="settings_list_layout">Layout of app list</string> <string name="settings_list_layout">Layout of app list</string>
<string name="settings_list_reverse_layout">Reverse app list</string>
<string name="settings_list_layout_item_default">Default</string> <string name="settings_list_layout_item_default">Default</string>
<string name="settings_list_layout_item_text">Text</string> <string name="settings_list_layout_item_text">Text</string>
@ -264,7 +263,6 @@
<!-- Pin shortcuts --> <!-- Pin shortcuts -->
<string name="pin_shortcut_title">Add Shortcut</string> <string name="pin_shortcut_title">Add Shortcut</string>
<string name="pin_shortcut_button_bind">Bind to gesture</string> <string name="pin_shortcut_button_bind">Bind to gesture</string>
<string name="pin_shortcut_button_ok">Ok</string>
<string name="pin_shortcut_switch_visible">Show in app list</string> <string name="pin_shortcut_switch_visible">Show in app list</string>
<!-- <!--

View file

@ -123,9 +123,6 @@
<style name="AlertDialogCustom" parent="Theme.AppCompat.Light.Dialog.Alert"> <style name="AlertDialogCustom" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:color">#000000</item> <item name="android:color">#000000</item>
<item name="android:textColor">@color/text_color_toggle</item> <item name="android:textColor">@color/text_color_toggle</item>
<item name="android:shadowRadius">0</item>
<item name="android:shadowDx">0</item>
<item name="android:shadowDy">0</item>
</style> </style>
<style name="AlertDialogDanger" parent="AlertDialogCustom"> <style name="AlertDialogDanger" parent="AlertDialogCustom">

View file

@ -164,12 +164,6 @@
android:summary="%s" android:summary="%s"
android:defaultValue="DEFAULT"/> android:defaultValue="DEFAULT"/>
<SwitchPreference
android:key="@string/settings_list_reverse_layout_key"
android:title="@string/settings_list_reverse_layout"
android:defaultValue="false"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/settings_launcher_section_display" android:title="@string/settings_launcher_section_display"