From c9ee2c6304f9745cd314784e1d8174adbb2f2f53 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 02:00:26 +0100 Subject: [PATCH 1/7] handle exception when acessing shortcuts --- .../de/jrpie/android/launcher/Functions.kt | 2 +- .../launcher/apps/PinnedShortcutInfo.kt | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) 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 81e58d7..7c2abbf 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -100,7 +100,7 @@ fun removeUnusedShortcuts(context: Context) { }, profile ) - } catch (e: IllegalStateException) { + } catch (e: Exception) { // https://github.com/jrpie/launcher/issues/116 return null } diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt index a2815e5..1dc1e1f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt @@ -28,15 +28,20 @@ class PinnedShortcutInfo( fun getShortcutInfo(context: Context): ShortcutInfo? { val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - return launcherApps.getShortcuts( - ShortcutQuery().apply { - setQueryFlags(ShortcutQuery.FLAG_MATCH_PINNED) - setPackage(packageName) - setActivity(ComponentName(packageName, activityName)) - setShortcutIds(listOf(id)) - }, - getUserFromId(user, context) - )?.firstOrNull() + return try { + launcherApps.getShortcuts( + ShortcutQuery().apply { + setQueryFlags(ShortcutQuery.FLAG_MATCH_PINNED) + setPackage(packageName) + setActivity(ComponentName(packageName, activityName)) + setShortcutIds(listOf(id)) + }, + getUserFromId(user, context) + )?.firstOrNull() + } catch(_: Exception) { + // can throw SecurityException or IllegalStateException when profile is locked + null + } } override fun equals(other: Any?): Boolean { From b156b68d5380cc18582b9784e4868b1cd1bb85ed Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 02:39:30 +0100 Subject: [PATCH 2/7] improve German translation --- app/src/main/res/values-de/strings.xml | 32 +++++++++++++------------- app/src/main/res/values/strings.xml | 1 - 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e7c7546..17b840f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -6,8 +6,8 @@ - --> App kann nicht geöffnet werden - Möchtest du die App-Einstellungen anpassen? - Öffne die Einstellungen um für diese Geste eine Aktion zu wählen + App-Einstellungen anpassen? + Einstellungen öffnen um für diese Geste eine Aktion zu wählen Taschenlampe umschalten Tutorial - Nimm dir kurz Zeit und lerne, wie du diesen Launcher verwendest! + Hier eine kurze Erklärung, wie dieser Launcher funktioniert. Konzept - µLauncher bietet eine minimalistische, effiziente und ablenkungsfreie digitale Umgebung.\n\nDie App kostet dich nichts, enthält keine Werbung und sammelt keinerlei Daten. - Launcher ist open-source (MIT license) und auf GitHub!\n\nSchau gerne mal dort vorbei! + µLauncher bietet eine minimalistische, effiziente und ablenkungsfreie digitale Umgebung.\n\nDie App ist freie Software, enthält keine Werbung und sammelt keinerlei Daten. + Der Quellcode ist bei GitHub zu finden. Benutzung - Auf deinem Homescreen siehst du nur das Datum und die Uhrzeit. Keine Ablenkung. - Du öffnest Apps indem du über den Bildschirm wischt oder die Lautstärketasten drückst. Gleich wählst du deine Apps. + Der Homescreen zeigt nur das Datum und die Uhrzeit. Keine Ablenkung. + Häufig verwendete Apps können mittels Gesten, z.B. Wischen oder den Lautstärketasten geöffnet werden. Gleich können die Apps ausgewählt werden. Einrichtung - Wir haben dir ein paar Standardapps ausgewählt, du kannst sie hier gerne ändern: - Du kannst deine Auswahl in den Einstellungen später jederzeit ändern. + Es wurden Standardapps ausgewählt, die Zuordnung kann hier angepasst werden: + Die Auswahl kann in den Einstellungen später jederzeit geändert werden. Los gehts! - Du bist bereit loszulegen!\n\nIch hoffe diese App ist nützlich für dich!\n\n- Finn (der Entwickler)\n\tund Josia (der einige Änderungen vorgenommen hat und den Fork μLauncher entwickelt) + Es kann losgehen!\n\nWir hoffen, dass diese App hilfreich ist!\n\n- Finn (der Entwickler)\n\tund Josia (der einige Änderungen vorgenommen hat und den Fork μLauncher entwickelt) Starten Einstellungen Mehr Optionen Benachrichtigungen - Fehler: Diese Funktion wird von deinem Gerät leider nicht unterstützt. + Fehler: Die Funktion wird von diesem Gerät leider nicht unterstützt. Sekunden anzeigen Rückgängig Schnelleinstellungen @@ -195,7 +195,7 @@ Diese Funktionalität benötigt Android 15 oder neuer. Die App wurde versteckt. Sie kann in den Einstellungen wieder sichtbar gemacht werden. µLauncher muss Geräteadministrator sein, um den Bildschirm sperren zu dürfen. - Dies ist erforderlich, damit µLauncher den Bildschirm spreen kann. + Dies ist erforderlich, damit µLauncher den Bildschirm sperren kann. Die Aktion \"Bildschirm sperren\" aktivieren Es wurde keine geeignete Kamera gefunden. Fehler: Kein Zugriff auf die Kamera möglich. @@ -234,8 +234,8 @@ Dies erfordert sehr weitgehende Berechtigungen. µLauncher wird diese ausschließlich zum Sperren des Bildschirms verwenden.
- (Irgeneiner gerade heruntergeladenen App sollte man eine solche Behauptung natürlich nicht einfach glauben. - Du kannst jedoch den Source Code selbst prüfen.) + (Irgendeiner gerade heruntergeladenen App sollte man eine solche Behauptung natürlich nicht einfach glauben. + Der Quelltext dieser App ist jedoch frei verfügbar und kann überprüft werden.)



@@ -277,4 +277,4 @@ Ich willige ein, dass µLauncher eine Bedienungshilfe für Zwecke verwendet, die nicht unter Barrierefreiheit fallen. Ich willige ein, dass µLauncher keine Daten sammelt. Bedienungshilfe aktivieren - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 902e147..3177442 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,7 +192,6 @@ --> Set μLauncher as home screen App Info - Your device does not support this feature. Manage application details instead? View Launcher Tutorial From 6cd17343fcc856a6fab0ef6ffbea28f58a174ef6 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 04:09:28 +0100 Subject: [PATCH 3/7] show questionmark when unkown app or shortcut is bound to gesture --- .../actions/SettingsFragmentActionsRecycler.kt | 10 ++++++++-- .../main/res/drawable/baseline_question_mark_24.xml | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_question_mark_24.xml diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/actions/SettingsFragmentActionsRecycler.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/actions/SettingsFragmentActionsRecycler.kt index d7862fa..1f91913 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/actions/SettingsFragmentActionsRecycler.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/actions/SettingsFragmentActionsRecycler.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -94,6 +95,8 @@ class SettingsFragmentActionsRecycler : Fragment(), UIObject { class ActionsRecyclerAdapter(val activity: Activity) : RecyclerView.Adapter() { + private val drawableUnknown = AppCompatResources.getDrawable(activity, R.drawable.baseline_question_mark_24) + private val gesturesList: ArrayList = Gesture.entries.filter(Gesture::isEnabled) as ArrayList @@ -115,15 +118,18 @@ class ActionsRecyclerAdapter(val activity: Activity) : private fun updateViewHolder(gesture: Gesture, viewHolder: ViewHolder) { val action = Action.forGesture(gesture) - val drawable = action?.getIcon(activity) - if (action == null || drawable == null) { + if (action == null) { viewHolder.img.visibility = View.INVISIBLE viewHolder.removeAction.visibility = View.GONE viewHolder.chooseButton.visibility = View.VISIBLE return } + // Use the unknown icon if there is an action, but we can't find its icon. + // Probably an app was uninstalled. + val drawable = action.getIcon(activity) ?: drawableUnknown + viewHolder.img.visibility = View.VISIBLE viewHolder.removeAction.visibility = View.VISIBLE viewHolder.chooseButton.visibility = View.INVISIBLE diff --git a/app/src/main/res/drawable/baseline_question_mark_24.xml b/app/src/main/res/drawable/baseline_question_mark_24.xml new file mode 100644 index 0000000..9a2b28a --- /dev/null +++ b/app/src/main/res/drawable/baseline_question_mark_24.xml @@ -0,0 +1,12 @@ + + + + + From c7af387a944dfb90a2a50db80a582cfa192caea5 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 04:11:47 +0100 Subject: [PATCH 4/7] implement #98 - hide lock icon when 'hide private space when locked' setting is set --- .../launcher/actions/LauncherAction.kt | 17 ++++-- .../android/launcher/apps/PrivateSpace.kt | 14 +++++ .../android/launcher/ui/list/ListActivity.kt | 58 ++++++++++--------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt index 1ed6473..3c89a67 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt @@ -13,6 +13,7 @@ import androidx.appcompat.content.res.AppCompatResources import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.apps.AppFilter +import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked import de.jrpie.android.launcher.apps.isPrivateSpaceSupported import de.jrpie.android.launcher.apps.togglePrivateSpaceLock import de.jrpie.android.launcher.preferences.LauncherPreferences @@ -66,7 +67,11 @@ enum class LauncherAction( R.string.list_other_list_private_space, R.drawable.baseline_security_24, { context -> - openAppsList(context, private = true) + if ((context.applicationContext as Application).privateSpaceLocked.value != true + || !hidePrivateSpaceWhenLocked(context) + ) { + openAppsList(context, private = true) + } }, available = { _ -> isPrivateSpaceSupported() @@ -83,31 +88,31 @@ enum class LauncherAction( "volume_up", R.string.list_other_volume_up, R.drawable.baseline_volume_up_24, - { context -> audioVolumeAdjust(context, true)} + { context -> audioVolumeAdjust(context, true) } ), VOLUME_DOWN( "volume_down", R.string.list_other_volume_down, R.drawable.baseline_volume_down_24, - { context -> audioVolumeAdjust(context, false)} + { context -> audioVolumeAdjust(context, false) } ), TRACK_PLAY_PAUSE( "play_pause_track", R.string.list_other_track_play_pause, R.drawable.baseline_play_arrow_24, - { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)} + { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) } ), TRACK_NEXT( "next_track", R.string.list_other_track_next, R.drawable.baseline_skip_next_24, - { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_NEXT)} + { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_NEXT) } ), TRACK_PREV( "previous_track", R.string.list_other_track_previous, R.drawable.baseline_skip_previous_24, - { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PREVIOUS)} + { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PREVIOUS) } ), EXPAND_NOTIFICATIONS_PANEL( "expand_notifications_panel", diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/PrivateSpace.kt b/app/src/main/java/de/jrpie/android/launcher/apps/PrivateSpace.kt index 9b37d60..a1241af 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/PrivateSpace.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/PrivateSpace.kt @@ -95,6 +95,12 @@ fun lockPrivateSpace(context: Context, lock: Boolean) { if (!isPrivateSpaceSupported()) { return } + + // silently return when trying to unlock but hide when locked is set + if (!lock && hidePrivateSpaceWhenLocked(context)) { + return + } + val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager val privateSpaceUser = getPrivateSpaceUser(context) ?: return userManager.requestQuietModeEnabled(lock, privateSpaceUser) @@ -116,3 +122,11 @@ fun togglePrivateSpaceLock(context: Context) { } } +fun hidePrivateSpaceWhenLocked(context: Context): Boolean { + // TODO: perhaps this should be cached + + // https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Launcher3/src/com/android/launcher3/util/SettingsCache.java;l=61;drc=56bf7ad33bc9d5ed3c18e7abefeec5c177ec75d7 + val key = "hide_privatespace_entry_point" + return Settings.Secure.getInt(context.contentResolver, key, 0) == 1 +} + diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/ListActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/ListActivity.kt index 334bd62..95ed56e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/ListActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/ListActivity.kt @@ -22,6 +22,7 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.REQUEST_UNINSTALL import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.apps.AppFilter +import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.apps.isPrivateSpaceSetUp import de.jrpie.android.launcher.apps.togglePrivateSpaceLock @@ -34,10 +35,12 @@ import de.jrpie.android.launcher.ui.list.other.ListFragmentOther // TODO: Better solution for this intercommunication functionality (used in list-fragments) var intention = ListActivity.ListActivityIntention.VIEW -var favoritesVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.VISIBLE +var favoritesVisibility: AppFilter.Companion.AppSetVisibility = + AppFilter.Companion.AppSetVisibility.VISIBLE var privateSpaceVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.VISIBLE -var hiddenVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.HIDDEN +var hiddenVisibility: AppFilter.Companion.AppSetVisibility = + AppFilter.Companion.AppSetVisibility.HIDDEN var forGesture: String? = null /** @@ -52,6 +55,23 @@ class ListActivity : AppCompatActivity(), UIObject { private fun updateLockIcon(locked: Boolean) { + if ( + // only show lock for VIEW intention + (intention != ListActivityIntention.VIEW) + // hide lock when private space does not exist + || !isPrivateSpaceSetUp(this) + // hide lock when private space apps are hidden from the main list and we are not in the private space list + || (LauncherPreferences.apps().hidePrivateSpaceApps() + && privateSpaceVisibility != AppFilter.Companion.AppSetVisibility.EXCLUSIVE) + // hide lock when private space is locked and the hidden when locked setting is set + || (locked && hidePrivateSpaceWhenLocked(this)) + ) { + binding.listLock.visibility = View.GONE + return + } + + binding.listLock.visibility = View.VISIBLE + binding.listLock.setImageDrawable( AppCompatResources.getDrawable( this, @@ -74,7 +94,6 @@ class ListActivity : AppCompatActivity(), UIObject { } - enum class ListActivityIntention(val titleResource: Int) { VIEW(R.string.list_title_view), /* view list of apps */ PICK(R.string.list_title_pick) /* choose app or action to associate to a gesture */ @@ -119,20 +138,6 @@ class ListActivity : AppCompatActivity(), UIObject { LauncherAction.SETTINGS.launch(this@ListActivity) } - binding.listLock.visibility = - if (intention != ListActivityIntention.VIEW) { - View.GONE - } else if (!isPrivateSpaceSetUp(this)) { - View.GONE - } else if (LauncherPreferences.apps().hidePrivateSpaceApps()) { - if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { - View.VISIBLE - } else { - View.GONE - } - } else { - View.VISIBLE - } if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { isPrivateSpaceSetUp(this, showToast = true, launchSettings = true) @@ -200,15 +205,16 @@ class ListActivity : AppCompatActivity(), UIObject { fun updateTitle() { var titleResource = intention.titleResource if (intention == ListActivityIntention.VIEW) { - titleResource = if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { - R.string.list_title_hidden - } else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { - R.string.list_title_private_space - } else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { - R.string.list_title_favorite - } else { - R.string.list_title_view - } + titleResource = + if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { + R.string.list_title_hidden + } else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { + R.string.list_title_private_space + } else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) { + R.string.list_title_favorite + } else { + R.string.list_title_view + } } binding.listHeading.text = getString(titleResource) From e250a58ef4509c682376470b557bf5cf72713006 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 13:37:41 +0100 Subject: [PATCH 5/7] add new action: adjust volume --- .../android/launcher/actions/LauncherAction.kt | 18 ++++++++++-------- .../res/drawable/baseline_volume_adjust_24.xml | 16 ++++++++++++++++ app/src/main/res/values-de/strings.xml | 5 +++-- app/src/main/res/values/strings.xml | 5 +++-- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_volume_adjust_24.xml diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt index 3c89a67..5d2be94 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt @@ -88,13 +88,19 @@ enum class LauncherAction( "volume_up", R.string.list_other_volume_up, R.drawable.baseline_volume_up_24, - { context -> audioVolumeAdjust(context, true) } + { context -> audioVolumeAdjust(context, AudioManager.ADJUST_RAISE) } ), VOLUME_DOWN( "volume_down", R.string.list_other_volume_down, R.drawable.baseline_volume_down_24, - { context -> audioVolumeAdjust(context, false) } + { context -> audioVolumeAdjust(context, AudioManager.ADJUST_LOWER) } + ), + VOLUME_ADJUST( + "volume_adjust", + R.string.list_other_volume_adjust, + R.drawable.baseline_volume_adjust_24, + { context -> audioVolumeAdjust(context, AudioManager.ADJUST_SAME) } ), TRACK_PLAY_PAUSE( "play_pause_track", @@ -181,17 +187,13 @@ private fun audioManagerPressKey(context: Context, key: Int) { } -private fun audioVolumeAdjust(context: Context, louder: Boolean) { +private fun audioVolumeAdjust(context: Context, direction: Int) { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_MUSIC, - if (louder) { - AudioManager.ADJUST_RAISE - } else { - AudioManager.ADJUST_LOWER - }, + direction, AudioManager.FLAG_SHOW_UI ) } diff --git a/app/src/main/res/drawable/baseline_volume_adjust_24.xml b/app/src/main/res/drawable/baseline_volume_adjust_24.xml new file mode 100644 index 0000000..38e6a8b --- /dev/null +++ b/app/src/main/res/drawable/baseline_volume_adjust_24.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 17b840f..cf7f681 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -152,8 +152,9 @@ Alle Anwendungen Favoriten Privaten Bereich (ent)sperren - Musik: Lauter - Musik: Leiser + Lauter + Leiser + Lautstärke ändern Musik: Weiter Musik: Zurück Nichts tun diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3177442..1a79b5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -250,8 +250,9 @@ Favorite Applications Private Space Toggle Private Space Lock - Music: Louder - Music: Quieter + Raise Volume + Lower Volume + Adjust Volume Music: Next Music: Previous Music: Play / Pause From 077ee4381a7783ff1d044b9cb6cce437eae2f942 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 15:27:26 +0100 Subject: [PATCH 6/7] lint --- .../de/jrpie/android/launcher/Application.kt | 11 +++--- .../de/jrpie/android/launcher/Functions.kt | 4 +-- .../LauncherPreferences$Config.java | 2 ++ .../launcher/preferences/legacy/Version1.kt | 2 +- .../launcher/preferences/legacy/Version3.kt | 16 ++++----- .../jrpie/android/launcher/ui/HomeActivity.kt | 15 ++++---- .../launcher/ui/TouchGestureDetector.kt | 34 +++++++++++++++---- .../ui/list/apps/AppsRecyclerAdapter.kt | 2 +- .../ui/list/apps/ContextMenuActions.kt | 3 +- .../ui/list/other/OtherRecyclerAdapter.kt | 2 +- .../ui/settings/meta/SettingsFragmentMeta.kt | 3 +- .../launcher/ui/tutorial/TutorialActivity.kt | 17 +++++++++- 12 files changed, 75 insertions(+), 36 deletions(-) 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 09229ab..e674e4e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -10,6 +10,8 @@ import android.content.pm.ShortcutInfo import android.os.AsyncTask import android.os.Build import android.os.Build.VERSION_CODES +import android.os.Handler +import android.os.Looper import android.os.UserHandle import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData @@ -108,12 +110,10 @@ class Application : android.app.Application() { // Try to restore old preferences migratePreferencesToNewVersion(this) - // First time opening the app: set defaults and start tutorial + // First time opening the app: set defaults + // The tutorial is started from HomeActivity#onStart, as starting it here is blocked by android if (!LauncherPreferences.internal().started()) { resetPreferences(this) - - LauncherPreferences.internal().started(true) - openTutorial(this) } @@ -134,7 +134,8 @@ class Application : android.app.Application() { it.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) } } - ContextCompat.registerReceiver(this, profileAvailabilityBroadcastReceiver, filter, + ContextCompat.registerReceiver( + this, profileAvailabilityBroadcastReceiver, filter, ContextCompat.RECEIVER_EXPORTED ) } 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 7c2abbf..57f13a5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -135,9 +135,7 @@ fun openInBrowser(url: String, context: Context) { } fun openTutorial(context: Context) { - context.startActivity(Intent(context, TutorialActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }) + context.startActivity(Intent(context, TutorialActivity::class.java)) } 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 ca60591..85979fe 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 @@ -21,8 +21,10 @@ import eu.jonahbauer.android.preference.annotations.Preferences; r = R.class, value = { @PreferenceGroup(name = "internal", prefix = "settings_internal_", suffix = "_key", value = { + // set after the user finished the tutorial @Preference(name = "started", type = boolean.class, defaultValue = "false"), @Preference(name = "started_time", type = long.class), + // see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt @Preference(name = "version_code", type = int.class, defaultValue = "-1"), }), @PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = { 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 6408b70..6252811 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 @@ -100,7 +100,7 @@ private fun migrateAppInfoStringMap(key: String) { } }?.toMap(HashMap()) )?.let { - preferences.edit().putStringSet(key, it as Set).apply() + preferences.edit().putStringSet(key, it).apply() } } 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 index 7698e62..4a9241f 100644 --- 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 @@ -11,6 +11,7 @@ import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPre import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.util.HashSet +import androidx.core.content.edit /** * Migrate preferences from version 3 (used until version 0.0.23) to the current format @@ -42,6 +43,7 @@ private fun migrateSetAppInfo(key: String, preferences: SharedPreferences, edito deserializeSet(preferences.getStringSet(key, null))?.let { set.addAll(it) } + @Suppress("UNCHECKED_CAST") editor.putStringSet( key, serializer.serialize(set as java.util.Set) as Set? @@ -60,6 +62,7 @@ private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences, deserializeMap(preferences.getStringSet(key, null))?.let { map.putAll(it) } + @Suppress("UNCHECKED_CAST") editor.putStringSet(key, serializer.serialize(map) as Set?) } catch (e: Exception) { e.printStackTrace() @@ -72,14 +75,11 @@ fun migratePreferencesFromVersion3() { 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() - - + preferences.edit { + migrateSetAppInfo(LauncherPreferences.apps().keys().favorites(), preferences, this) + migrateSetAppInfo(LauncherPreferences.apps().keys().hidden(), preferences, this) + migrateMapAppInfoString(LauncherPreferences.apps().keys().customNames(), preferences, this) + } LauncherPreferences.internal().versionCode(4) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 61a4250..7875473 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -9,9 +9,6 @@ import android.util.DisplayMetrics import android.view.KeyEvent import android.view.MotionEvent import android.view.View -import android.view.Window -import android.view.WindowInsets -import android.view.WindowInsetsController import android.window.OnBackInvokedDispatcher import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible @@ -20,6 +17,7 @@ import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.databinding.HomeBinding +import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity import java.util.Locale @@ -58,7 +56,6 @@ class HomeActivity : UIObject, AppCompatActivity() { super.onCreate(savedInstanceState) super.onCreate() - val displayMetrics = DisplayMetrics() windowManager.defaultDisplay.getMetrics(displayMetrics) @@ -88,8 +85,6 @@ class HomeActivity : UIObject, AppCompatActivity() { binding.buttonFallbackSettings.setOnClickListener { LauncherAction.SETTINGS.invoke(this) } - - } override fun onStart() { @@ -97,6 +92,11 @@ class HomeActivity : UIObject, AppCompatActivity() { super.onStart() + // If the tutorial was not finished, start it + if (!LauncherPreferences.internal().started()) { + openTutorial(this) + } + LauncherPreferences.getSharedPreferences() .registerOnSharedPreferenceChangeListener(sharedPreferencesListener) @@ -220,7 +220,8 @@ class HomeActivity : UIObject, AppCompatActivity() { } override fun onTouchEvent(event: MotionEvent): Boolean { - return touchGestureDetector.onTouchEvent(event) || super.onTouchEvent(event) + touchGestureDetector.onTouchEvent(event) + return true } override fun setOnClicks() { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt index 00629a5..1c05d54 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt @@ -1,6 +1,8 @@ package de.jrpie.android.launcher.ui import android.content.Context +import android.os.Handler +import android.os.Looper import android.view.MotionEvent import android.view.ViewConfiguration import de.jrpie.android.launcher.actions.Gesture @@ -27,6 +29,8 @@ class TouchGestureDetector( private val MIN_TRIANGLE_HEIGHT = 250 + private val longPressHandler = Handler(Looper.getMainLooper()) + data class Vector(val x: Float, val y: Float) { fun absSquared(): Float { @@ -83,16 +87,28 @@ class TouchGestureDetector( } private var paths = HashMap() + private var gestureIsLongClick = false private var lastTappedTime = 0L private var lastTappedLocation: Vector? = null - fun onTouchEvent(event: MotionEvent): Boolean { + fun onTouchEvent(event: MotionEvent) { val pointerIdToIndex = (0.. LONG_PRESS_TIMEOUT) { - // TODO: Don't wait until the finger is lifted. - // Instead set a timer to start long click as soon as LONG_PRESS_TIMEOUT is reached - Gesture.LONG_CLICK.invoke(context) } } else { // detect swipes 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 0c0407e..1f275e4 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 @@ -67,7 +67,7 @@ class AppsRecyclerAdapter( override fun onClick(v: View) { val rect = Rect() img.getGlobalVisibleRect(rect) - selectItem(adapterPosition, rect) + selectItem(bindingAdapterPosition, rect) } init { 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 c1f3406..8b681b9 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 @@ -22,6 +22,7 @@ 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 +import androidx.core.net.toUri private const val LOG_TAG = "AppContextMenu" @@ -44,7 +45,7 @@ fun AbstractAppInfo.uninstall(activity: Activity) { Log.i(LOG_TAG, "uninstalling $this") val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) - intent.data = Uri.parse("package:$packageName") + intent.data = "package:$packageName".toUri() getUserFromId(userId, activity).let { user -> intent.putExtra(Intent.EXTRA_USER, user) } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt index 97d1c84..dfba334 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt @@ -33,7 +33,7 @@ class OtherRecyclerAdapter(val activity: Activity) : override fun onClick(v: View) { - val pos = adapterPosition + val pos = bindingAdapterPosition val content = othersList[pos] forGesture?.let { returnChoiceIntent(it, content) } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt index 70a225d..4cce930 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt @@ -16,6 +16,7 @@ import de.jrpie.android.launcher.copyToClipboard import de.jrpie.android.launcher.databinding.SettingsMetaBinding import de.jrpie.android.launcher.getDeviceInfo import de.jrpie.android.launcher.openInBrowser +import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.resetPreferences import de.jrpie.android.launcher.ui.LegalInfoActivity import de.jrpie.android.launcher.ui.UIObject @@ -48,7 +49,7 @@ class SettingsFragmentMeta : Fragment(), UIObject { override fun setOnClicks() { binding.settingsMetaButtonViewTutorial.setOnClickListener { - startActivity(Intent(this.context, TutorialActivity::class.java)) + openTutorial(requireContext()) } // prompting for settings-reset confirmation diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/TutorialActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/TutorialActivity.kt index 28e2e02..fd60d19 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/TutorialActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/TutorialActivity.kt @@ -2,7 +2,9 @@ package de.jrpie.android.launcher.ui.tutorial import android.content.Intent import android.content.res.Resources +import android.os.Build import android.os.Bundle +import android.window.OnBackInvokedDispatcher import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -33,6 +35,19 @@ class TutorialActivity : AppCompatActivity(), UIObject { super.onCreate(savedInstanceState) super.onCreate() + // Handle back key / gesture on Android 13+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + onBackInvokedDispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_OVERLAY + ) { + // prevent going back when the tutorial is shown for the first time + if (!LauncherPreferences.internal().started()) { + return@registerOnBackInvokedCallback + } + finish() + } + } + // Initialise layout setContentView(R.layout.tutorial) @@ -60,7 +75,7 @@ class TutorialActivity : AppCompatActivity(), UIObject { } } - // Default: prevent going back, allow if viewed again later + // prevent going back when the tutorial is shown for the first time override fun onBackPressed() { if (LauncherPreferences.internal().started()) super.onBackPressed() From 55af3927063b31ba75d5e03a2794f7284175fcba Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 14 Mar 2025 15:40:06 +0100 Subject: [PATCH 7/7] add donate button --- .../ui/settings/meta/SettingsFragmentMeta.kt | 48 +++++++------------ app/src/main/res/layout/settings_meta.xml | 11 ++++- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt index 4cce930..26f276a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt @@ -48,6 +48,15 @@ class SettingsFragmentMeta : Fragment(), UIObject { override fun setOnClicks() { + fun bindURL(view: View, urlRes: Int) { + view.setOnClickListener { + openInBrowser( + getString(urlRes), + requireContext() + ) + } + } + binding.settingsMetaButtonViewTutorial.setOnClickListener { openTutorial(requireContext()) } @@ -70,12 +79,7 @@ class SettingsFragmentMeta : Fragment(), UIObject { // view code - binding.settingsMetaButtonViewCode.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_link_github), - requireContext() - ) - } + bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github) // report a bug binding.settingsMetaButtonReportBug.setOnClickListener { @@ -111,37 +115,19 @@ class SettingsFragmentMeta : Fragment(), UIObject { } // join chat - binding.settingsMetaButtonJoinChat.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_chat_url), - requireContext() - ) - } - + bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url) // contact developer - binding.settingsMetaButtonContact.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_contact_url), - requireContext() - ) - } + // bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url) // contact fork developer - binding.settingsMetaButtonForkContact.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_fork_contact_url), - requireContext() - ) - } + bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url) + + // donate + bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url) // privacy policy - binding.settingsMetaButtonPrivacy.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_privacy_url), - requireContext() - ) - } + bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url) // legal info binding.settingsMetaButtonLicenses.setOnClickListener { diff --git a/app/src/main/res/layout/settings_meta.xml b/app/src/main/res/layout/settings_meta.xml index a5876e6..6f21baa 100644 --- a/app/src/main/res/layout/settings_meta.xml +++ b/app/src/main/res/layout/settings_meta.xml @@ -59,12 +59,12 @@ android:text="@string/settings_meta_join_chat" android:textAllCaps="false" /> -