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 81e58d7..57f13a5 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 } @@ -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/actions/LauncherAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt index 1ed6473..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 @@ -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,37 @@ 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", 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", @@ -176,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/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 { 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/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/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) 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/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/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..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 @@ -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 @@ -47,8 +48,17 @@ class SettingsFragmentMeta : Fragment(), UIObject { override fun setOnClicks() { + fun bindURL(view: View, urlRes: Int) { + view.setOnClickListener { + openInBrowser( + getString(urlRes), + requireContext() + ) + } + } + binding.settingsMetaButtonViewTutorial.setOnClickListener { - startActivity(Intent(this.context, TutorialActivity::class.java)) + openTutorial(requireContext()) } // prompting for settings-reset confirmation @@ -69,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 { @@ -110,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/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() 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 @@ + + + + + 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/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" /> -