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 0a1c0e8..ddf6531 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -4,10 +4,12 @@ import android.os.Build import android.os.Build.VERSION_CODES import androidx.preference.PreferenceManager import de.jrpie.android.launcher.actions.TorchManager +import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.preferences.LauncherPreferences class Application : android.app.Application() { var torchManager: TorchManager? = null + var customAppNames: HashMap = HashMap() override fun onCreate() { super.onCreate() @@ -18,5 +20,13 @@ class Application : android.app.Application() { val preferences = PreferenceManager.getDefaultSharedPreferences(this) LauncherPreferences.init(preferences, this.resources) + customAppNames = LauncherPreferences.apps().customNames() + + LauncherPreferences.getSharedPreferences() + .registerOnSharedPreferenceChangeListener { _, pref -> + if (pref == getString(R.string.settings_apps_custom_names_key)) { + customAppNames = LauncherPreferences.apps().customNames() + } + } } } 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 37dc3fb..6efa0f3 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -185,7 +185,7 @@ fun loadApps(packageManager: PackageManager, context: Context) { loadList.add(detailedAppInfo) } } - loadList.sortBy { it.label.toString() } + loadList.sortBy { it.getCustomLabel(context).toString() } appsList.clear() appsList.addAll(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 c8a75cf..741e19b 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 @@ -58,7 +58,7 @@ class AppAction(val appInfo: AppInfo) : Action { } override fun label(context: Context): String { - return DetailedAppInfo.fromAppInfo(appInfo, context)?.label.toString() + return DetailedAppInfo.fromAppInfo(appInfo, context)?.getCustomLabel(context).toString() } override fun getIcon(context: Context): Drawable? { 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 01ab448..327fc9c 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 @@ -1,5 +1,6 @@ package de.jrpie.android.launcher.apps +import android.content.Context import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.Gesture @@ -8,12 +9,14 @@ import java.util.Locale import kotlin.text.Regex.Companion.escape class AppFilter( + var context: Context, var search: String, var favoritesVisibility: AppSetVisibility = AppSetVisibility.VISIBLE, var hiddenVisibility: AppSetVisibility = AppSetVisibility.HIDDEN, ) { operator fun invoke(apps: List): List { - var apps = apps + var apps = + apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) } val hidden = LauncherPreferences.apps().hidden() ?: setOf() val favorites = LauncherPreferences.apps().favorites() ?: setOf() @@ -51,7 +54,7 @@ class AppFilter( val appsSecondary: MutableList = ArrayList() val normalizedText: String = normalize(search) for (item in apps) { - val itemLabel: String = normalize(item.label.toString()) + val itemLabel: String = normalize(item.getCustomLabel(context).toString()) if (itemLabel.startsWith(normalizedText)) { r.add(item) 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 8a8b2a3..23d7870 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,6 +4,9 @@ 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 /** * Stores information used to create [AppsRecyclerAdapter] rows. @@ -16,12 +19,36 @@ class DetailedAppInfo( ) { constructor(activityInfo: LauncherActivityInfo) : this( - AppInfo(activityInfo.applicationInfo.packageName, activityInfo.name, activityInfo.user.hashCode()), + AppInfo( + activityInfo.applicationInfo.packageName, + activityInfo.name, + activityInfo.user.hashCode() + ), activityInfo.label, activityInfo.getBadgedIcon(0), activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0 ) + fun getCustomLabel(context: Context): CharSequence { + val map = (context.applicationContext as? Application)?.customAppNames ?: return label + + return map[app] ?: label + } + + 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) + } + companion object { fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? { return appInfo.getLauncherActivityInfo(context)?.let { DetailedAppInfo(it) } 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 54035a3..de3fca1 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 @@ -1,7 +1,15 @@ package de.jrpie.android.launcher.preferences; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.apps.AppInfo; @@ -28,6 +36,7 @@ import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSeriali @PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = { @Preference(name = "favorites", type = Set.class, serializer = LauncherPreferences$Config.AppInfoSetSerializer.class), @Preference(name = "hidden", type = Set.class, serializer = LauncherPreferences$Config.AppInfoSetSerializer.class), + @Preference(name = "custom_names", type = HashMap.class, serializer = LauncherPreferences$Config.MapAppInfoStringSerializer.class), @Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), }), @PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = { @@ -96,6 +105,46 @@ public final class LauncherPreferences$Config { return deserialized; } + } + public static class MapAppInfoStringSerializer implements PreferenceSerializer, Set> { + + @Override + public Set serialize(HashMap value) throws PreferenceSerializationException { + if (value == null) return null; + + var serialized = new HashSet(value.size()); + + for (var entry : value.entrySet()) { + JSONObject obj = new JSONObject(); + try { + obj.put("key", entry.getKey().serialize()); + obj.put("value", entry.getValue()); + serialized.add(obj.toString()); + } catch (JSONException ignored) { + } + } + + return serialized; + } + + @Override + public HashMap deserialize(Set value) throws PreferenceSerializationException { + if (value == null) return null; + + var deserialized = new HashMap(); + + for (var entry : value) { + try { + JSONObject obj = new JSONObject(entry); + AppInfo info = AppInfo.Companion.deserialize(obj.getString("key")); + String s = obj.getString("value"); + deserialized.put(info, s); + } catch (JSONException ignored) { + } + } + + return deserialized; + } } } 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 a126bfc..8df095f 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 @@ -5,14 +5,17 @@ import android.app.Activity import android.content.Intent import android.graphics.Rect import android.os.AsyncTask +import android.text.InputType import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager +import android.widget.EditText import android.widget.ImageView import android.widget.PopupMenu import android.widget.TextView +import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import de.jrpie.android.launcher.R @@ -44,7 +47,7 @@ class AppsRecyclerAdapter( private val intention: ListActivity.ListActivityIntention = ListActivity.ListActivityIntention.VIEW, private val forGesture: String? = "", - private var appFilter: AppFilter = AppFilter("") + private var appFilter: AppFilter = AppFilter(activity, "") ) : RecyclerView.Adapter() { @@ -69,7 +72,7 @@ class AppsRecyclerAdapter( override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { - val appLabel = appsListDisplayed[i].label.toString() + val appLabel = appsListDisplayed[i].getCustomLabel(activity).toString() val appIcon = appsListDisplayed[i].icon viewHolder.textView.text = appLabel @@ -146,7 +149,10 @@ class AppsRecyclerAdapter( if (favorites.contains(appInfo.app)) { favorites.remove(appInfo.app) - Log.i("LAUNCHER", "Removing " + appInfo.app.serialize() + " from favorites.") + Log.i( + "LAUNCHER", + "Removing " + appInfo.app.serialize() + " from favorites." + ) } else { Log.i("LAUNCHER", "Adding " + appInfo.app.serialize() + " to favorites.") favorites.add(appInfo.app) @@ -181,6 +187,30 @@ class AppsRecyclerAdapter( true } + R.id.app_menu_rename -> { + val builder = AlertDialog.Builder(activity, R.style.AlertDialogCustom) + + val title = activity.getString(R.string.dialog_rename_title, appInfo.label) + builder.setTitle(title) + builder.setView(R.layout.dialog_rename_app) + + builder.setNegativeButton(R.string.dialog_rename_cancel) { d, _ -> d.cancel() } + builder.setPositiveButton(R.string.dialog_rename_ok) { d, _ -> + appInfo.setCustomLabel( + (d as? AlertDialog) + ?.findViewById(R.id.dialog_rename_app_edit_text) + ?.text.toString() + ) + } + + val dialog = builder.create() + dialog.show() + val input = dialog.findViewById(R.id.dialog_rename_app_edit_text) + input?.setText(appInfo.getCustomLabel(activity)) + input?.hint = appInfo.label + true + } + else -> false } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt index c5b840d..fa48d14 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt @@ -66,6 +66,7 @@ class ListFragmentApps : Fragment(), UIObject { AppsRecyclerAdapter( requireActivity(), binding.root, intention, forGesture, appFilter = AppFilter( + requireContext(), "", favoritesVisibility = favoritesVisibility, hiddenVisibility = hiddenVisibility diff --git a/app/src/main/res/layout/dialog_rename_app.xml b/app/src/main/res/layout/dialog_rename_app.xml new file mode 100644 index 0000000..c177504 --- /dev/null +++ b/app/src/main/res/layout/dialog_rename_app.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_app.xml b/app/src/main/res/menu/menu_app.xml index 017a0af..44bfe09 100644 --- a/app/src/main/res/menu/menu_app.xml +++ b/app/src/main/res/menu/menu_app.xml @@ -6,6 +6,8 @@ android:title="@string/list_app_favorite_add" /> + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index bd68ce5..af53936 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -136,6 +136,7 @@ Deinstallieren App Info Sichtbar machen + Umbenennen Die App wurde entfernt Die App konnte nicht entfernt werden Anwendungen suchen @@ -188,4 +189,7 @@ Die Aktion \"Bildschirm sperren\" aktivieren Es wurde keine geeignete Kamera gefunden. Fehler: Kein Zugriff auf die Kamera möglich. + Abbrechen + Ok + %1$s umbenennen \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 16d09c2..73ae5ea 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -11,6 +11,7 @@ internal.version_code apps.favorites apps.hidden + apps.custom_names apps.hide_bound_apps general.select_launcher diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 158ec0c..d0e8dea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,6 +191,7 @@ Remove from favorites Hide Show + Rename Removed the selected application Unable to remove application @@ -283,4 +284,7 @@ Use Accessibility Service Use Device Admin Choose method for locking the screen + Cancel + Ok + Rename %1$s