diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 209b346..9a671f0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -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 diff --git a/.scripts/release.sh b/.scripts/release.sh index f207c87..0c71f4a 100755 --- a/.scripts/release.sh +++ b/.scripts/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -export JAVA_HOME="/usr/lib/jvm/java-21-openjdk/" +export JAVA_HOME="/usr/lib/jvm/java-23-openjdk/" OUTPUT_DIR="$HOME/launcher-release" BUILD_TOOLS_DIR="$HOME/Android/Sdk/build-tools/35.0.0" KEYSTORE="$HOME/data/keys/launcher_jrpie.jks" diff --git a/README.md b/README.md index ce1d0d0..016b362 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ The following gestures are available: - swipe up / down / left / right, - swipe with two fingers, - swipe on the left / right resp. top / bottom edge, - - tap, then swipe up / down / left / right, - draw < / > / V / Λ - click on date / time, - double click, diff --git a/app/build.gradle b/app/build.gradle index 1a0a6fb..6656d5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { minSdkVersion 21 targetSdkVersion 35 compileSdk 35 - versionCode 44 - versionName "0.1.4" + versionCode 38 + versionName "0.0.22" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -85,17 +85,16 @@ android { // Disables dependency metadata when building Android App Bundles. includeInBundle = false } - lint { + + lintOptions { abortOnError false } - } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.activity:activity-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.core:core-ktx:1.15.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5f8831..3096d6d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,16 +19,6 @@ android:supportsRtl="true" android:theme="@style/launcherBaseTheme" tools:ignore="UnusedAttribute"> - - - - - - @@ -97,4 +87,4 @@ - + \ No newline at end of file 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 e6cce23..c856066 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -7,6 +7,7 @@ import android.content.IntentFilter import android.content.SharedPreferences import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo +import android.os.AsyncTask import android.os.Build import android.os.Build.VERSION_CODES import android.os.UserHandle @@ -14,18 +15,15 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData import androidx.preference.PreferenceManager import de.jrpie.android.launcher.actions.TorchManager -import de.jrpie.android.launcher.apps.AbstractAppInfo -import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo +import de.jrpie.android.launcher.apps.AppInfo +import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion import de.jrpie.android.launcher.preferences.resetPreferences -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class Application : android.app.Application() { - val apps = MutableLiveData>() + val apps = MutableLiveData>() val privateSpaceLocked = MutableLiveData() private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { @@ -84,12 +82,10 @@ class Application : android.app.Application() { } var torchManager: TorchManager? = null - private var customAppNames: HashMap? = null + private var customAppNames: HashMap? = null private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref -> if (pref == getString(R.string.settings_apps_custom_names_key)) { customAppNames = LauncherPreferences.apps().customNames() - } else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) { - loadApps() } } @@ -110,10 +106,12 @@ class Application : android.app.Application() { // Try to restore old preferences migratePreferencesToNewVersion(this) - // First time opening the app: set defaults - // The tutorial is started from HomeActivity#onStart, as starting it here is blocked by android + // First time opening the app: set defaults and start tutorial if (!LauncherPreferences.internal().started()) { resetPreferences(this) + + LauncherPreferences.internal().started(true) + openTutorial(this) } @@ -134,27 +132,21 @@ 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 ) } - if (Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1) { - removeUnusedShortcuts(this) - } loadApps() } - fun getCustomAppNames(): HashMap { + fun getCustomAppNames(): HashMap { return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap()) .also { customAppNames = it } } private fun loadApps() { privateSpaceLocked.postValue(isPrivateSpaceLocked(this)) - CoroutineScope(Dispatchers.Default).launch { - apps.postValue(getApps(packageManager, applicationContext)) - } + AsyncTask.execute { apps.postValue(getApps(packageManager, applicationContext)) } } } 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 afc2c31..5f7e9c9 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -9,9 +9,8 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.LauncherApps -import android.content.pm.LauncherApps.ShortcutQuery import android.content.pm.PackageManager -import android.content.pm.ShortcutInfo +import android.net.Uri import android.os.Build import android.os.Bundle import android.os.UserHandle @@ -19,27 +18,25 @@ import android.os.UserManager import android.provider.Settings import android.util.Log import android.widget.Toast -import androidx.annotation.RequiresApi import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.actions.ShortcutAction -import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER -import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo -import de.jrpie.android.launcher.apps.DetailedPinnedShortcutInfo -import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.apps.getPrivateSpaceUser import de.jrpie.android.launcher.apps.isPrivateSpaceSupported import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity -import androidx.core.net.toUri -const val LOG_TAG = "Launcher" +/* REQUEST CODES */ + +const val REQUEST_CHOOSE_APP = 1 +const val REQUEST_UNINSTALL = 2 const val REQUEST_SET_DEFAULT_HOME = 42 +const val LOG_TAG = "Launcher" + fun isDefaultHomeScreen(context: Context): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val roleManager = context.getSystemService(RoleManager::class.java) @@ -61,7 +58,7 @@ fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context is Activity - && checkDefault // using role manager only works when µLauncher is not already the default. + && !isDefault // using role manager only works when µLauncher is not already the default. ) { val roleManager = context.getSystemService(RoleManager::class.java) context.startActivityForResult( @@ -84,43 +81,9 @@ fun getUserFromId(userId: Int?, context: Context): UserHandle { return profiles.firstOrNull { it.hashCode() == userId } ?: profiles[0] } -@RequiresApi(Build.VERSION_CODES.N_MR1) -fun removeUnusedShortcuts(context: Context) { - val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - fun getShortcuts(profile: UserHandle): List? { - return try { - launcherApps.getShortcuts( - ShortcutQuery().apply { - setQueryFlags(ShortcutQuery.FLAG_MATCH_PINNED) - }, - profile - ) - } catch (e: Exception) { - // https://github.com/jrpie/launcher/issues/116 - return null - } - } - - val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager - val boundActions: MutableSet = - Gesture.entries.mapNotNull { Action.forGesture(it) as? ShortcutAction }.map { it.shortcut } - .toMutableSet() - LauncherPreferences.apps().pinnedShortcuts()?.let { boundActions.addAll(it) } - try { - userManager.userProfiles.filter { !userManager.isQuietModeEnabled(it) }.forEach { profile -> - getShortcuts(profile)?.groupBy { it.`package` }?.forEach { (p, shortcuts) -> - launcherApps.pinShortcuts(p, - shortcuts.filter { boundActions.contains(PinnedShortcutInfo(it)) } - .map { it.id }.toList(), - profile - ) - } - } - } catch (_: SecurityException) { } -} fun openInBrowser(url: String, context: Context) { - val intent = Intent(Intent.ACTION_VIEW, url.toUri()) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) intent.putExtras(Bundle().apply { putBoolean("new_window", true) }) try { context.startActivity(intent) @@ -130,19 +93,18 @@ fun openInBrowser(url: String, context: Context) { } fun openTutorial(context: Context) { - context.startActivity(Intent(context, TutorialActivity::class.java)) + context.startActivity(Intent(context, TutorialActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }) } /** * Load all apps. */ -fun getApps( - packageManager: PackageManager, - context: Context -): MutableList { - var start = System.currentTimeMillis() - val loadList = mutableListOf() +fun getApps(packageManager: PackageManager, context: Context): MutableList { + val start = System.currentTimeMillis() + val loadList = mutableListOf() val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager @@ -179,7 +141,7 @@ fun getApps( i.addCategory(Intent.CATEGORY_LAUNCHER) val allApps = packageManager.queryIntentActivities(i, 0) 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( app, ri.loadLabel(packageManager), @@ -189,24 +151,22 @@ fun getApps( loadList.add(detailedAppInfo) } } - loadList.sortBy { it.getCustomLabel(context) } + 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)") - 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 } + +// Used in Tutorial and Settings `ActivityOnResult` +fun saveListActivityChoice(data: Intent?) { + val forGesture = data?.getStringExtra("forGesture") ?: return + Gesture.byId(forGesture)?.let { Action.setActionForGesture(it, Action.fromIntent(data)) } +} + + // used for the bug report button fun getDeviceInfo(): String { return """ diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt index 9a2dc62..ddef92a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt @@ -2,6 +2,7 @@ package de.jrpie.android.launcher.actions import android.app.Activity import android.content.Context +import android.content.Intent import android.content.SharedPreferences.Editor import android.graphics.Rect import android.graphics.drawable.Drawable @@ -11,7 +12,6 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import androidx.core.content.edit @Serializable @@ -29,6 +29,10 @@ sealed interface Action { prefEditor.putString(id, Json.encodeToString(this)) } + fun writeToIntent(intent: Intent) { + intent.putExtra("action", Json.encodeToString(this)) + } + companion object { fun forGesture(gesture: Gesture): Action? { @@ -40,23 +44,23 @@ sealed interface Action { } fun resetToDefaultActions(context: Context) { - LauncherPreferences.getSharedPreferences().edit { - val boundActions = HashSet() - Gesture.entries.forEach { gesture -> - context.resources - .getStringArray(gesture.defaultsResource) - .filterNot { boundActions.contains(it) } - .map { Pair(it, Json.decodeFromString(it)) } - .firstOrNull { it.second.isAvailable(context) } - ?.apply { - // allow to bind CHOOSE to multiple gestures - if (second != LauncherAction.CHOOSE) { - boundActions.add(first) - } - second.bindToGesture(this@edit, gesture.id) + val editor = LauncherPreferences.getSharedPreferences().edit() + val boundActions = HashSet() + Gesture.entries.forEach { gesture -> + context.resources + .getStringArray(gesture.defaultsResource) + .filterNot { boundActions.contains(it) } + .map { Pair(it, Json.decodeFromString(it)) } + .firstOrNull { it.second.isAvailable(context) } + ?.apply { + // allow to bind CHOOSE to multiple gestures + if (second != LauncherAction.CHOOSE) { + boundActions.add(first) } - } + second.bindToGesture(editor, gesture.id) + } } + editor.apply() } fun setActionForGesture(gesture: Gesture, action: Action?) { @@ -64,15 +68,15 @@ sealed interface Action { clearActionForGesture(gesture) return } - LauncherPreferences.getSharedPreferences().edit { - action.bindToGesture(this, gesture.id) - } + val editor = LauncherPreferences.getSharedPreferences().edit() + action.bindToGesture(editor, gesture.id) + editor.apply() } fun clearActionForGesture(gesture: Gesture) { - LauncherPreferences.getSharedPreferences().edit { - remove(gesture.id) - } + LauncherPreferences.getSharedPreferences().edit() + .remove(gesture.id) + .apply() } fun launch( @@ -83,9 +87,6 @@ sealed interface Action { ) { if (action != null && action.invoke(context)) { if (context is Activity) { - // There does not seem to be a good alternative to overridePendingTransition. - // Note that we can't use overrideActivityTransition here. - @Suppress("deprecation") context.overridePendingTransition(animationIn, animationOut) } } else { @@ -96,5 +97,10 @@ sealed interface Action { ).show() } } + + fun fromIntent(data: Intent): Action? { + val json = data.getStringExtra("action") ?: return null + return Json.decodeFromString(json) + } } } \ No newline at end of file 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 1446b13..90145aa 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt @@ -11,7 +11,7 @@ import android.graphics.drawable.Drawable import android.util.Log import de.jrpie.android.launcher.R import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.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.ui.list.apps.openSettings import kotlinx.serialization.SerialName @@ -67,7 +67,7 @@ class AppAction(val app: AppInfo) : Action { } override fun getIcon(context: Context): Drawable? { - return DetailedAppInfo.fromAppInfo(app, context)?.getIcon(context) + return DetailedAppInfo.fromAppInfo(app, context)?.icon } override fun isAvailable(context: Context): Boolean { diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/Gesture.kt b/app/src/main/java/de/jrpie/android/launcher/actions/Gesture.kt index a2434e1..a4f25b4 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/Gesture.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/Gesture.kt @@ -79,13 +79,6 @@ enum class Gesture( R.array.default_up_right, R.anim.bottom_up ), - TAP_AND_SWIPE_UP( - "action.tap_up", - R.string.settings_gesture_tap_up, - R.string.settings_gesture_description_tap_up, - R.array.default_up, - R.anim.bottom_up - ), SWIPE_UP_DOUBLE( "action.double_up", R.string.settings_gesture_double_up, @@ -114,13 +107,6 @@ enum class Gesture( R.array.default_down_right, R.anim.top_down ), - TAP_AND_SWIPE_DOWN( - "action.tap_down", - R.string.settings_gesture_tap_down, - R.string.settings_gesture_description_tap_down, - R.array.default_down, - R.anim.bottom_up - ), SWIPE_DOWN_DOUBLE( "action.double_down", R.string.settings_gesture_double_down, @@ -149,13 +135,6 @@ enum class Gesture( R.array.default_messengers, R.anim.right_left ), - TAP_AND_SWIPE_LEFT( - "action.tap_left", - R.string.settings_gesture_tap_left, - R.string.settings_gesture_description_tap_left, - R.array.default_messengers, - R.anim.right_left - ), SWIPE_LEFT_DOUBLE( "action.double_left", R.string.settings_gesture_double_left, @@ -184,13 +163,6 @@ enum class Gesture( R.array.default_right_bottom, R.anim.left_right ), - TAP_AND_SWIPE_RIGHT( - "action.tap_right", - R.string.settings_gesture_tap_right, - R.string.settings_gesture_description_tap_right, - R.array.default_right, - R.anim.left_right - ), SWIPE_RIGHT_DOUBLE( "action.double_right", R.string.settings_gesture_double_right, @@ -250,7 +222,7 @@ enum class Gesture( "action.back", R.string.settings_gesture_back, R.string.settings_gesture_description_back, - R.array.default_back + R.array.default_up ); enum class Edge { @@ -307,17 +279,6 @@ enum class Gesture( } } - fun getTapComboVariant(): Gesture { - return when (this) { - SWIPE_UP -> TAP_AND_SWIPE_UP - SWIPE_DOWN -> TAP_AND_SWIPE_DOWN - SWIPE_LEFT -> TAP_AND_SWIPE_LEFT - SWIPE_RIGHT -> TAP_AND_SWIPE_RIGHT - else -> this - } - - } - fun isDoubleVariant(): Boolean { return when (this) { SWIPE_UP_DOUBLE, 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 6ba467e..4f89758 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 @@ -11,11 +11,8 @@ import android.view.KeyEvent import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService 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 @@ -69,11 +66,7 @@ enum class LauncherAction( R.string.list_other_list_private_space, R.drawable.baseline_security_24, { context -> - if ((context.applicationContext as Application).privateSpaceLocked.value != true - || !hidePrivateSpaceWhenLocked(context) - ) { - openAppsList(context, private = true) - } + openAppsList(context, private = true) }, available = { _ -> isPrivateSpaceSupported() @@ -89,38 +82,22 @@ enum class LauncherAction( VOLUME_UP( "volume_up", R.string.list_other_volume_up, - R.drawable.baseline_volume_up_24, - { context -> audioVolumeAdjust(context, AudioManager.ADJUST_RAISE) } + R.drawable.baseline_volume_up_24, ::audioVolumeUp ), VOLUME_DOWN( "volume_down", R.string.list_other_volume_down, - R.drawable.baseline_volume_down_24, - { 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) } + R.drawable.baseline_volume_down_24, ::audioVolumeDown ), TRACK_NEXT( "next_track", R.string.list_other_track_next, - R.drawable.baseline_skip_next_24, - { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_NEXT) } + R.drawable.baseline_skip_next_24, ::audioNextTrack ), TRACK_PREV( "previous_track", R.string.list_other_track_previous, - R.drawable.baseline_skip_previous_24, - { context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PREVIOUS) } + R.drawable.baseline_skip_previous_24, ::audioPreviousTrack ), EXPAND_NOTIFICATIONS_PANEL( "expand_notifications_panel", @@ -134,14 +111,6 @@ enum class LauncherAction( R.drawable.baseline_settings_applications_24, ::expandSettingsPanel ), - RECENT_APPS( - "recent_apps", - R.string.list_other_recent_apps, - R.drawable.baseline_apps_24, - LauncherAccessibilityService::openRecentApps, - false, - { _ -> BuildConfig.USE_ACCESSIBILITY_SERVICE } - ), LOCK_SCREEN( "lock_screen", R.string.list_other_lock_screen, @@ -152,13 +121,7 @@ enum class LauncherAction( "toggle_torch", R.string.list_other_torch, R.drawable.baseline_flashlight_on_24, - ::toggleTorch, - ), - LAUNCH_OTHER_LAUNCHER( - "launcher_other_launcher", - R.string.list_other_launch_other_launcher, - R.drawable.baseline_home_24, - ::launchOtherLauncher + ::toggleTorch ), NOP("nop", R.string.list_other_nop, R.drawable.baseline_not_interested_24, {}); @@ -192,28 +155,56 @@ enum class LauncherAction( /* Media player actions */ -private fun audioManagerPressKey(context: Context, key: Int) { - val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val eventTime: Long = SystemClock.uptimeMillis() - val downEvent = - KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, key, 0) - mAudioManager.dispatchMediaKeyEvent(downEvent) - val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, key, 0) - mAudioManager.dispatchMediaKeyEvent(upEvent) +private fun audioNextTrack(context: Context) { + + val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + val eventTime: Long = SystemClock.uptimeMillis() + + val downEvent = + KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT, 0) + mAudioManager.dispatchMediaKeyEvent(downEvent) + + val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT, 0) + mAudioManager.dispatchMediaKeyEvent(upEvent) } -private fun audioVolumeAdjust(context: Context, direction: Int) { +private fun audioPreviousTrack(context: Context) { + val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + val eventTime: Long = SystemClock.uptimeMillis() + + val downEvent = + KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0) + mAudioManager.dispatchMediaKeyEvent(downEvent) + + val upEvent = + KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0) + mAudioManager.dispatchMediaKeyEvent(upEvent) +} + +private fun audioVolumeUp(context: Context) { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_MUSIC, - direction, + AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI ) } +private fun audioVolumeDown(context: Context) { + val audioManager = + context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + audioManager.adjustStreamVolume( + AudioManager.STREAM_MUSIC, + AudioManager.ADJUST_LOWER, + AudioManager.FLAG_SHOW_UI + ) +} /* End media player actions */ private fun toggleTorch(context: Context) { @@ -264,15 +255,6 @@ private fun expandSettingsPanel(context: Context) { } } -private fun launchOtherLauncher(context: Context) { - context.startActivity( - Intent.createChooser( - Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }, - context.getString(R.string.list_other_launch_other_launcher) - ) - ) -} - private fun openSettings(context: Context) { context.startActivity(Intent(context, SettingsActivity::class.java)) } @@ -338,4 +320,5 @@ private class LauncherActionSerializer : KSerializer { encodeSerializableElement(descriptor, 0, String.serializer(), value.id) } } + } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt deleted file mode 100644 index a89f9e2..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/actions/ShortcutAction.kt +++ /dev/null @@ -1,57 +0,0 @@ -package de.jrpie.android.launcher.actions - -import android.app.Service -import android.content.Context -import android.content.pm.LauncherApps -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.os.Build -import de.jrpie.android.launcher.apps.PinnedShortcutInfo -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -@SerialName("action:shortcut") -class ShortcutAction(val shortcut: PinnedShortcutInfo) : Action { - - override fun invoke(context: Context, rect: Rect?): Boolean { - val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { - // TODO - return false - } - shortcut.getShortcutInfo(context)?.let { - launcherApps.startShortcut(it, rect, null) - } - - // TODO: handle null - return true - } - - override fun label(context: Context): String { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { - return "?" - } - - return shortcut.getShortcutInfo(context)?.longLabel?.toString() ?: "?" - } - - override fun getIcon(context: Context): Drawable? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { - return null - } - val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - return shortcut.getShortcutInfo(context)?.let { launcherApps.getShortcutBadgedIconDrawable(it, 0) } - } - - override fun isAvailable(context: Context): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { - return false - } - return shortcut.getShortcutInfo(context) != null - } - - override fun canReachSettings(): Boolean { - return false - } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LauncherAccessibilityService.kt b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LauncherAccessibilityService.kt index 7cb32d9..a8ef6f2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LauncherAccessibilityService.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LauncherAccessibilityService.kt @@ -22,44 +22,26 @@ class LauncherAccessibilityService : AccessibilityService() { companion object { private const val TAG = "Launcher Accessibility" - private const val ACTION_REQUEST_ENABLE = "ACTION_REQUEST_ENABLE" const val ACTION_LOCK_SCREEN = "ACTION_LOCK_SCREEN" - const val ACTION_RECENT_APPS = "ACTION_RECENT_APPS" - private fun invoke(context: Context, action: String, failureMessageRes: Int) { + fun lockScreen(context: Context) { try { context.startService( Intent( context, LauncherAccessibilityService::class.java ).apply { - this.action = action + action = ACTION_LOCK_SCREEN }) - } catch (_: Exception) { + } catch (e: Exception) { Toast.makeText( context, - context.getString(failureMessageRes), + context.getString(R.string.alert_lock_screen_failed), Toast.LENGTH_LONG ).show() } } - fun lockScreen(context: Context) { - if (!isEnabled(context)) { - showEnableDialog(context) - } else { - invoke(context, ACTION_LOCK_SCREEN, R.string.alert_lock_screen_failed) - } - } - - fun openRecentApps(context: Context) { - if (!isEnabled(context)) { - showEnableDialog(context) - } else { - invoke(context, ACTION_RECENT_APPS, R.string.alert_recent_apps_failed) - } - } - fun isEnabled(context: Context): Boolean { val enabledServices = Settings.Secure.getString( context.contentResolver, @@ -76,7 +58,7 @@ class LauncherAccessibilityService : AccessibilityService() { setView(R.layout.dialog_consent_accessibility) setTitle(R.string.dialog_consent_accessibility_title) setPositiveButton(R.string.dialog_consent_accessibility_ok) { _, _ -> - invoke(context, ACTION_REQUEST_ENABLE, R.string.alert_enable_accessibility_failed) + lockScreen(context) } setNegativeButton(R.string.dialog_cancel) { _, _ -> } }.create().also { it.show() }.apply { @@ -112,9 +94,7 @@ class LauncherAccessibilityService : AccessibilityService() { } when (action) { - ACTION_REQUEST_ENABLE -> {} // do nothing ACTION_LOCK_SCREEN -> handleLockScreen() - ACTION_RECENT_APPS -> performGlobalAction(GLOBAL_ACTION_RECENTS) } } return super.onStartCommand(intent, flags, startId) diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt index 93b4cbf..8ae2415 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt @@ -6,10 +6,10 @@ import android.widget.Button import androidx.appcompat.app.AlertDialog import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService import de.jrpie.android.launcher.preferences.LauncherPreferences +@Suppress("unused") enum class LockMethod( private val lock: (Context) -> Unit, private val isEnabled: (Context) -> Boolean, diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt deleted file mode 100644 index dd60752..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractAppInfo.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt deleted file mode 100644 index 9c7413d..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AbstractDetailedAppInfo.kt +++ /dev/null @@ -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() - - if (label.isNullOrEmpty()) { - map.remove(getRawInfo()) - } else { - map[getRawInfo()] = label.toString() - } - LauncherPreferences.apps().customNames(map) - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt index ca387c0..ecc7eaa 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AppFilter.kt @@ -6,7 +6,6 @@ import android.os.Build import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.actions.ShortcutAction import de.jrpie.android.launcher.preferences.LauncherPreferences import java.util.Locale import kotlin.text.Regex.Companion.escape @@ -19,14 +18,13 @@ class AppFilter( var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE ) { - operator fun invoke(apps: List): List { + operator fun invoke(apps: List): List { var apps = - apps.sortedBy { app -> app.getCustomLabel(context).lowercase(Locale.ROOT) } + apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) } val hidden = LauncherPreferences.apps().hidden() ?: setOf() val favorites = LauncherPreferences.apps().favorites() ?: setOf() - val private = apps.filter { it.isPrivate() } - .map { it.getRawInfo() }.toSet() + val private = apps.filter { it.isPrivateSpaceApp }.map { it.app }.toSet() apps = apps.filter { info -> favoritesVisibility.predicate(favorites, info) @@ -37,13 +35,9 @@ class AppFilter( if (LauncherPreferences.apps().hideBoundApps()) { val boundApps = Gesture.entries .filter(Gesture::isEnabled) - .mapNotNull { g -> Action.forGesture(g) } - .mapNotNull { - (it as? AppAction)?.app - ?: (it as? ShortcutAction)?.shortcut - } + .mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app } .toSet() - apps = apps.filterNot { info -> boundApps.contains(info.getRawInfo()) } + apps = apps.filterNot { info -> boundApps.contains(info.app) } } // normalize text for search @@ -63,11 +57,11 @@ class AppFilter( if (query.isEmpty()) { return apps } else { - val r: MutableList = ArrayList() - val appsSecondary: MutableList = ArrayList() + val r: MutableList = ArrayList() + val appsSecondary: MutableList = ArrayList() val normalizedQuery: String = normalize(query) for (item in apps) { - val itemLabel: String = normalize(item.getCustomLabel(context)) + val itemLabel: String = normalize(item.getCustomLabel(context).toString()) if (itemLabel.startsWith(normalizedQuery)) { r.add(item) @@ -83,11 +77,11 @@ class AppFilter( companion object { enum class AppSetVisibility( - val predicate: (set: Set, AbstractDetailedAppInfo) -> Boolean + val predicate: (set: Set, DetailedAppInfo) -> Boolean ) { VISIBLE({ _, _ -> true }), - HIDDEN({ set, appInfo -> !set.contains(appInfo.getRawInfo()) }), - EXCLUSIVE({ set, appInfo -> set.contains(appInfo.getRawInfo()) }), + HIDDEN({ set, appInfo -> !set.contains(appInfo.app) }), + EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }), ; } diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt index 9534431..21614f8 100644 --- a/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt @@ -4,18 +4,33 @@ import android.app.Service import android.content.Context import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherApps -import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.getUserFromId -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json /** * Represents an app installed on the users device. * Contains the minimal amount of data required to identify the app. */ @Serializable -@SerialName("app") -data class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER): AbstractAppInfo { +class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) { + + fun serialize(): String { + return Json.encodeToString(this) + } + + override fun equals(other: Any?): Boolean { + if(other is AppInfo) { + return other.user == user && other.packageName == packageName + && other.activityName == activityName + } + return super.equals(other) + } + + override fun hashCode(): Int { + return packageName.hashCode() + } fun getLauncherActivityInfo( context: Context @@ -26,4 +41,17 @@ data class AppInfo(val packageName: String, val activityName: String?, val user: return activityList.firstOrNull { app -> app.name == activityName } ?: activityList.firstOrNull() } + + + override fun toString(): String { + return "AppInfo {package=$packageName, activity=$activityName, user=$user}" + } + + companion object { + const val INVALID_USER = -1 + + fun deserialize(serialized: String): AppInfo { + return Json.decodeFromString(serialized) + } + } } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt index 76f7fbb..d77bf93 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,21 +4,20 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.LauncherActivityInfo import android.graphics.drawable.Drawable -import android.os.UserHandle -import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.actions.AppAction -import de.jrpie.android.launcher.getUserFromId +import android.util.Log +import de.jrpie.android.launcher.Application +import de.jrpie.android.launcher.preferences.LauncherPreferences /** * Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows. */ class DetailedAppInfo( - private val app: AppInfo, - private val label: CharSequence, - private val icon: Drawable, - private val privateSpace: Boolean, - private val removable: Boolean = true, -): AbstractDetailedAppInfo { + val app: AppInfo, + val label: CharSequence, + val icon: Drawable, + val isPrivateSpaceApp: Boolean, + val isSystemApp: Boolean = false, +) { constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this( AppInfo( @@ -29,41 +28,29 @@ class DetailedAppInfo( activityInfo.label, activityInfo.getBadgedIcon(0), 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 - - override fun getLabel(): String { - return label.toString() + return map[app] ?: label } - override fun getIcon(context: Context): Drawable { - return icon - } + fun setCustomLabel(label: CharSequence?) { - override fun getRawInfo(): AppInfo { - return app - } + Log.i("Launcher", "Setting custom label for ${this.app} to ${label}.") + val map = LauncherPreferences.apps().customNames() ?: HashMap() - override fun getUser(context: Context): UserHandle { - return getUserFromId(app.user, context) - } + if (label.isNullOrEmpty()) { + map.remove(app) + } else { + map[app] = label.toString() + } - override fun isPrivate(): Boolean { - return privateSpace + LauncherPreferences.apps().customNames(map) } - override fun isRemovable(): Boolean { - return removable - } - - override fun getAction(): Action { - return AppAction(app) - } - - companion object { fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? { return appInfo.getLauncherActivityInfo(context)?.let { diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt deleted file mode 100644 index f66034d..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedPinnedShortcutInfo.kt +++ /dev/null @@ -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) - } - } - } -} \ No newline at end of file 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 deleted file mode 100644 index 54230ae..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/apps/PinnedShortcutInfo.kt +++ /dev/null @@ -1,46 +0,0 @@ -package de.jrpie.android.launcher.apps - -import android.app.Service -import android.content.ComponentName -import android.content.Context -import android.content.pm.LauncherApps -import android.content.pm.LauncherApps.ShortcutQuery -import android.content.pm.ShortcutInfo -import android.os.Build -import androidx.annotation.RequiresApi -import de.jrpie.android.launcher.getUserFromId -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - - -@RequiresApi(Build.VERSION_CODES.N_MR1) -@Serializable -@SerialName("shortcut") -data class PinnedShortcutInfo( - val id: String, - val packageName: String, - val activityName: String, - val user: Int -): AbstractAppInfo { - - constructor(info: ShortcutInfo) : this(info.id, info.`package`, info.activity?.className ?: "", info.userHandle.hashCode()) - - fun getShortcutInfo(context: Context): ShortcutInfo? { - val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - - 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 - } - } -} \ No newline at end of file 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 24665d7..9b37d60 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 @@ -91,17 +91,10 @@ fun isPrivateSpaceLocked(context: Context): Boolean { val privateSpaceUser = getPrivateSpaceUser(context) ?: return false return userManager.isQuietModeEnabled(privateSpaceUser) } - 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) @@ -123,18 +116,3 @@ fun togglePrivateSpaceLock(context: Context) { } } -@Suppress("SameReturnValue") -fun hidePrivateSpaceWhenLocked(context: Context): Boolean { - // Trying to access the setting as a 3rd party launcher raises a security exception. - // This is an Android bug: https://issuetracker.google.com/issues/352276244#comment5 - // The logic for this is implemented. - // TODO: replace this once the Android bug is fixed - return false - - // 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/ColorPreference.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/ColorPreference.kt index 0f95efd..a21d458 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/ColorPreference.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/ColorPreference.kt @@ -17,7 +17,6 @@ import androidx.core.graphics.green import androidx.core.graphics.red import androidx.preference.Preference import de.jrpie.android.launcher.R -import androidx.core.graphics.toColorInt class ColorPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) { @@ -53,7 +52,7 @@ class ColorPreference(context: Context, attrs: AttributeSet?) : AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { setView(R.layout.dialog_choose_color) setTitle(R.string.dialog_choose_color_title) - setPositiveButton(android.R.string.ok) { _, _ -> + setPositiveButton(R.string.dialog_select_color_ok) { _, _ -> persistInt(currentColor) summary = currentColor.getHex() } @@ -84,10 +83,10 @@ class ColorPreference(context: Context, attrs: AttributeSet?) : override fun onTextChanged(text: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun afterTextChanged(editable: Editable?) { preview.hasFocus() || return - val newText = editable?.toString() ?: return - newText.isBlank() && return + val newText = editable?.toString() + newText.isNullOrBlank() && return try { - val newColor = newText.toColorInt() + val newColor = Color.parseColor(newText.toString()) currentColor = newColor updateColor(false) } catch (_: IllegalArgumentException) { 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 85979fe..c216911 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java @@ -5,9 +5,8 @@ import java.util.Set; import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.actions.lock.LockMethod; -import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer; -import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer; -import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.Font; @@ -21,24 +20,20 @@ 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 = { - @Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), - @Preference(name = "hidden", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), - @Preference(name = "pinned_shortcuts", type = Set.class, serializer = SetPinnedShortcutInfoPreferenceSerializer.class), - @Preference(name = "custom_names", type = HashMap.class, serializer = MapAbstractAppInfoStringPreferenceSerializer.class), + @Preference(name = "favorites", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class), + @Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class), + @Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class), @Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), @Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"), @Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"), }), @PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = { - @Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT"), - @Preference(name = "reverse_layout", type = boolean.class, defaultValue = "false") + @Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT") }), @PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = { }), @@ -64,8 +59,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences; }), @PreferenceGroup(name = "display", prefix = "settings_display_", suffix = "_key", value = { @Preference(name = "screen_timeout_disabled", type = boolean.class, defaultValue = "false"), - @Preference(name = "hide_status_bar", type = boolean.class, defaultValue = "true"), - @Preference(name = "hide_navigation_bar", type = boolean.class, defaultValue = "false"), + @Preference(name = "full_screen", type = boolean.class, defaultValue = "true"), @Preference(name = "rotate_screen", type = boolean.class, defaultValue = "true"), }), @PreferenceGroup(name = "functionality", prefix = "settings_functionality_", suffix = "_key", value = { diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/ListLayout.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/ListLayout.kt index 5f7b9d6..e20945a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/ListLayout.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/ListLayout.kt @@ -1,7 +1,6 @@ package de.jrpie.android.launcher.preferences import android.content.Context -import android.util.TypedValue import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -28,10 +27,8 @@ enum class ListLayout( GRID( { c -> val displayMetrics = c.resources.displayMetrics - val widthColumnPx = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 90f, displayMetrics) - val numColumns = (displayMetrics.widthPixels / widthColumnPx).toInt() - GridLayoutManager(c, numColumns) + val widthSp = displayMetrics.widthPixels / displayMetrics.scaledDensity + GridLayoutManager(c, (widthSp / 90).toInt()) }, R.layout.list_apps_row_variant_grid, false diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt index 59ecc7a..9460125 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt @@ -5,12 +5,9 @@ import android.util.Log import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.AbstractAppInfo -import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2 -import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown 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 * `migratePreferencesToNewVersion` */ -const val PREFERENCE_VERSION = 4 +const val PREFERENCE_VERSION = 3 const val UNKNOWN_PREFERENCE_VERSION = -1 private const val TAG = "Launcher - Preferences" @@ -47,10 +44,6 @@ fun migratePreferencesToNewVersion(context: Context) { migratePreferencesFromVersion2() Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).") } - 3 -> { - migratePreferencesFromVersion3() - Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).") - } else -> { Log.w( @@ -73,16 +66,16 @@ fun resetPreferences(context: Context) { LauncherPreferences.internal().versionCode(PREFERENCE_VERSION) - val hidden: MutableSet = mutableSetOf() + val hidden: MutableSet = mutableSetOf() val launcher = DetailedAppInfo.fromAppInfo( AppInfo( BuildConfig.APPLICATION_ID, HomeActivity::class.java.name, - INVALID_USER + AppInfo.INVALID_USER ), context ) - launcher?.getRawInfo()?.let { hidden.add(it) } - Log.i(TAG,"Hiding ${launcher?.getRawInfo()}") + launcher?.app?.let { hidden.add(it) } + Log.i(TAG,"Hiding ${launcher?.app}") LauncherPreferences.apps().hidden(hidden) Action.resetToDefaultActions(context) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt index a1cb022..a61980a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt @@ -5,27 +5,14 @@ import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.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.PREFERENCE_VERSION -import kotlinx.serialization.Serializable +import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.json.JSONException import org.json.JSONObject -import androidx.core.content.edit - - -@Serializable -@Suppress("unused") -private class LegacyMapEntry(val key: AppInfo, val value: String) - -private fun serializeMapAppInfo(value: Map?): Set? { - return value?.map { (key, value) -> - Json.encodeToString(LegacyMapEntry(key, value)) - }?.toSet() -} - val oldLauncherActionIds: Map = mapOf( @@ -90,7 +77,7 @@ private fun Action.Companion.legacyFromPreference(id: String): Action? { private fun migrateAppInfoStringMap(key: String) { val preferences = LauncherPreferences.getSharedPreferences() - serializeMapAppInfo( + MapAppInfoStringPreferenceSerializer().serialize( preferences.getStringSet(key, setOf())?.mapNotNull { entry -> try { val obj = JSONObject(entry) @@ -102,7 +89,7 @@ private fun migrateAppInfoStringMap(key: String) { } }?.toMap(HashMap()) )?.let { - preferences.edit { putStringSet(key, it) } + preferences.edit().putStringSet(key, it as Set).apply() } } @@ -111,16 +98,16 @@ private fun migrateAppInfoSet(key: String) { .map(AppInfo.Companion::legacyDeserialize) .map(AppInfo::serialize) .toSet() - .let { LauncherPreferences.getSharedPreferences().edit { putStringSet(key, it) } } + .let { LauncherPreferences.getSharedPreferences().edit().putStringSet(key, it).apply() } } private fun migrateAction(key: String) { Action.legacyFromPreference(key)?.let { action -> - LauncherPreferences.getSharedPreferences().edit { - putString(key, Json.encodeToString(action)) - .remove("$key.app") - .remove("$key.user") - } + LauncherPreferences.getSharedPreferences().edit() + .putString(key, Json.encodeToString(action)) + .remove("$key.app") + .remove("$key.user") + .apply() } } diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt index 4e6eae1..bcac3ae 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt @@ -12,9 +12,9 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION * (see [PREFERENCE_VERSION]) */ fun migratePreferencesFromVersion2() { + assert(PREFERENCE_VERSION == 3) assert(LauncherPreferences.internal().versionCode() == 2) // previously there was no setting for this Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE) LauncherPreferences.internal().versionCode(3) - migratePreferencesFromVersion3() } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt deleted file mode 100644 index 4a9241f..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt +++ /dev/null @@ -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 -import androidx.core.content.edit - -/** - * Migrate preferences from version 3 (used until version 0.0.23) to the current format - * (see [PREFERENCE_VERSION]) - */ - - -fun deserializeSet(value: Set?): Set? { - return value?.map { - Json.decodeFromString(it) - }?.toHashSet() -} - -fun deserializeMap(value: Set?): HashMap? { - return value?.associateTo(HashMap()) { - val entry = Json.decodeFromString(it) - Pair(entry.key, entry.value) - } -} - -@Serializable -private class MapEntry(val key: AppInfo, val value: String) - -private fun migrateSetAppInfo(key: String, preferences: SharedPreferences, editor: Editor) { - try { - val serializer = SetAbstractAppInfoPreferenceSerializer() - val set = HashSet() - - deserializeSet(preferences.getStringSet(key, null))?.let { - set.addAll(it) - } - @Suppress("UNCHECKED_CAST") - editor.putStringSet( - key, - serializer.serialize(set as java.util.Set) as Set? - ) - } 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() - - 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() - editor.putStringSet(key, null) - } -} - -fun migratePreferencesFromVersion3() { - assert(PREFERENCE_VERSION == 4) - assert(LauncherPreferences.internal().versionCode() == 3) - - val preferences = LauncherPreferences.getSharedPreferences() - 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/preferences/legacy/VersionUnknown.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt index 2d1152d..a33670b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt @@ -6,7 +6,7 @@ import android.util.Log import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.theme.Background import de.jrpie.android.launcher.preferences.theme.ColorTheme -import androidx.core.content.edit + private fun migrateStringPreference( @@ -64,317 +64,318 @@ fun migratePreferencesFromVersionUnknown(context: Context) { return } - LauncherPreferences.getSharedPreferences().edit { + val newPrefs = LauncherPreferences.getSharedPreferences().edit() - migrateBooleanPreference( - oldPrefs, - this, - "startedBefore", - "internal.started_before", - false - ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "startedBefore", + "internal.started_before", + false + ) - migrateStringPreference( - oldPrefs, - this, - "action_volumeUpApp", - "action.volume_up.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_volumeUpApp_user", - "action.volume_up.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_volumeDownApp", - "action.volume_down.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_volumeDownApp_user", - "action.volume_down.user", - -1 - ) - migrateStringPreference(oldPrefs, this, "action_timeApp", "action.time.app", "") - migrateIntPreference(oldPrefs, this, "action_timeApp_user", "action.time.user", -1) - migrateStringPreference(oldPrefs, this, "action_dateApp", "action.date.app", "") - migrateIntPreference(oldPrefs, this, "action_dateApp_user", "action.date.user", -1) - migrateStringPreference( - oldPrefs, - this, - "action_longClickApp", - "action.long_click.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_longClickApp_user", - "action.long_click.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_doubleClickApp", - "action.double_click.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_doubleClickApp_user", - "action.double_click.user", - -1 - ) - migrateStringPreference(oldPrefs, this, "action_upApp", "action.up.app", "") - migrateIntPreference(oldPrefs, this, "action_upApp_user", "action.up.user", -1) - migrateStringPreference( - oldPrefs, - this, - "action_up_leftApp", - "action.up_left.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_up_leftApp_user", - "action.up_left.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_up_rightApp", - "action.up_right.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_up_rightApp_user", - "action.up_right.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_doubleUpApp", - "action.double_up.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_doubleUpApp_user", - "action.double_up.user", - -1 - ) - migrateStringPreference(oldPrefs, this, "action_downApp", "action.down.app", "") - migrateIntPreference(oldPrefs, this, "action_downApp_user", "action.down.user", -1) - migrateStringPreference( - oldPrefs, - this, - "action_down_leftApp", - "action.down_left.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_down_leftApp_user", - "action.down_left.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_down_rightApp", - "action.down_right.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_down_rightApp_user", - "action.down_right.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_doubleDownApp", - "action.double_down.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_doubleDownApp_user", - "action.double_down.user", - -1 - ) - migrateStringPreference(oldPrefs, this, "action_leftApp", "action.left.app", "") - migrateIntPreference(oldPrefs, this, "action_leftApp_user", "action.left.user", -1) - migrateStringPreference( - oldPrefs, - this, - "action_left_topApp", - "action.left_top.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_left_topApp_user", - "action.left_top.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_left_bottomApp", - "action.left_bottom.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_left_bottomApp_user", - "action.left_bottom.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_doubleLeftApp", - "action.double_left.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_doubleLeftApp_user", - "action.double_left.user", - -1 - ) - migrateStringPreference(oldPrefs, this, "action_rightApp", "action.right.app", "") - migrateIntPreference( - oldPrefs, - this, - "action_rightApp_user", - "action.right.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_right_topApp", - "action.right_top.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_right_topApp_user", - "action.right_top.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_right_bottomApp", - "action.right_bottom.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_right_bottomApp_user", - "action.right_bottom.user", - -1 - ) - migrateStringPreference( - oldPrefs, - this, - "action_doubleRightApp", - "action.double_right.app", - "" - ) - migrateIntPreference( - oldPrefs, - this, - "action_doubleRightApp_user", - "action.double_right.user", - -1 - ) - migrateBooleanPreference(oldPrefs, this, "timeVisible", "clock.time_visible", true) - migrateBooleanPreference(oldPrefs, this, "dateVisible", "clock.date_visible", true) - migrateBooleanPreference( - oldPrefs, - this, - "dateLocalized", - "clock.date_localized", - false - ) - migrateBooleanPreference( - oldPrefs, - this, - "dateTimeFlip", - "clock.date_time_flip", - false - ) - migrateBooleanPreference( - oldPrefs, - this, - "disableTimeout", - "display.disable_timeout", - false - ) - migrateBooleanPreference( - oldPrefs, - this, - "useFullScreen", - "display.use_full_screen", - true - ) - migrateBooleanPreference( - oldPrefs, - this, - "enableDoubleActions", - "enabled_gestures.double_actions", - true - ) - migrateBooleanPreference( - oldPrefs, - this, - "enableEdgeActions", - "enabled_gestures.edge_actions", - true - ) - migrateBooleanPreference( - oldPrefs, - this, - "searchAutoLaunch", - "functionality.search_auto_launch", - true - ) - migrateBooleanPreference( - oldPrefs, - this, - "searchAutoKeyboard", - "functionality.search_auto_keyboard", - true - ) - } + migrateStringPreference( + oldPrefs, + newPrefs, + "action_volumeUpApp", + "action.volume_up.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_volumeUpApp_user", + "action.volume_up.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_volumeDownApp", + "action.volume_down.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_volumeDownApp_user", + "action.volume_down.user", + -1 + ) + migrateStringPreference(oldPrefs, newPrefs, "action_timeApp", "action.time.app", "") + migrateIntPreference(oldPrefs, newPrefs, "action_timeApp_user", "action.time.user", -1) + migrateStringPreference(oldPrefs, newPrefs, "action_dateApp", "action.date.app", "") + migrateIntPreference(oldPrefs, newPrefs, "action_dateApp_user", "action.date.user", -1) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_longClickApp", + "action.long_click.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_longClickApp_user", + "action.long_click.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_doubleClickApp", + "action.double_click.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_doubleClickApp_user", + "action.double_click.user", + -1 + ) + migrateStringPreference(oldPrefs, newPrefs, "action_upApp", "action.up.app", "") + migrateIntPreference(oldPrefs, newPrefs, "action_upApp_user", "action.up.user", -1) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_up_leftApp", + "action.up_left.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_up_leftApp_user", + "action.up_left.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_up_rightApp", + "action.up_right.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_up_rightApp_user", + "action.up_right.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_doubleUpApp", + "action.double_up.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_doubleUpApp_user", + "action.double_up.user", + -1 + ) + migrateStringPreference(oldPrefs, newPrefs, "action_downApp", "action.down.app", "") + migrateIntPreference(oldPrefs, newPrefs, "action_downApp_user", "action.down.user", -1) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_down_leftApp", + "action.down_left.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_down_leftApp_user", + "action.down_left.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_down_rightApp", + "action.down_right.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_down_rightApp_user", + "action.down_right.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_doubleDownApp", + "action.double_down.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_doubleDownApp_user", + "action.double_down.user", + -1 + ) + migrateStringPreference(oldPrefs, newPrefs, "action_leftApp", "action.left.app", "") + migrateIntPreference(oldPrefs, newPrefs, "action_leftApp_user", "action.left.user", -1) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_left_topApp", + "action.left_top.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_left_topApp_user", + "action.left_top.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_left_bottomApp", + "action.left_bottom.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_left_bottomApp_user", + "action.left_bottom.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_doubleLeftApp", + "action.double_left.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_doubleLeftApp_user", + "action.double_left.user", + -1 + ) + migrateStringPreference(oldPrefs, newPrefs, "action_rightApp", "action.right.app", "") + migrateIntPreference( + oldPrefs, + newPrefs, + "action_rightApp_user", + "action.right.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_right_topApp", + "action.right_top.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_right_topApp_user", + "action.right_top.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_right_bottomApp", + "action.right_bottom.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_right_bottomApp_user", + "action.right_bottom.user", + -1 + ) + migrateStringPreference( + oldPrefs, + newPrefs, + "action_doubleRightApp", + "action.double_right.app", + "" + ) + migrateIntPreference( + oldPrefs, + newPrefs, + "action_doubleRightApp_user", + "action.double_right.user", + -1 + ) + migrateBooleanPreference(oldPrefs, newPrefs, "timeVisible", "clock.time_visible", true) + migrateBooleanPreference(oldPrefs, newPrefs, "dateVisible", "clock.date_visible", true) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "dateLocalized", + "clock.date_localized", + false + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "dateTimeFlip", + "clock.date_time_flip", + false + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "disableTimeout", + "display.disable_timeout", + false + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "useFullScreen", + "display.use_full_screen", + true + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "enableDoubleActions", + "enabled_gestures.double_actions", + true + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "enableEdgeActions", + "enabled_gestures.edge_actions", + true + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "searchAutoLaunch", + "functionality.search_auto_launch", + true + ) + migrateBooleanPreference( + oldPrefs, + newPrefs, + "searchAutoKeyboard", + "functionality.search_auto_keyboard", + true + ) + + newPrefs.apply() when (oldPrefs.getString("theme", "finn")) { "finn" -> { diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt index 3e19daf..041fe4d 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt @@ -2,8 +2,7 @@ package de.jrpie.android.launcher.preferences.serialization -import de.jrpie.android.launcher.apps.AbstractAppInfo -import de.jrpie.android.launcher.apps.PinnedShortcutInfo +import de.jrpie.android.launcher.apps.AppInfo import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import kotlinx.serialization.Serializable @@ -11,61 +10,40 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json - +// Serializers for [LauncherPreference$Config] @Suppress("UNCHECKED_CAST") -class SetAbstractAppInfoPreferenceSerializer : - PreferenceSerializer?, java.util.Set?> { +class SetAppInfoPreferenceSerializer : + PreferenceSerializer?, java.util.Set?> { @Throws(PreferenceSerializationException::class) - override fun serialize(value: java.util.Set?): java.util.Set { - return value?.map(AbstractAppInfo::serialize) - ?.toHashSet() as java.util.Set + override fun serialize(value: java.util.Set?): java.util.Set { + return value?.map(AppInfo::serialize)?.toHashSet() as java.util.Set } @Throws(PreferenceSerializationException::class) - override fun deserialize(value: java.util.Set?): java.util.Set? { - return value?.map(java.lang.String::toString)?.map(AbstractAppInfo::deserialize) - ?.toHashSet() as? java.util.Set + override fun deserialize(value: java.util.Set?): java.util.Set? { + return value?.map (java.lang.String::toString)?.map(AppInfo::deserialize)?.toHashSet() as? java.util.Set } } @Suppress("UNCHECKED_CAST") -class SetPinnedShortcutInfoPreferenceSerializer : - PreferenceSerializer?, java.util.Set?> { - @Throws(PreferenceSerializationException::class) - override fun serialize(value: java.util.Set?): java.util.Set { - return value?.map { Json.encodeToString(it) } - ?.toHashSet() as java.util.Set - } - - @Throws(PreferenceSerializationException::class) - override fun deserialize(value: java.util.Set?): java.util.Set? { - return value?.map(java.lang.String::toString) - ?.map { Json.decodeFromString(it) } - ?.toHashSet() as? java.util.Set - } -} - - -@Suppress("UNCHECKED_CAST") -class MapAbstractAppInfoStringPreferenceSerializer : - PreferenceSerializer?, java.util.Set?> { +class MapAppInfoStringPreferenceSerializer : + PreferenceSerializer?, java.util.Set?> { @Serializable - private class MapEntry(val key: AbstractAppInfo, val value: String) + private class MapEntry(val key: AppInfo, val value: String) @Throws(PreferenceSerializationException::class) - override fun serialize(value: java.util.HashMap?): java.util.Set? { + override fun serialize(value: java.util.HashMap?): java.util.Set? { return value?.map { (key, value) -> Json.encodeToString(MapEntry(key, value)) }?.toHashSet() as? java.util.Set } @Throws(PreferenceSerializationException::class) - override fun deserialize(value: java.util.Set?): java.util.HashMap? { + override fun deserialize(value: java.util.Set?): java.util.HashMap? { return value?.associateTo(HashMap()) { val entry = Json.decodeFromString(it.toString()) Pair(entry.key, entry.value) } } } - diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt index d3088f4..816d94f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt @@ -5,6 +5,7 @@ import android.content.res.Resources import com.google.android.material.color.DynamicColors import de.jrpie.android.launcher.R +@Suppress("unused") enum class ColorTheme( private val id: Int, private val labelResource: Int, diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt index 1ca4d2b..b8fc82e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt @@ -27,14 +27,10 @@ fun View.blink( } // Taken from: https://stackoverflow.com/a/30340794/12787264 -fun ImageView.transformGrayscale(grayscale: Boolean) { - this.colorFilter = if (grayscale) { - ColorMatrixColorFilter(ColorMatrix().apply { - setSaturation(0f) - }) - } else { - null - } +fun ImageView.transformGrayscale() { + this.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { + setSaturation(0f) + }) } 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 2ab5d9f..973e0ca 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 @@ -2,10 +2,10 @@ package de.jrpie.android.launcher.ui import android.annotation.SuppressLint import android.content.SharedPreferences -import android.content.res.Configuration import android.content.res.Resources import android.os.Build import android.os.Bundle +import android.util.DisplayMetrics import android.view.KeyEvent import android.view.MotionEvent import android.view.View @@ -17,7 +17,6 @@ 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 @@ -37,7 +36,7 @@ import java.util.Locale class HomeActivity : UIObject, AppCompatActivity() { private lateinit var binding: HomeBinding - private var touchGestureDetector: TouchGestureDetector? = null + private lateinit var touchGestureDetector: TouchGestureDetector private var sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> @@ -57,11 +56,24 @@ class HomeActivity : UIObject, AppCompatActivity() { super.onCreate() + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + + val width = displayMetrics.widthPixels + val height = displayMetrics.heightPixels + + touchGestureDetector = TouchGestureDetector( + this, + width, + height, + LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f + ) + // Initialise layout binding = HomeBinding.inflate(layoutInflater) - setContentView(binding.root) + // Handle back key / gesture on Android 13+, cf. onKeyDown() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { onBackInvokedDispatcher.registerOnBackInvokedCallback( @@ -73,11 +85,8 @@ class HomeActivity : UIObject, AppCompatActivity() { binding.buttonFallbackSettings.setOnClickListener { LauncherAction.SETTINGS.invoke(this) } - } - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - touchGestureDetector?.updateScreenSize(windowManager) + } override fun onStart() { @@ -85,25 +94,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) } - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - - if (hasFocus && LauncherPreferences.display().hideNavigationBar()) { - hideNavigationBar() - } - } - - private fun updateSettingsFallbackButtonVisibility() { // If µLauncher settings can not be reached from any action bound to an enabled gesture, // show the fallback button. @@ -171,28 +166,8 @@ class HomeActivity : UIObject, AppCompatActivity() { override fun onResume() { super.onResume() - /* This should be initialized in onCreate() - However on some devices there seems to be a bug where the touchGestureDetector - is not working properly after resuming the app. - Reinitializing the touchGestureDetector every time the app is resumed might help to fix that. - (see issue #138) - */ - touchGestureDetector = TouchGestureDetector( - this, 0, 0, + touchGestureDetector.edgeWidth = LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f - ).also { - it.updateScreenSize(windowManager) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - binding.root.setOnApplyWindowInsetsListener { _, windowInsets -> - @Suppress("deprecation") // required to support API 29 - val insets = windowInsets.systemGestureInsets - touchGestureDetector?.setSystemGestureInsets(insets) - - windowInsets - } - } initClock() updateSettingsFallbackButtonVisibility() @@ -211,7 +186,6 @@ class HomeActivity : UIObject, AppCompatActivity() { // Only used pre Android 13, cf. onBackInvokedDispatcher handleBack() } - KeyEvent.KEYCODE_VOLUME_UP -> { if (Action.forGesture(Gesture.VOLUME_UP) == LauncherAction.VOLUME_UP) { // Let the OS handle the key event. This works better with some custom ROMs @@ -233,8 +207,7 @@ class HomeActivity : UIObject, AppCompatActivity() { } override fun onTouchEvent(event: MotionEvent): Boolean { - touchGestureDetector?.onTouchEvent(event) - return true + return touchGestureDetector.onTouchEvent(event) || super.onTouchEvent(event) } override fun setOnClicks() { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt deleted file mode 100644 index 71908ba..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt +++ /dev/null @@ -1,154 +0,0 @@ -package de.jrpie.android.launcher.ui - -import android.app.AlertDialog -import android.app.Service -import android.content.Context -import android.content.pm.LauncherApps -import android.content.pm.LauncherApps.PinItemRequest -import android.content.res.Resources -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.actions.ShortcutAction -import de.jrpie.android.launcher.apps.PinnedShortcutInfo -import de.jrpie.android.launcher.databinding.ActivityPinShortcutBinding -import de.jrpie.android.launcher.preferences.LauncherPreferences -import androidx.core.content.edit - -class PinShortcutActivity : AppCompatActivity(), UIObject { - private lateinit var binding: ActivityPinShortcutBinding - - private var isBound = false - private var request: PinItemRequest? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - super.onCreate() - enableEdgeToEdge() - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - finish() - return - } - - binding = ActivityPinShortcutBinding.inflate(layoutInflater) - setContentView(binding.root) - - val launcherApps = getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps - - val request = launcherApps.getPinItemRequest(intent) - this.request = request - if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) { - finish() - return - } - - binding.pinShortcutLabel.text = request.shortcutInfo!!.shortLabel ?: "?" - binding.pinShortcutLabel.setCompoundDrawables( - launcherApps.getShortcutBadgedIconDrawable(request.shortcutInfo, 0).also { - val size = (40 * resources.displayMetrics.density).toInt() - it.setBounds(0,0, size, size) - }, null, null, null) - - binding.pinShortcutButtonBind.setOnClickListener { - AlertDialog.Builder(this, R.style.AlertDialogCustom) - .setTitle(getString(R.string.pin_shortcut_button_bind)) - .setView(R.layout.dialog_select_gesture) - .setNegativeButton(android.R.string.cancel, null) - .create().also { it.show() }.let { dialog -> - val viewManager = LinearLayoutManager(dialog.context) - val viewAdapter = GestureRecyclerAdapter (dialog.context) { gesture -> - if (!isBound) { - isBound = true - request.accept() - } - LauncherPreferences.getSharedPreferences().edit { - ShortcutAction(PinnedShortcutInfo(request.shortcutInfo!!)).bindToGesture( - this, - gesture.id - ) - } - dialog.dismiss() - } - dialog.findViewById(R.id.dialog_select_gesture_recycler).apply { - setHasFixedSize(true) - layoutManager = viewManager - adapter = viewAdapter - } - } - } - - binding.pinShortcutClose.setOnClickListener { finish() } - binding.pinShortcutButtonOk.setOnClickListener { finish() } - } - - override fun onStart() { - super.onStart() - super.onStart() - } - - override fun onDestroy() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - super.onDestroy() - return - } - if(binding.pinShortcutSwitchVisible.isChecked) { - if(!isBound) { - request?.accept() - } - request?.shortcutInfo?.let { - val set = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf() - set.add(PinnedShortcutInfo(it)) - LauncherPreferences.apps().pinnedShortcuts(set) - } - } - super.onDestroy() - } - - override fun getTheme(): Resources.Theme { - return modifyTheme(super.getTheme()) - } - - inner class GestureRecyclerAdapter(val context: Context, val onClick: (Gesture) -> Unit): RecyclerView.Adapter() { - private val gestures = Gesture.entries.filter { it.isEnabled() }.toList() - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val label: TextView = itemView.findViewById(R.id.dialog_select_gesture_row_name) - val description: TextView = itemView.findViewById(R.id.dialog_select_gesture_row_description) - val icon: ImageView = itemView.findViewById(R.id.dialog_select_gesture_row_icon) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view: View = inflater.inflate(R.layout.dialog_select_gesture_row, parent, false) - return ViewHolder(view) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val gesture = gestures[position] - holder.label.text = gesture.getLabel(context) - holder.description.text = gesture.getDescription(context) - holder.icon.setImageDrawable( - Action.forGesture(gesture)?.getIcon(context) - ) - holder.itemView.setOnClickListener { - onClick(gesture) - } - } - - override fun getItemCount(): Int { - return gestures.size - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt index 8e8ed4e..0ddbfd1 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,15 +1,8 @@ package de.jrpie.android.launcher.ui import android.content.Context -import android.graphics.Insets -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.util.DisplayMetrics import android.view.MotionEvent import android.view.ViewConfiguration -import android.view.WindowManager -import androidx.annotation.RequiresApi import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.preferences.LauncherPreferences import kotlin.math.abs @@ -19,8 +12,8 @@ import kotlin.math.tan class TouchGestureDetector( private val context: Context, - var width: Int, - var height: Int, + val width: Int, + val height: Int, var edgeWidth: Float ) { private val ANGULAR_THRESHOLD = tan(Math.PI / 6) @@ -34,31 +27,20 @@ class TouchGestureDetector( private val MIN_TRIANGLE_HEIGHT = 250 - private val longPressHandler = Handler(Looper.getMainLooper()) - - private var systemGestureInsetTop = 100 - private var systemGestureInsetBottom = 0 - private var systemGestureInsetLeft = 0 - private var systemGestureInsetRight = 0 - data class Vector(val x: Float, val y: Float) { fun absSquared(): Float { return this.x * this.x + this.y * this.y } - fun plus(vector: Vector): Vector { return Vector(this.x + vector.x, this.y + vector.y) } - fun max(other: Vector): Vector { return Vector(max(this.x, other.x), max(this.y, other.y)) } - fun min(other: Vector): Vector { return Vector(min(this.x, other.x), min(this.y, other.y)) } - operator fun minus(vector: Vector): Vector { return Vector(this.x - vector.x, this.y - vector.y) } @@ -75,35 +57,16 @@ class TouchGestureDetector( fun sizeSquared(): Float { return (max - min).absSquared() } - fun getDirection(): Vector { return last - start } - fun update(vector: Vector) { min = min.min(vector) max = max.max(vector) last = vector } } - - private fun PointerPath.startIntersectsSystemGestureInsets(): Boolean { - // ignore x, since this makes edge swipes very hard to execute - return start.y < systemGestureInsetTop - || start.y > height - systemGestureInsetBottom - } - - private fun PointerPath.intersectsSystemGestureInsets(): Boolean { - return min.x < systemGestureInsetLeft - || min.y < systemGestureInsetTop - || max.x > width - systemGestureInsetRight - || max.y > height - systemGestureInsetBottom - } - private fun PointerPath.isTap(): Boolean { - if (intersectsSystemGestureInsets()) { - return false - } return sizeSquared() < TOUCH_SLOP_SQUARE } @@ -121,48 +84,20 @@ class TouchGestureDetector( private var paths = HashMap() - /* Set when - * - the longPressHandler has detected this gesture as a long press - * - the gesture was cancelled by MotionEvent.ACTION_CANCEL - * In any case, the current gesture should be ignored by further detection logic. - */ - private var cancelled = false - private var lastTappedTime = 0L private var lastTappedLocation: Vector? = null - fun onTouchEvent(event: MotionEvent) { - - if (event.actionMasked == MotionEvent.ACTION_CANCEL) { - synchronized(this@TouchGestureDetector) { - cancelled = true - } - } - + fun onTouchEvent(event: MotionEvent): Boolean { 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 @@ -266,38 +197,34 @@ class TouchGestureDetector( val startEndMax = mainPointerPath.start.max(mainPointerPath.last) when (gesture) { Gesture.SWIPE_DOWN -> { - if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { + if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { gesture = Gesture.SWIPE_LARGER } else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) { gesture = Gesture.SWIPE_SMALLER } } - Gesture.SWIPE_UP -> { - if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { + if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { gesture = Gesture.SWIPE_LARGER_REVERSE } else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) { gesture = Gesture.SWIPE_SMALLER_REVERSE } } - Gesture.SWIPE_RIGHT -> { - if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { + if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { gesture = Gesture.SWIPE_V } else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) { gesture = Gesture.SWIPE_LAMBDA } } - Gesture.SWIPE_LEFT -> { - if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { + if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { gesture = Gesture.SWIPE_V_REVERSE } else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) { gesture = Gesture.SWIPE_LAMBDA_REVERSE } } - - else -> {} + else -> { } } if (edgeActions) { @@ -313,27 +240,7 @@ class TouchGestureDetector( gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM) } } - - if (timeStart - lastTappedTime < 2 * DOUBLE_TAP_TIMEOUT) { - gesture = gesture?.getTapComboVariant() - } gesture?.invoke(context) } } - - fun updateScreenSize(windowManager: WindowManager) { - val displayMetrics = DisplayMetrics() - @Suppress("deprecation") // required to support API < 30 - windowManager.defaultDisplay.getMetrics(displayMetrics) - width = displayMetrics.widthPixels - height = displayMetrics.heightPixels - } - - @RequiresApi(Build.VERSION_CODES.Q) - fun setSystemGestureInsets(insets: Insets) { - systemGestureInsetTop = insets.top - systemGestureInsetBottom = insets.bottom - systemGestureInsetLeft = insets.left - systemGestureInsetRight = insets.right - } } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt b/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt index 51324f4..3702bb2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt @@ -3,11 +3,7 @@ package de.jrpie.android.launcher.ui import android.app.Activity import android.content.pm.ActivityInfo import android.content.res.Resources -import android.os.Build -import android.view.View import android.view.Window -import android.view.WindowInsets -import android.view.WindowInsetsController import android.view.WindowManager import de.jrpie.android.launcher.preferences.LauncherPreferences @@ -15,12 +11,10 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences * An interface implemented by every [Activity], Fragment etc. in Launcher. * It handles themes and window flags - a useful abstraction as it is the same everywhere. */ -@Suppress("deprecation") // FLAG_FULLSCREEN is required to support API level < 30 fun setWindowFlags(window: Window, homeScreen: Boolean) { window.setFlags(0, 0) // clear flags - // Display notification bar - if (LauncherPreferences.display().hideStatusBar()) + if (LauncherPreferences.display().fullScreen()) window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN @@ -42,19 +36,17 @@ fun setWindowFlags(window: Window, homeScreen: Boolean) { } - interface UIObject { fun onCreate() { - if (this !is Activity) { - return - } - setWindowFlags(window, isHomeScreen()) + if (this is Activity) { + setWindowFlags(window, isHomeScreen()) + + if (!LauncherPreferences.display().rotateScreen()) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR + } - if (!LauncherPreferences.display().rotateScreen()) { - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR } } - fun onStart() { setOnClicks() adjustLayout() @@ -78,26 +70,4 @@ interface UIObject { fun isHomeScreen(): Boolean { return false } - - - @Suppress("DEPRECATION") - fun hideNavigationBar() { - if (this !is Activity) { - return - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.insetsController?.apply { - hide(WindowInsets.Type.navigationBars()) - systemBarsBehavior = - WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } else { - // Try to hide the navigation bar but do not hide the status bar - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - or View.SYSTEM_UI_FLAG_IMMERSIVE - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE) - } - } } \ No newline at end of file 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 fe27f0f..c4ecded 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 @@ -1,20 +1,27 @@ package de.jrpie.android.launcher.ui.list +import android.app.Activity +import android.content.Context +import android.content.Intent import android.content.res.Resources import android.graphics.Rect import android.os.Build import android.os.Bundle import android.view.View +import android.widget.Toast import android.window.OnBackInvokedDispatcher import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager.widget.ViewPager +import com.google.android.material.tabs.TabLayout import de.jrpie.android.launcher.Application 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 @@ -25,6 +32,14 @@ import de.jrpie.android.launcher.ui.list.apps.ListFragmentApps 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 privateSpaceVisibility: AppFilter.Companion.AppSetVisibility = + AppFilter.Companion.AppSetVisibility.VISIBLE +var hiddenVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.HIDDEN +var forGesture: String? = null + /** * The [ListActivity] is the most general purpose activity in Launcher: * - used to view all apps and edit their settings @@ -34,34 +49,9 @@ import de.jrpie.android.launcher.ui.list.other.ListFragmentOther */ class ListActivity : AppCompatActivity(), UIObject { private lateinit var binding: ListBinding - var intention = ListActivityIntention.VIEW - 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 forGesture: String? = null 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, @@ -84,6 +74,7 @@ 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 */ @@ -108,13 +99,10 @@ class ListActivity : AppCompatActivity(), UIObject { ?.let { ListActivityIntention.valueOf(it) } ?: ListActivityIntention.VIEW - @Suppress("deprecation") // required to support API level < 33 favoritesVisibility = bundle.getSerializable("favoritesVisibility") as? AppFilter.Companion.AppSetVisibility ?: favoritesVisibility - @Suppress("deprecation") // required to support API level < 33 privateSpaceVisibility = bundle.getSerializable("privateSpaceVisibility") as? AppFilter.Companion.AppSetVisibility ?: privateSpaceVisibility - @Suppress("deprecation") // required to support API level < 33 hiddenVisibility = bundle.getSerializable("hiddenVisibility") as? AppFilter.Companion.AppSetVisibility ?: hiddenVisibility @@ -131,6 +119,20 @@ 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) @@ -153,7 +155,7 @@ class ListActivity : AppCompatActivity(), UIObject { binding.listContainer.context.resources.displayMetrics.heightPixels val diff = height - r.bottom if (diff != 0 && - LauncherPreferences.display().hideStatusBar() + LauncherPreferences.display().fullScreen() ) { if (binding.listContainer.paddingBottom != diff) { binding.listContainer.setPadding(0, 0, 0, diff) @@ -181,19 +183,32 @@ class ListActivity : AppCompatActivity(), UIObject { finish() } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_UNINSTALL) { + if (resultCode == Activity.RESULT_OK) { + Toast.makeText(this, getString(R.string.list_removed), Toast.LENGTH_LONG).show() + finish() + } else if (resultCode == Activity.RESULT_FIRST_USER) { + Toast.makeText(this, getString(R.string.list_not_removed), Toast.LENGTH_LONG).show() + finish() + } + } + } + + 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) @@ -223,11 +238,11 @@ class ListActivity : AppCompatActivity(), UIObject { updateTitle() - val sectionsPagerAdapter = ListSectionsPagerAdapter(this) - binding.listViewpager.let { - it.adapter = sectionsPagerAdapter - binding.listTabs.setupWithViewPager(it) - } + val sectionsPagerAdapter = ListSectionsPagerAdapter(this, supportFragmentManager) + val viewPager: ViewPager = findViewById(R.id.list_viewpager) + viewPager.adapter = sectionsPagerAdapter + val tabs: TabLayout = findViewById(R.id.list_tabs) + tabs.setupWithViewPager(viewPager) } } @@ -239,15 +254,9 @@ private val TAB_TITLES = arrayOf( /** * The [ListSectionsPagerAdapter] returns the fragment, * which corresponds to the selected tab in [ListActivity]. - * - * This should eventually be replaced by a [FragmentStateAdapter] - * However this keyboard does not open when using [ViewPager2] - * so currently [ViewPager] is used here. - * https://github.com/jrpie/launcher/issues/130 */ -@Suppress("deprecation") -class ListSectionsPagerAdapter(private val activity: ListActivity) : - FragmentPagerAdapter(activity.supportFragmentManager) { +class ListSectionsPagerAdapter(private val context: Context, fm: FragmentManager) : + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { override fun getItem(position: Int): Fragment { return when (position) { @@ -258,11 +267,11 @@ class ListSectionsPagerAdapter(private val activity: ListActivity) : } override fun getPageTitle(position: Int): CharSequence { - return activity.resources.getString(TAB_TITLES[position]) + return context.resources.getString(TAB_TITLES[position]) } override fun getCount(): Int { - return when (activity.intention) { + return when (intention) { ListActivity.ListActivityIntention.VIEW -> 1 else -> 2 } 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 65278ce..2d8e1eb 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 @@ -2,6 +2,7 @@ package de.jrpie.android.launcher.ui.list.apps import android.annotation.SuppressLint import android.app.Activity +import android.content.Intent import android.graphics.Rect import android.view.LayoutInflater import android.view.View @@ -14,12 +15,12 @@ import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.RecyclerView import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo +import de.jrpie.android.launcher.REQUEST_CHOOSE_APP +import de.jrpie.android.launcher.actions.AppAction import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo +import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.ListLayout import de.jrpie.android.launcher.ui.list.ListActivity @@ -46,8 +47,7 @@ class AppsRecyclerAdapter( RecyclerView.Adapter() { private val apps = (activity.applicationContext as Application).apps - private val appsListDisplayed: MutableList = mutableListOf() - private val grayscale = LauncherPreferences.theme().monochromeIcons() + private val appsListDisplayed: MutableList = mutableListOf() // temporarily disable auto launch var disableAutoLaunch: Boolean = false @@ -68,7 +68,7 @@ class AppsRecyclerAdapter( override fun onClick(v: View) { val rect = Rect() img.getGlobalVisibleRect(rect) - selectItem(bindingAdapterPosition, rect) + selectItem(adapterPosition, rect) } init { @@ -80,19 +80,20 @@ class AppsRecyclerAdapter( override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { var appLabel = appsListDisplayed[i].getCustomLabel(activity) - val appIcon = appsListDisplayed[i].getIcon(activity) - - viewHolder.img.transformGrayscale(grayscale) - viewHolder.img.setImageDrawable(appIcon.constantState?.newDrawable() ?: appIcon) - if (layout.useBadgedText) { appLabel = activity.packageManager.getUserBadgedLabel( appLabel, - appsListDisplayed[i].getUser(activity) + getUserFromId(appsListDisplayed[i].app.user, activity) ).toString() } - viewHolder.textView.text = appLabel + val appIcon = appsListDisplayed[i].icon + + viewHolder.textView.text = appLabel + viewHolder.img.setImageDrawable(appIcon) + + if (LauncherPreferences.theme().monochromeIcons()) + viewHolder.img.transformGrayscale() // decide when to show the options popup menu about if (intention == ListActivity.ListActivityIntention.VIEW) { @@ -117,26 +118,22 @@ class AppsRecyclerAdapter( @Suppress("SameReturnValue") private fun showOptionsPopup( viewHolder: ViewHolder, - appInfo: AbstractDetailedAppInfo + appInfo: DetailedAppInfo ): Boolean { //create the popup menu val popup = PopupMenu(activity, viewHolder.img) popup.inflate(R.menu.menu_app) - if (!appInfo.isRemovable()) { + if (appInfo.isSystemApp) { popup.menu.findItem(R.id.app_menu_delete).setVisible(false) } - if (appInfo !is DetailedAppInfo) { - popup.menu.findItem(R.id.app_menu_info).setVisible(false) - } - - if (LauncherPreferences.apps().hidden()?.contains(appInfo.getRawInfo()) == true) { + if (LauncherPreferences.apps().hidden()?.contains(appInfo.app) == true) { 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) } @@ -144,19 +141,19 @@ class AppsRecyclerAdapter( popup.setOnMenuItemClickListener { when (it.itemId) { R.id.app_menu_delete -> { - appInfo.getRawInfo().uninstall(activity); true + appInfo.app.uninstall(activity); true } R.id.app_menu_info -> { - (appInfo.getRawInfo() as? AppInfo)?.openSettings(activity); true + appInfo.app.openSettings(activity); true } R.id.app_menu_favorite -> { - appInfo.getRawInfo().toggleFavorite(); true + appInfo.app.toggleFavorite(); true } R.id.app_menu_hidden -> { - appInfo.getRawInfo().toggleHidden(root); true + appInfo.app.toggleHidden(root); true } R.id.app_menu_rename -> { @@ -191,14 +188,15 @@ class AppsRecyclerAdapter( val appInfo = appsListDisplayed[pos] when (intention) { ListActivity.ListActivityIntention.VIEW -> { - appInfo.getAction().invoke(activity, rect) + AppAction(appInfo.app).invoke(activity, rect) } ListActivity.ListActivityIntention.PICK -> { + val returnIntent = Intent() + AppAction(appInfo.app).writeToIntent(returnIntent) + returnIntent.putExtra("forGesture", forGesture) + activity.setResult(REQUEST_CHOOSE_APP, returnIntent) activity.finish() - forGesture ?: return - val gesture = Gesture.byId(forGesture) ?: return - Action.setActionForGesture(gesture, appInfo.getAction()) } } } @@ -213,8 +211,8 @@ class AppsRecyclerAdapter( && !disableAutoLaunch && LauncherPreferences.functionality().searchAutoLaunch() ) { - val app = appsListDisplayed[0] - app.getAction().invoke(activity) + val info = appsListDisplayed[0] + AppAction(info.app).invoke(activity) val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt index 22dff02..9636dc2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ContextMenuActions.kt @@ -1,11 +1,11 @@ package de.jrpie.android.launcher.ui.list.apps -import android.app.Activity import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.LauncherApps import android.graphics.Rect +import android.net.Uri import android.os.Bundle import android.util.Log import android.view.View @@ -13,13 +13,11 @@ import android.widget.EditText import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.REQUEST_UNINSTALL import de.jrpie.android.launcher.apps.AppInfo -import de.jrpie.android.launcher.apps.AbstractAppInfo -import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo -import de.jrpie.android.launcher.apps.PinnedShortcutInfo +import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.preferences.LauncherPreferences -import androidx.core.net.toUri private const val LOG_TAG = "AppContextMenu" @@ -34,29 +32,27 @@ fun AppInfo.openSettings( } } -fun AbstractAppInfo.uninstall(activity: Activity) { - if (this is AppInfo) { - val packageName = this.packageName - val userId = this.user +fun AppInfo.uninstall(activity: android.app.Activity) { + val packageName = this.packageName + val userId = this.user - Log.i(LOG_TAG, "uninstalling $this") + Log.i(LOG_TAG, "uninstalling $this") - val intent = Intent(Intent.ACTION_DELETE) - intent.data = "package:$packageName".toUri() - getUserFromId(userId, activity).let { user -> - intent.putExtra(Intent.EXTRA_USER, user) - } - activity.startActivity(intent) - - } else if(this is PinnedShortcutInfo) { - val pinned = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf() - pinned.remove(this) - LauncherPreferences.apps().pinnedShortcuts(pinned) + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) + intent.data = Uri.parse("package:$packageName") + getUserFromId(userId, activity).let { user -> + intent.putExtra(Intent.EXTRA_USER, user) } + + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) + activity.startActivityForResult( + intent, + REQUEST_UNINSTALL + ) } -fun AbstractAppInfo.toggleFavorite() { - val favorites: MutableSet = +fun AppInfo.toggleFavorite() { + val favorites: MutableSet = LauncherPreferences.apps().favorites() ?: mutableSetOf() if (favorites.contains(this)) { @@ -73,8 +69,8 @@ fun AbstractAppInfo.toggleFavorite() { /** * @param view: used to show a snackbar letting the user undo the action */ -fun AbstractAppInfo.toggleHidden(view: View) { - val hidden: MutableSet = +fun AppInfo.toggleHidden(view: View) { + val hidden: MutableSet = LauncherPreferences.apps().hidden() ?: mutableSetOf() if (hidden.contains(this)) { hidden.remove(this) @@ -91,12 +87,12 @@ fun AbstractAppInfo.toggleHidden(view: View) { LauncherPreferences.apps().hidden(hidden) } -fun AbstractDetailedAppInfo.showRenameDialog(context: Context) { +fun DetailedAppInfo.showRenameDialog(context: Context) { 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) - setNegativeButton(android.R.string.cancel) { d, _ -> d.cancel() } - setPositiveButton(android.R.string.ok) { d, _ -> + setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() } + setPositiveButton(R.string.dialog_rename_ok) { d, _ -> setCustomLabel( (d as? AlertDialog) ?.findViewById(R.id.dialog_rename_app_edit_text) @@ -106,7 +102,7 @@ fun AbstractDetailedAppInfo.showRenameDialog(context: Context) { }.create().also { it.show() }.apply { val input = findViewById(R.id.dialog_rename_app_edit_text) input?.setText(getCustomLabel(context)) - input?.hint = getLabel() + input?.hint = label } } 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 1a55bbb..3a6e403 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 @@ -9,14 +9,17 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast 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.apps.AppFilter import de.jrpie.android.launcher.databinding.ListAppsBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.UIObject import de.jrpie.android.launcher.ui.list.ListActivity +import de.jrpie.android.launcher.ui.list.favoritesVisibility +import de.jrpie.android.launcher.ui.list.forGesture +import de.jrpie.android.launcher.ui.list.hiddenVisibility +import de.jrpie.android.launcher.ui.list.intention +import de.jrpie.android.launcher.ui.list.privateSpaceVisibility import de.jrpie.android.launcher.ui.openSoftKeyboard @@ -49,7 +52,7 @@ class ListFragmentApps : Fragment(), UIObject { .registerOnSharedPreferenceChangeListener(sharedPreferencesListener) binding.listAppsCheckBoxFavorites.isChecked = - ((activity as? ListActivity)?.favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) + (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) } override fun onStop() { @@ -62,33 +65,25 @@ class ListFragmentApps : Fragment(), UIObject { override fun setOnClicks() {} override fun adjustLayout() { - val listActivity = activity as? ListActivity ?: return appsRecyclerAdapter = AppsRecyclerAdapter( - listActivity, binding.root, listActivity.intention, listActivity.forGesture, + requireActivity(), binding.root, intention, forGesture, appFilter = AppFilter( requireContext(), "", - favoritesVisibility = listActivity.favoritesVisibility, - privateSpaceVisibility = listActivity.privateSpaceVisibility, - hiddenVisibility = listActivity.hiddenVisibility + favoritesVisibility = favoritesVisibility, + privateSpaceVisibility = privateSpaceVisibility, + hiddenVisibility = hiddenVisibility ), layout = LauncherPreferences.list().layout() ) - // set up the list / recycler binding.listAppsRview.apply { // improve performance (since content changes don't change the layout size) setHasFixedSize(true) layoutManager = LauncherPreferences.list().layout().layoutManager(context) - .also { - if (LauncherPreferences.list().reverseLayout()) { - (it as? LinearLayoutManager)?.reverseLayout = true - (it as? GridLayoutManager)?.reverseLayout = true - } - } adapter = appsRecyclerAdapter } @@ -120,8 +115,7 @@ class ListFragmentApps : Fragment(), UIObject { if (newText == " " && !appsRecyclerAdapter.disableAutoLaunch && - (activity as? ListActivity)?.intention - == ListActivity.ListActivityIntention.VIEW && + intention == ListActivity.ListActivityIntention.VIEW && LauncherPreferences.functionality().searchAutoLaunch() ) { appsRecyclerAdapter.disableAutoLaunch = true @@ -138,17 +132,17 @@ class ListFragmentApps : Fragment(), UIObject { }) binding.listAppsCheckBoxFavorites.setOnClickListener { - listActivity.favoritesVisibility = + favoritesVisibility = if (binding.listAppsCheckBoxFavorites.isChecked) { AppFilter.Companion.AppSetVisibility.EXCLUSIVE } else { AppFilter.Companion.AppSetVisibility.VISIBLE } - appsRecyclerAdapter.setFavoritesVisibility(listActivity.favoritesVisibility) + appsRecyclerAdapter.setFavoritesVisibility(favoritesVisibility) (activity as? ListActivity)?.updateTitle() } - if (listActivity.intention == ListActivity.ListActivityIntention.VIEW + if (intention == ListActivity.ListActivityIntention.VIEW && LauncherPreferences.functionality().searchAutoOpenKeyboard() ) { binding.listAppsSearchview.openSoftKeyboard(requireContext()) 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 f176469..97d1c84 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 @@ -1,6 +1,7 @@ package de.jrpie.android.launcher.ui.list.other import android.app.Activity +import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -8,10 +9,9 @@ import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.actions.Gesture +import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.actions.LauncherAction -import de.jrpie.android.launcher.ui.list.ListActivity +import de.jrpie.android.launcher.ui.list.forGesture /** * The [OtherRecyclerAdapter] will only be displayed in the ListActivity, @@ -33,13 +33,10 @@ class OtherRecyclerAdapter(val activity: Activity) : override fun onClick(v: View) { - val pos = bindingAdapterPosition + val pos = adapterPosition val content = othersList[pos] - activity.finish() - val gestureId = (activity as? ListActivity)?.forGesture ?: return - val gesture = Gesture.byId(gestureId) ?: return - Action.setActionForGesture(gesture, content) + forGesture?.let { returnChoiceIntent(it, content) } } init { @@ -64,4 +61,12 @@ class OtherRecyclerAdapter(val activity: Activity) : val view: View = inflater.inflate(R.layout.list_other_row, parent, false) return ViewHolder(view) } + + private fun returnChoiceIntent(forGesture: String, action: LauncherAction) { + val returnIntent = Intent() + returnIntent.putExtra("forGesture", forGesture) + action.writeToIntent(returnIntent) + activity.setResult(REQUEST_CHOOSE_APP, returnIntent) + activity.finish() + } } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/SettingsActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/SettingsActivity.kt index cd59726..fde61a7 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/SettingsActivity.kt @@ -1,5 +1,6 @@ package de.jrpie.android.launcher.ui.settings +import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.res.Resources @@ -7,14 +8,17 @@ import android.os.Bundle import android.provider.Settings import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.google.android.material.tabs.TabLayoutMediator +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager.widget.ViewPager +import com.google.android.material.tabs.TabLayout import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.databinding.SettingsBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.theme.Background import de.jrpie.android.launcher.preferences.theme.ColorTheme +import de.jrpie.android.launcher.saveListActivityChoice import de.jrpie.android.launcher.ui.UIObject import de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActions import de.jrpie.android.launcher.ui.settings.launcher.SettingsFragmentLauncher @@ -45,15 +49,15 @@ class SettingsActivity : AppCompatActivity(), UIObject { // This ugly workaround causes a jump to the top of the list, but at least // the text stays readable. val i = Intent(this, SettingsActivity::class.java) - .also { it.putExtra(EXTRA_TAB, 1) } + .also { it.putExtra("tab", 1) } finish() startActivity(i) } else - if (prefKey?.startsWith("theme.") == true || - prefKey?.startsWith("display.") == true - ) { - recreate() - } + if (prefKey?.startsWith("theme.") == true || + prefKey?.startsWith("display.") == true + ) { + recreate() + } } private lateinit var binding: SettingsBinding @@ -67,14 +71,15 @@ class SettingsActivity : AppCompatActivity(), UIObject { setContentView(binding.root) // set up tabs and swiping in settings - val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this) - binding.settingsViewpager.apply { - adapter = sectionsPagerAdapter - setCurrentItem(intent.getIntExtra(EXTRA_TAB, 0), false) + val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this, supportFragmentManager) + val viewPager: ViewPager = findViewById(R.id.settings_viewpager) + viewPager.adapter = sectionsPagerAdapter + + val tabs: TabLayout = findViewById(R.id.settings_tabs) + tabs.setupWithViewPager(viewPager) + if (intent.hasExtra("tab")) { + tabs.getTabAt(intent.getIntExtra("tab", 0))?.select() } - TabLayoutMediator(binding.settingsTabs, binding.settingsViewpager) { tab, position -> - tab.text = sectionsPagerAdapter.getPageTitle(position) - }.attach() } override fun onStart() { @@ -103,21 +108,24 @@ class SettingsActivity : AppCompatActivity(), UIObject { } } - companion object { - private const val EXTRA_TAB = "tab" + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_CHOOSE_APP -> saveListActivityChoice(data) + else -> super.onActivityResult(requestCode, resultCode, data) + } } } private val TAB_TITLES = arrayOf( - R.string.settings_tab_actions, + R.string.settings_tab_app, R.string.settings_tab_launcher, R.string.settings_tab_meta ) -class SettingsSectionsPagerAdapter(private val activity: FragmentActivity) : - FragmentStateAdapter(activity) { +class SettingsSectionsPagerAdapter(private val context: Context, fm: FragmentManager) : + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun createFragment(position: Int): Fragment { + override fun getItem(position: Int): Fragment { return when (position) { 0 -> SettingsFragmentActions() 1 -> SettingsFragmentLauncher() @@ -126,11 +134,11 @@ class SettingsSectionsPagerAdapter(private val activity: FragmentActivity) : } } - fun getPageTitle(position: Int): CharSequence { - return activity.resources.getString(TAB_TITLES[position]) + override fun getPageTitle(position: Int): CharSequence { + return context.resources.getString(TAB_TITLES[position]) } - override fun getItemCount(): Int { + override fun getCount(): Int { return 3 } } 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 ae47ce2..d7862fa 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,11 +11,11 @@ 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 import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.apps.AppFilter @@ -94,8 +94,6 @@ 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 @@ -117,18 +115,15 @@ 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) { + if (action == null || drawable == 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 @@ -142,7 +137,9 @@ class ActionsRecyclerAdapter(val activity: Activity) : val description = gesture.getDescription(activity) viewHolder.descriptionTextView.text = description - viewHolder.img.transformGrayscale(LauncherPreferences.theme().monochromeIcons()) + + if (LauncherPreferences.theme().monochromeIcons()) + viewHolder.img.transformGrayscale() updateViewHolder(gesture, viewHolder) viewHolder.img.setOnClickListener { chooseApp(gesture) } @@ -178,6 +175,9 @@ class ActionsRecyclerAdapter(val activity: Activity) : intent.putExtra("intention", ListActivity.ListActivityIntention.PICK.toString()) intent.putExtra("hiddenVisibility", AppFilter.Companion.AppSetVisibility.VISIBLE) intent.putExtra("forGesture", gesture.id) // for which action we choose the app - activity.startActivity(intent) + activity.startActivityForResult( + intent, + REQUEST_CHOOSE_APP + ) } } 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 dea0bcf..70a225d 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 @@ -2,6 +2,7 @@ package de.jrpie.android.launcher.ui.settings.meta import android.app.AlertDialog import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,10 +16,10 @@ 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 +import de.jrpie.android.launcher.ui.tutorial.TutorialActivity /** * The [SettingsFragmentMeta] is a used as a tab in the SettingsActivity. @@ -46,17 +47,8 @@ class SettingsFragmentMeta : Fragment(), UIObject { override fun setOnClicks() { - fun bindURL(view: View, urlRes: Int) { - view.setOnClickListener { - openInBrowser( - getString(urlRes), - requireContext() - ) - } - } - binding.settingsMetaButtonViewTutorial.setOnClickListener { - openTutorial(requireContext()) + startActivity(Intent(this.context, TutorialActivity::class.java)) } // prompting for settings-reset confirmation @@ -77,7 +69,12 @@ class SettingsFragmentMeta : Fragment(), UIObject { // view code - bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github) + binding.settingsMetaButtonViewCode.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_link_github), + requireContext() + ) + } // report a bug binding.settingsMetaButtonReportBug.setOnClickListener { @@ -113,19 +110,37 @@ class SettingsFragmentMeta : Fragment(), UIObject { } // join chat - bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url) + binding.settingsMetaButtonJoinChat.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_chat_url), + requireContext() + ) + } + // contact developer - // bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url) + binding.settingsMetaButtonContact.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_contact_url), + requireContext() + ) + } // contact fork developer - bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url) - - // donate - bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url) + binding.settingsMetaButtonForkContact.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_fork_contact_url), + requireContext() + ) + } // privacy policy - bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url) + binding.settingsMetaButtonPrivacy.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_privacy_url), + requireContext() + ) + } // 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 847639c..28e2e02 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 @@ -1,26 +1,24 @@ 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.view.View -import android.window.OnBackInvokedDispatcher import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.viewpager2.adapter.FragmentStateAdapter -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.tabs.TabLayoutMediator -import de.jrpie.android.launcher.databinding.TutorialBinding +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager.widget.ViewPager +import com.google.android.material.tabs.TabLayout +import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.preferences.LauncherPreferences +import de.jrpie.android.launcher.saveListActivityChoice import de.jrpie.android.launcher.ui.UIObject -import de.jrpie.android.launcher.ui.blink -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment0Start -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment1Concept -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment2Usage -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment3AppList -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment4Setup -import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment5Finish +import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentConcept +import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentFinish +import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentSetup +import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentStart +import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentUsage /** * The [TutorialActivity] is displayed automatically on new installations. @@ -31,75 +29,19 @@ import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment5Finish */ class TutorialActivity : AppCompatActivity(), UIObject { - private lateinit var binding: TutorialBinding - - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) super.onCreate() // Initialise layout - binding = TutorialBinding.inflate(layoutInflater) - setContentView(binding.root) - - // 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() - } - } - + setContentView(R.layout.tutorial) // set up tabs and swiping in settings - val sectionsPagerAdapter = TutorialSectionsPagerAdapter(this) - binding.tutorialViewpager.apply { - adapter = sectionsPagerAdapter - currentItem = 0 - registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - binding.tutorialButtonNext.apply { - val lastItem = sectionsPagerAdapter.itemCount - 1 - visibility = if (position == lastItem) { - View.INVISIBLE - } else { - View.VISIBLE - } - if (position == 0) { - blink() - } else { - clearAnimation() - } - } - binding.tutorialButtonBack.apply { - visibility = if (position == 0) { - View.INVISIBLE - } else { - View.VISIBLE - } - } - } - }) - } - TabLayoutMediator(binding.tutorialTabs, binding.tutorialViewpager) { _, _ -> }.attach() - binding.tutorialButtonNext.setOnClickListener { - binding.tutorialViewpager.apply { - setCurrentItem( - (currentItem + 1).coerceAtMost(sectionsPagerAdapter.itemCount - 1), - true - ) - } - } - binding.tutorialButtonBack.setOnClickListener { - binding.tutorialViewpager.apply { - setCurrentItem((currentItem - 1).coerceAtLeast(0), true) - } - } + val sectionsPagerAdapter = TutorialSectionsPagerAdapter(supportFragmentManager) + val viewPager: ViewPager = findViewById(R.id.tutorial_viewpager) + viewPager.adapter = sectionsPagerAdapter + val tabs: TabLayout = findViewById(R.id.tutorial_tabs) + tabs.setupWithViewPager(viewPager) } override fun getTheme(): Resources.Theme { @@ -111,9 +53,14 @@ class TutorialActivity : AppCompatActivity(), UIObject { super.onStart() } - // prevent going back when the tutorial is shown for the first time - @Deprecated("Deprecated in Java", ReplaceWith("use anyway")) - @Suppress("deprecation") // support API level < 33 + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_CHOOSE_APP -> saveListActivityChoice(data) + else -> super.onActivityResult(requestCode, resultCode, data) + } + } + + // Default: prevent going back, allow if viewed again later override fun onBackPressed() { if (LauncherPreferences.internal().started()) super.onBackPressed() @@ -127,22 +74,26 @@ class TutorialActivity : AppCompatActivity(), UIObject { * * Tabs: (Start | Concept | Usage | Setup | Finish) */ -class TutorialSectionsPagerAdapter(activity: FragmentActivity) : - FragmentStateAdapter(activity) { +class TutorialSectionsPagerAdapter(fm: FragmentManager) : + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getItemCount(): Int { - return 6 - } - - override fun createFragment(position: Int): Fragment { + override fun getItem(position: Int): Fragment { return when (position) { - 0 -> TutorialFragment0Start() - 1 -> TutorialFragment1Concept() - 2 -> TutorialFragment2Usage() - 3 -> TutorialFragment3AppList() - 4 -> TutorialFragment4Setup() - 5 -> TutorialFragment5Finish() + 0 -> TutorialFragmentStart() + 1 -> TutorialFragmentConcept() + 2 -> TutorialFragmentUsage() + 3 -> TutorialFragmentSetup() + 4 -> TutorialFragmentFinish() else -> Fragment() } } + + /* We don't use titles here, as we have the dots */ + override fun getPageTitle(position: Int): CharSequence { + return "" + } + + override fun getCount(): Int { + return 5 + } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment3AppList.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment3AppList.kt deleted file mode 100644 index 78698aa..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment3AppList.kt +++ /dev/null @@ -1,30 +0,0 @@ -package de.jrpie.android.launcher.ui.tutorial.tabs - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.ui.UIObject - -/** - * The [TutorialFragment3AppList] is a used as a tab in the TutorialActivity. - * - * Tells the user how his screen will look and how the app can be used - */ -class TutorialFragment3AppList : Fragment(), UIObject { - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.tutorial_3_app_list, container, false) - } - - override fun onStart() { - super.onStart() - super.onStart() - } - -} diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment1Concept.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentConcept.kt similarity index 68% rename from app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment1Concept.kt rename to app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentConcept.kt index 876266e..f0fd233 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment1Concept.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentConcept.kt @@ -6,22 +6,22 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import de.jrpie.android.launcher.BuildConfig -import de.jrpie.android.launcher.databinding.Tutorial1ConceptBinding +import de.jrpie.android.launcher.databinding.TutorialConceptBinding import de.jrpie.android.launcher.ui.UIObject /** - * The [TutorialFragment1Concept] is a used as a tab in the TutorialActivity. + * The [TutorialFragmentConcept] is a used as a tab in the TutorialActivity. * * It is used to display info about Launchers concept (open source, efficiency ...) */ -class TutorialFragment1Concept : Fragment(), UIObject { - private lateinit var binding: Tutorial1ConceptBinding +class TutorialFragmentConcept : Fragment(), UIObject { + private lateinit var binding: TutorialConceptBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = Tutorial1ConceptBinding.inflate(inflater, container, false) + binding = TutorialConceptBinding.inflate(inflater, container, false) binding.tutorialConceptBadgeVersion.text = BuildConfig.VERSION_NAME return binding.root } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentFinish.kt similarity index 80% rename from app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt rename to app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentFinish.kt index 2fd093e..2d01d0a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentFinish.kt @@ -6,25 +6,25 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import de.jrpie.android.launcher.BuildConfig.VERSION_CODE -import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding +import de.jrpie.android.launcher.databinding.TutorialFinishBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.setDefaultHomeScreen import de.jrpie.android.launcher.ui.UIObject /** - * The [TutorialFragment5Finish] is a used as a tab in the TutorialActivity. + * The [TutorialFragmentFinish] is a used as a tab in the TutorialActivity. * * It is used to display further resources and let the user start Launcher */ -class TutorialFragment5Finish : Fragment(), UIObject { +class TutorialFragmentFinish : Fragment(), UIObject { - private lateinit var binding: Tutorial5FinishBinding + private lateinit var binding: TutorialFinishBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = Tutorial5FinishBinding.inflate(inflater, container, false) + binding = TutorialFinishBinding.inflate(inflater, container, false) return binding.root } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment4Setup.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentSetup.kt similarity index 74% rename from app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment4Setup.kt rename to app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentSetup.kt index 56eb6ca..09ef4c9 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment4Setup.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentSetup.kt @@ -9,17 +9,17 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.ui.UIObject /** - * The [TutorialFragment4Setup] is a used as a tab in the TutorialActivity. + * The [TutorialFragmentSetup] is a used as a tab in the TutorialActivity. * * It is used to display info in the tutorial */ -class TutorialFragment4Setup : Fragment(), UIObject { +class TutorialFragmentSetup : Fragment(), UIObject { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.tutorial_4_setup, container, false) + return inflater.inflate(R.layout.tutorial_setup, container, false) } override fun onStart() { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment0Start.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentStart.kt similarity index 59% rename from app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment0Start.kt rename to app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentStart.kt index 5ce5920..445ded1 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment0Start.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentStart.kt @@ -5,22 +5,24 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import de.jrpie.android.launcher.databinding.Tutorial0StartBinding +import de.jrpie.android.launcher.databinding.TutorialStartBinding import de.jrpie.android.launcher.ui.UIObject +import de.jrpie.android.launcher.ui.blink /** - * The [TutorialFragment0Start] is a used as a tab in the TutorialActivity. + * The [TutorialFragmentStart] is a used as a tab in the TutorialActivity. * * It displays info about the app and gets the user into the tutorial */ -class TutorialFragment0Start : Fragment(), UIObject { +class TutorialFragmentStart : Fragment(), UIObject { - private lateinit var binding: Tutorial0StartBinding + private lateinit var binding: TutorialStartBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = Tutorial0StartBinding.inflate(inflater, container, false) + binding = TutorialStartBinding.inflate(inflater, container, false) + binding.tutorialStartIconRight.blink() return binding.root } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment2Usage.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentUsage.kt similarity index 75% rename from app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment2Usage.kt rename to app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentUsage.kt index 4b24dcd..90db232 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment2Usage.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragmentUsage.kt @@ -9,17 +9,17 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.ui.UIObject /** - * The [TutorialFragment2Usage] is a used as a tab in the TutorialActivity. + * The [TutorialFragmentUsage] is a used as a tab in the TutorialActivity. * * Tells the user how his screen will look and how the app can be used */ -class TutorialFragment2Usage : Fragment(), UIObject { +class TutorialFragmentUsage : Fragment(), UIObject { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.tutorial_2_usage, container, false) + return inflater.inflate(R.layout.tutorial_usage, container, false) } override fun onStart() { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/util/HtmlTextView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/util/HtmlTextView.kt index 5e38b9f..549f10f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/util/HtmlTextView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/util/HtmlTextView.kt @@ -12,7 +12,6 @@ class HtmlTextView(context: Context, attr: AttributeSet?, int: Int) : constructor(context: Context) : this(context, null, 0) init { - @Suppress("deprecation") // required to support API level < 24 text = Html.fromHtml(text.toString()) movementMethod = LinkMovementMethod.getInstance() } diff --git a/app/src/main/res/drawable-mdpi/tutorial_app_list.png b/app/src/main/res/drawable-mdpi/tutorial_app_list.png deleted file mode 100644 index 66ebaaf..0000000 Binary files a/app/src/main/res/drawable-mdpi/tutorial_app_list.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/tutorial_home_screen.png b/app/src/main/res/drawable-mdpi/tutorial_home_screen.png deleted file mode 100644 index ccbce5d..0000000 Binary files a/app/src/main/res/drawable-mdpi/tutorial_home_screen.png and /dev/null differ diff --git a/app/src/main/res/drawable/baseline_apps_24.xml b/app/src/main/res/drawable/baseline_apps_24.xml deleted file mode 100644 index c5a49a0..0000000 --- a/app/src/main/res/drawable/baseline_apps_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_close_24.xml b/app/src/main/res/drawable/baseline_close_24.xml index 2ab439d..41350ac 100644 --- a/app/src/main/res/drawable/baseline_close_24.xml +++ b/app/src/main/res/drawable/baseline_close_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_favorite_24.xml b/app/src/main/res/drawable/baseline_favorite_24.xml index 5a612d2..4f9b020 100644 --- a/app/src/main/res/drawable/baseline_favorite_24.xml +++ b/app/src/main/res/drawable/baseline_favorite_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_favorite_border_24.xml b/app/src/main/res/drawable/baseline_favorite_border_24.xml index 14875dd..cecc9b0 100644 --- a/app/src/main/res/drawable/baseline_favorite_border_24.xml +++ b/app/src/main/res/drawable/baseline_favorite_border_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_flashlight_on_24.xml b/app/src/main/res/drawable/baseline_flashlight_on_24.xml index 16654cd..e1326ae 100644 --- a/app/src/main/res/drawable/baseline_flashlight_on_24.xml +++ b/app/src/main/res/drawable/baseline_flashlight_on_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_home_24.xml b/app/src/main/res/drawable/baseline_home_24.xml deleted file mode 100644 index 935d1b6..0000000 --- a/app/src/main/res/drawable/baseline_home_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_lock_24.xml b/app/src/main/res/drawable/baseline_lock_24.xml index 8cb2d1f..1e96180 100644 --- a/app/src/main/res/drawable/baseline_lock_24.xml +++ b/app/src/main/res/drawable/baseline_lock_24.xml @@ -2,8 +2,9 @@ android:width="24dp" android:height="24dp" android:viewportWidth="960" - android:viewportHeight="960"> - + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + diff --git a/app/src/main/res/drawable/baseline_lock_open_24.xml b/app/src/main/res/drawable/baseline_lock_open_24.xml index 8d8e09b..f0f6ea3 100644 --- a/app/src/main/res/drawable/baseline_lock_open_24.xml +++ b/app/src/main/res/drawable/baseline_lock_open_24.xml @@ -1,6 +1,7 @@ + - + diff --git a/app/src/main/res/drawable/baseline_more_horiz_24.xml b/app/src/main/res/drawable/baseline_more_horiz_24.xml new file mode 100644 index 0000000..a370298 --- /dev/null +++ b/app/src/main/res/drawable/baseline_more_horiz_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_navigate_before_24.xml b/app/src/main/res/drawable/baseline_navigate_before_24.xml deleted file mode 100644 index 4097b26..0000000 --- a/app/src/main/res/drawable/baseline_navigate_before_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_navigate_next_24.xml b/app/src/main/res/drawable/baseline_navigate_next_24.xml deleted file mode 100644 index 22cef28..0000000 --- a/app/src/main/res/drawable/baseline_navigate_next_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_not_interested_24.xml b/app/src/main/res/drawable/baseline_not_interested_24.xml index 875f546..48ab05d 100644 --- a/app/src/main/res/drawable/baseline_not_interested_24.xml +++ b/app/src/main/res/drawable/baseline_not_interested_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_notifications_24.xml b/app/src/main/res/drawable/baseline_notifications_24.xml index ca969df..b695693 100644 --- a/app/src/main/res/drawable/baseline_notifications_24.xml +++ b/app/src/main/res/drawable/baseline_notifications_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_play_arrow_24.xml b/app/src/main/res/drawable/baseline_play_arrow_24.xml deleted file mode 100644 index ca4e475..0000000 --- a/app/src/main/res/drawable/baseline_play_arrow_24.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_question_mark_24.xml b/app/src/main/res/drawable/baseline_question_mark_24.xml deleted file mode 100644 index 9a2b28a..0000000 --- a/app/src/main/res/drawable/baseline_question_mark_24.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_search_24.xml b/app/src/main/res/drawable/baseline_search_24.xml index 9ba30e3..ca9cbc0 100644 --- a/app/src/main/res/drawable/baseline_search_24.xml +++ b/app/src/main/res/drawable/baseline_search_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_security_24.xml b/app/src/main/res/drawable/baseline_security_24.xml index cd38b06..3c260ff 100644 --- a/app/src/main/res/drawable/baseline_security_24.xml +++ b/app/src/main/res/drawable/baseline_security_24.xml @@ -1,5 +1,6 @@ diff --git a/app/src/main/res/drawable/baseline_settings_24.xml b/app/src/main/res/drawable/baseline_settings_24.xml index 4200acc..7cb5b17 100644 --- a/app/src/main/res/drawable/baseline_settings_24.xml +++ b/app/src/main/res/drawable/baseline_settings_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_settings_applications_24.xml b/app/src/main/res/drawable/baseline_settings_applications_24.xml index dd30af7..f2d03cc 100644 --- a/app/src/main/res/drawable/baseline_settings_applications_24.xml +++ b/app/src/main/res/drawable/baseline_settings_applications_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_skip_next_24.xml b/app/src/main/res/drawable/baseline_skip_next_24.xml index 9e203e0..0091e03 100644 --- a/app/src/main/res/drawable/baseline_skip_next_24.xml +++ b/app/src/main/res/drawable/baseline_skip_next_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_skip_previous_24.xml b/app/src/main/res/drawable/baseline_skip_previous_24.xml index 832a188..0029a3e 100644 --- a/app/src/main/res/drawable/baseline_skip_previous_24.xml +++ b/app/src/main/res/drawable/baseline_skip_previous_24.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/baseline_volume_adjust_24.xml b/app/src/main/res/drawable/baseline_volume_adjust_24.xml deleted file mode 100644 index 38e6a8b..0000000 --- a/app/src/main/res/drawable/baseline_volume_adjust_24.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/baseline_volume_down_24.xml b/app/src/main/res/drawable/baseline_volume_down_24.xml index 1a34ad9..78b51d3 100644 --- a/app/src/main/res/drawable/baseline_volume_down_24.xml +++ b/app/src/main/res/drawable/baseline_volume_down_24.xml @@ -2,6 +2,7 @@ android:width="24dp" android:height="24dp" android:autoMirrored="true" + android:tint="?attr/colorControlNormal" android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/baseline_volume_up_24.xml b/app/src/main/res/drawable/baseline_volume_up_24.xml index f147499..6737fa6 100644 --- a/app/src/main/res/drawable/baseline_volume_up_24.xml +++ b/app/src/main/res/drawable/baseline_volume_up_24.xml @@ -2,6 +2,7 @@ android:width="24dp" android:height="24dp" android:autoMirrored="true" + android:tint="?attr/colorControlNormal" android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/home_round_screen.png b/app/src/main/res/drawable/home_round_screen.png new file mode 100644 index 0000000..f0237e8 Binary files /dev/null and b/app/src/main/res/drawable/home_round_screen.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..4f1c4ab --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_pin_shortcut.xml b/app/src/main/res/layout/activity_pin_shortcut.xml deleted file mode 100644 index 2519374..0000000 --- a/app/src/main/res/layout/activity_pin_shortcut.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -