mirror of
https://github.com/jrpie/Launcher.git
synced 2025-04-18 18:00:50 +02:00
Compare commits
88 commits
Author | SHA1 | Date | |
---|---|---|---|
22633bdac3 | |||
4f795289d5 | |||
2774b74d9d | |||
3d49ec16a7 | |||
![]() |
a0b2417363 | ||
![]() |
20a01e9f03 | ||
![]() |
24250ad345 | ||
![]() |
7cce425339 | ||
![]() |
0877ca6772 | ||
![]() |
03a9833b51 | ||
![]() |
ce65741717 | ||
![]() |
c085087e1e | ||
![]() |
cbd23159da | ||
![]() |
940e5785dc | ||
![]() |
14ffbd1f6c | ||
![]() |
bfc84b57ca | ||
![]() |
8b1963f3e1 | ||
![]() |
4f801427a4 | ||
![]() |
8a487eb4c7 | ||
![]() |
e6dd2634ae | ||
0441b3fd3d | |||
e7c1d28576 | |||
653d16b269 | |||
5d695ec0ea | |||
b4608ef153 | |||
8e140e2e69 | |||
7fc58fe384 | |||
54409b6312 | |||
865cd47583 | |||
58ddd3c8cc | |||
0baa889de5 | |||
fa34cbae90 | |||
7ac09bd465 | |||
![]() |
c8d7a1cc3e | ||
7094d55484 | |||
b3e4d8834a | |||
008d0242ee | |||
![]() |
65034bf2fb | ||
![]() |
3cec2c36c6 | ||
00350d4c3a | |||
0941062270 | |||
c783a51658 | |||
da115bb2d9 | |||
90434617e7 | |||
47940811b4 | |||
![]() |
232046e986 | ||
![]() |
ff108ee323 | ||
![]() |
943867d938 | ||
![]() |
59f4a29044 | ||
![]() |
bd70b822cf | ||
72f9c0595f | |||
75b22400c5 | |||
c1511cd475 | |||
3597baee1f | |||
e02ca4091f | |||
541e60356c | |||
492749a340 | |||
55af392706 | |||
077ee4381a | |||
e250a58ef4 | |||
c7af387a94 | |||
6cd17343fc | |||
b156b68d53 | |||
c9ee2c6304 | |||
bf45b6602e | |||
d7dd1aa71a | |||
3664159782 | |||
8df9aae029 | |||
![]() |
f776fbb88e | ||
![]() |
a5ec8bb796 | ||
3b2dca9af9 | |||
1b12032750 | |||
55a54fb9a5 | |||
![]() |
e39ff62613 | ||
9fe1a37ed6 | |||
1f825d6f00 | |||
5ea03d39fa | |||
ae119ac4ce | |||
8948b34243 | |||
f18811bfa2 | |||
bd1f999a0e | |||
941b06b258 | |||
![]() |
9935386ad8 | ||
![]() |
d44224071f | ||
![]() |
1f8f75dec8 | ||
86528f4e27 | |||
3aee137a3c | |||
88a78749c2 |
139 changed files with 3439 additions and 1307 deletions
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
@ -1,3 +1,3 @@
|
||||||
# How you can support finnmglas/Launcher
|
# How you can support jrpie/Launcher
|
||||||
|
|
||||||
custom: sponsor.finnmglas.com
|
custom: https://s.jrpie.de/launcher-donate
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export JAVA_HOME="/usr/lib/jvm/java-23-openjdk/"
|
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk/"
|
||||||
OUTPUT_DIR="$HOME/launcher-release"
|
OUTPUT_DIR="$HOME/launcher-release"
|
||||||
BUILD_TOOLS_DIR="$HOME/Android/Sdk/build-tools/35.0.0"
|
BUILD_TOOLS_DIR="$HOME/Android/Sdk/build-tools/35.0.0"
|
||||||
KEYSTORE="$HOME/data/keys/launcher_jrpie.jks"
|
KEYSTORE="$HOME/data/keys/launcher_jrpie.jks"
|
||||||
|
|
|
@ -59,6 +59,7 @@ The following gestures are available:
|
||||||
- swipe up / down / left / right,
|
- swipe up / down / left / right,
|
||||||
- swipe with two fingers,
|
- swipe with two fingers,
|
||||||
- swipe on the left / right resp. top / bottom edge,
|
- swipe on the left / right resp. top / bottom edge,
|
||||||
|
- tap, then swipe up / down / left / right,
|
||||||
- draw < / > / V / Λ
|
- draw < / > / V / Λ
|
||||||
- click on date / time,
|
- click on date / time,
|
||||||
- double click,
|
- double click,
|
||||||
|
|
|
@ -23,8 +23,8 @@ android {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 35
|
targetSdkVersion 35
|
||||||
compileSdk 35
|
compileSdk 35
|
||||||
versionCode 38
|
versionCode 44
|
||||||
versionName "0.0.22"
|
versionName "0.1.4"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
@ -85,16 +85,17 @@ android {
|
||||||
// Disables dependency metadata when building Android App Bundles.
|
// Disables dependency metadata when building Android App Bundles.
|
||||||
includeInBundle = false
|
includeInBundle = false
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
lintOptions {
|
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
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.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.core:core-ktx:1.15.0'
|
implementation 'androidx.core:core-ktx:1.15.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/launcherBaseTheme"
|
android:theme="@style/launcherBaseTheme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.PinShortcutActivity"
|
||||||
|
android:autoRemoveFromRecents="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
|
||||||
|
<action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.HomeActivity"
|
android:name=".ui.HomeActivity"
|
||||||
android:clearTaskOnLaunch="true"
|
android:clearTaskOnLaunch="true"
|
||||||
|
@ -75,7 +85,7 @@
|
||||||
<service
|
<service
|
||||||
android:name=".actions.lock.LauncherAccessibilityService"
|
android:name=".actions.lock.LauncherAccessibilityService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/accessibility_service_name"
|
android:label="@string/app_name"
|
||||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||||
|
@ -87,4 +97,4 @@
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.content.IntentFilter
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
import android.content.pm.ShortcutInfo
|
import android.content.pm.ShortcutInfo
|
||||||
import android.os.AsyncTask
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
|
@ -15,15 +14,18 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import de.jrpie.android.launcher.actions.TorchManager
|
import de.jrpie.android.launcher.actions.TorchManager
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo
|
||||||
import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
|
import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
|
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
|
||||||
import de.jrpie.android.launcher.preferences.resetPreferences
|
import de.jrpie.android.launcher.preferences.resetPreferences
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Application : android.app.Application() {
|
class Application : android.app.Application() {
|
||||||
val apps = MutableLiveData<List<DetailedAppInfo>>()
|
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
||||||
val privateSpaceLocked = MutableLiveData<Boolean>()
|
val privateSpaceLocked = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
@ -82,10 +84,12 @@ class Application : android.app.Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var torchManager: TorchManager? = null
|
var torchManager: TorchManager? = null
|
||||||
private var customAppNames: HashMap<AppInfo, String>? = null
|
private var customAppNames: HashMap<AbstractAppInfo, String>? = null
|
||||||
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref ->
|
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref ->
|
||||||
if (pref == getString(R.string.settings_apps_custom_names_key)) {
|
if (pref == getString(R.string.settings_apps_custom_names_key)) {
|
||||||
customAppNames = LauncherPreferences.apps().customNames()
|
customAppNames = LauncherPreferences.apps().customNames()
|
||||||
|
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
|
||||||
|
loadApps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +110,10 @@ class Application : android.app.Application() {
|
||||||
// Try to restore old preferences
|
// Try to restore old preferences
|
||||||
migratePreferencesToNewVersion(this)
|
migratePreferencesToNewVersion(this)
|
||||||
|
|
||||||
// First time opening the app: set defaults and start tutorial
|
// First time opening the app: set defaults
|
||||||
|
// The tutorial is started from HomeActivity#onStart, as starting it here is blocked by android
|
||||||
if (!LauncherPreferences.internal().started()) {
|
if (!LauncherPreferences.internal().started()) {
|
||||||
resetPreferences(this)
|
resetPreferences(this)
|
||||||
|
|
||||||
LauncherPreferences.internal().started(true)
|
|
||||||
openTutorial(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,21 +134,27 @@ class Application : android.app.Application() {
|
||||||
it.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
|
it.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContextCompat.registerReceiver(this, profileAvailabilityBroadcastReceiver, filter,
|
ContextCompat.registerReceiver(
|
||||||
|
this, profileAvailabilityBroadcastReceiver, filter,
|
||||||
ContextCompat.RECEIVER_EXPORTED
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
|
||||||
|
removeUnusedShortcuts(this)
|
||||||
|
}
|
||||||
loadApps()
|
loadApps()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCustomAppNames(): HashMap<AppInfo, String> {
|
fun getCustomAppNames(): HashMap<AbstractAppInfo, String> {
|
||||||
return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap())
|
return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap())
|
||||||
.also { customAppNames = it }
|
.also { customAppNames = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadApps() {
|
private fun loadApps() {
|
||||||
privateSpaceLocked.postValue(isPrivateSpaceLocked(this))
|
privateSpaceLocked.postValue(isPrivateSpaceLocked(this))
|
||||||
AsyncTask.execute { apps.postValue(getApps(packageManager, applicationContext)) }
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
apps.postValue(getApps(packageManager, applicationContext))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,9 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
|
import android.content.pm.LauncherApps.ShortcutQuery
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.content.pm.ShortcutInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
|
@ -18,25 +19,27 @@ import android.os.UserManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
import de.jrpie.android.launcher.actions.ShortcutAction
|
||||||
|
import de.jrpie.android.launcher.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.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.DetailedPinnedShortcutInfo
|
||||||
|
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||||
import de.jrpie.android.launcher.apps.getPrivateSpaceUser
|
import de.jrpie.android.launcher.apps.getPrivateSpaceUser
|
||||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
/* 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"
|
const val LOG_TAG = "Launcher"
|
||||||
|
|
||||||
|
const val REQUEST_SET_DEFAULT_HOME = 42
|
||||||
|
|
||||||
fun isDefaultHomeScreen(context: Context): Boolean {
|
fun isDefaultHomeScreen(context: Context): Boolean {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val roleManager = context.getSystemService(RoleManager::class.java)
|
val roleManager = context.getSystemService(RoleManager::class.java)
|
||||||
|
@ -58,7 +61,7 @@ fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
&& context is Activity
|
&& context is Activity
|
||||||
&& !isDefault // using role manager only works when µLauncher is not already the default.
|
&& checkDefault // using role manager only works when µLauncher is not already the default.
|
||||||
) {
|
) {
|
||||||
val roleManager = context.getSystemService(RoleManager::class.java)
|
val roleManager = context.getSystemService(RoleManager::class.java)
|
||||||
context.startActivityForResult(
|
context.startActivityForResult(
|
||||||
|
@ -81,9 +84,43 @@ fun getUserFromId(userId: Int?, context: Context): UserHandle {
|
||||||
return profiles.firstOrNull { it.hashCode() == userId } ?: profiles[0]
|
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<ShortcutInfo>? {
|
||||||
|
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<PinnedShortcutInfo> =
|
||||||
|
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) {
|
fun openInBrowser(url: String, context: Context) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
intent.putExtras(Bundle().apply { putBoolean("new_window", true) })
|
intent.putExtras(Bundle().apply { putBoolean("new_window", true) })
|
||||||
try {
|
try {
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
@ -93,18 +130,19 @@ fun openInBrowser(url: String, context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openTutorial(context: Context) {
|
fun openTutorial(context: Context) {
|
||||||
context.startActivity(Intent(context, TutorialActivity::class.java).apply {
|
context.startActivity(Intent(context, TutorialActivity::class.java))
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all apps.
|
* Load all apps.
|
||||||
*/
|
*/
|
||||||
fun getApps(packageManager: PackageManager, context: Context): MutableList<DetailedAppInfo> {
|
fun getApps(
|
||||||
val start = System.currentTimeMillis()
|
packageManager: PackageManager,
|
||||||
val loadList = mutableListOf<DetailedAppInfo>()
|
context: Context
|
||||||
|
): MutableList<AbstractDetailedAppInfo> {
|
||||||
|
var start = System.currentTimeMillis()
|
||||||
|
val loadList = mutableListOf<AbstractDetailedAppInfo>()
|
||||||
|
|
||||||
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
|
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||||
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
|
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
|
||||||
|
@ -141,7 +179,7 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList<Detai
|
||||||
i.addCategory(Intent.CATEGORY_LAUNCHER)
|
i.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
val allApps = packageManager.queryIntentActivities(i, 0)
|
val allApps = packageManager.queryIntentActivities(i, 0)
|
||||||
for (ri in allApps) {
|
for (ri in allApps) {
|
||||||
val app = AppInfo(ri.activityInfo.packageName, null, AppInfo.INVALID_USER)
|
val app = AppInfo(ri.activityInfo.packageName, null, INVALID_USER)
|
||||||
val detailedAppInfo = DetailedAppInfo(
|
val detailedAppInfo = DetailedAppInfo(
|
||||||
app,
|
app,
|
||||||
ri.loadLabel(packageManager),
|
ri.loadLabel(packageManager),
|
||||||
|
@ -151,22 +189,24 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList<Detai
|
||||||
loadList.add(detailedAppInfo)
|
loadList.add(detailedAppInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadList.sortBy { it.getCustomLabel(context).toString() }
|
loadList.sortBy { it.getCustomLabel(context) }
|
||||||
|
|
||||||
val end = System.currentTimeMillis()
|
var end = System.currentTimeMillis()
|
||||||
Log.i(LOG_TAG, "${loadList.size} apps loaded (${end - start}ms)")
|
Log.i(LOG_TAG, "${loadList.size} apps loaded (${end - start}ms)")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
|
start = System.currentTimeMillis()
|
||||||
|
LauncherPreferences.apps().pinnedShortcuts()
|
||||||
|
?.mapNotNull { DetailedPinnedShortcutInfo.fromPinnedShortcutInfo(it, context) }
|
||||||
|
?.let {
|
||||||
|
end = System.currentTimeMillis()
|
||||||
|
Log.i(LOG_TAG, "${it.size} shortcuts loaded (${end - start}ms)")
|
||||||
|
loadList.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return loadList
|
return loadList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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
|
// used for the bug report button
|
||||||
fun getDeviceInfo(): String {
|
fun getDeviceInfo(): String {
|
||||||
return """
|
return """
|
||||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.actions
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences.Editor
|
import android.content.SharedPreferences.Editor
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
@ -12,6 +11,7 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -29,10 +29,6 @@ sealed interface Action {
|
||||||
prefEditor.putString(id, Json.encodeToString(this))
|
prefEditor.putString(id, Json.encodeToString(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun writeToIntent(intent: Intent) {
|
|
||||||
intent.putExtra("action", Json.encodeToString(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun forGesture(gesture: Gesture): Action? {
|
fun forGesture(gesture: Gesture): Action? {
|
||||||
|
@ -44,23 +40,23 @@ sealed interface Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetToDefaultActions(context: Context) {
|
fun resetToDefaultActions(context: Context) {
|
||||||
val editor = LauncherPreferences.getSharedPreferences().edit()
|
LauncherPreferences.getSharedPreferences().edit {
|
||||||
val boundActions = HashSet<String>()
|
val boundActions = HashSet<String>()
|
||||||
Gesture.entries.forEach { gesture ->
|
Gesture.entries.forEach { gesture ->
|
||||||
context.resources
|
context.resources
|
||||||
.getStringArray(gesture.defaultsResource)
|
.getStringArray(gesture.defaultsResource)
|
||||||
.filterNot { boundActions.contains(it) }
|
.filterNot { boundActions.contains(it) }
|
||||||
.map { Pair(it, Json.decodeFromString<Action>(it)) }
|
.map { Pair(it, Json.decodeFromString<Action>(it)) }
|
||||||
.firstOrNull { it.second.isAvailable(context) }
|
.firstOrNull { it.second.isAvailable(context) }
|
||||||
?.apply {
|
?.apply {
|
||||||
// allow to bind CHOOSE to multiple gestures
|
// allow to bind CHOOSE to multiple gestures
|
||||||
if (second != LauncherAction.CHOOSE) {
|
if (second != LauncherAction.CHOOSE) {
|
||||||
boundActions.add(first)
|
boundActions.add(first)
|
||||||
|
}
|
||||||
|
second.bindToGesture(this@edit, gesture.id)
|
||||||
}
|
}
|
||||||
second.bindToGesture(editor, gesture.id)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
editor.apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActionForGesture(gesture: Gesture, action: Action?) {
|
fun setActionForGesture(gesture: Gesture, action: Action?) {
|
||||||
|
@ -68,15 +64,15 @@ sealed interface Action {
|
||||||
clearActionForGesture(gesture)
|
clearActionForGesture(gesture)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val editor = LauncherPreferences.getSharedPreferences().edit()
|
LauncherPreferences.getSharedPreferences().edit {
|
||||||
action.bindToGesture(editor, gesture.id)
|
action.bindToGesture(this, gesture.id)
|
||||||
editor.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearActionForGesture(gesture: Gesture) {
|
fun clearActionForGesture(gesture: Gesture) {
|
||||||
LauncherPreferences.getSharedPreferences().edit()
|
LauncherPreferences.getSharedPreferences().edit {
|
||||||
.remove(gesture.id)
|
remove(gesture.id)
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launch(
|
fun launch(
|
||||||
|
@ -87,6 +83,9 @@ sealed interface Action {
|
||||||
) {
|
) {
|
||||||
if (action != null && action.invoke(context)) {
|
if (action != null && action.invoke(context)) {
|
||||||
if (context is Activity) {
|
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)
|
context.overridePendingTransition(animationIn, animationOut)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,10 +96,5 @@ sealed interface Action {
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromIntent(data: Intent): Action? {
|
|
||||||
val json = data.getStringExtra("action") ?: return null
|
|
||||||
return Json.decodeFromString(json)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||||
import de.jrpie.android.launcher.ui.list.apps.openSettings
|
import de.jrpie.android.launcher.ui.list.apps.openSettings
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
|
@ -67,7 +67,7 @@ class AppAction(val app: AppInfo) : Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(context: Context): Drawable? {
|
override fun getIcon(context: Context): Drawable? {
|
||||||
return DetailedAppInfo.fromAppInfo(app, context)?.icon
|
return DetailedAppInfo.fromAppInfo(app, context)?.getIcon(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAvailable(context: Context): Boolean {
|
override fun isAvailable(context: Context): Boolean {
|
||||||
|
|
|
@ -79,6 +79,13 @@ enum class Gesture(
|
||||||
R.array.default_up_right,
|
R.array.default_up_right,
|
||||||
R.anim.bottom_up
|
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(
|
SWIPE_UP_DOUBLE(
|
||||||
"action.double_up",
|
"action.double_up",
|
||||||
R.string.settings_gesture_double_up,
|
R.string.settings_gesture_double_up,
|
||||||
|
@ -107,6 +114,13 @@ enum class Gesture(
|
||||||
R.array.default_down_right,
|
R.array.default_down_right,
|
||||||
R.anim.top_down
|
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(
|
SWIPE_DOWN_DOUBLE(
|
||||||
"action.double_down",
|
"action.double_down",
|
||||||
R.string.settings_gesture_double_down,
|
R.string.settings_gesture_double_down,
|
||||||
|
@ -135,6 +149,13 @@ enum class Gesture(
|
||||||
R.array.default_messengers,
|
R.array.default_messengers,
|
||||||
R.anim.right_left
|
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(
|
SWIPE_LEFT_DOUBLE(
|
||||||
"action.double_left",
|
"action.double_left",
|
||||||
R.string.settings_gesture_double_left,
|
R.string.settings_gesture_double_left,
|
||||||
|
@ -163,6 +184,13 @@ enum class Gesture(
|
||||||
R.array.default_right_bottom,
|
R.array.default_right_bottom,
|
||||||
R.anim.left_right
|
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(
|
SWIPE_RIGHT_DOUBLE(
|
||||||
"action.double_right",
|
"action.double_right",
|
||||||
R.string.settings_gesture_double_right,
|
R.string.settings_gesture_double_right,
|
||||||
|
@ -222,7 +250,7 @@ enum class Gesture(
|
||||||
"action.back",
|
"action.back",
|
||||||
R.string.settings_gesture_back,
|
R.string.settings_gesture_back,
|
||||||
R.string.settings_gesture_description_back,
|
R.string.settings_gesture_description_back,
|
||||||
R.array.default_up
|
R.array.default_back
|
||||||
);
|
);
|
||||||
|
|
||||||
enum class Edge {
|
enum class Edge {
|
||||||
|
@ -279,6 +307,17 @@ 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 {
|
fun isDoubleVariant(): Boolean {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
SWIPE_UP_DOUBLE,
|
SWIPE_UP_DOUBLE,
|
||||||
|
|
|
@ -11,8 +11,11 @@ import android.view.KeyEvent
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.Application
|
||||||
|
import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.R
|
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.AppFilter
|
||||||
|
import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked
|
||||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
||||||
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
@ -66,7 +69,11 @@ enum class LauncherAction(
|
||||||
R.string.list_other_list_private_space,
|
R.string.list_other_list_private_space,
|
||||||
R.drawable.baseline_security_24,
|
R.drawable.baseline_security_24,
|
||||||
{ context ->
|
{ context ->
|
||||||
openAppsList(context, private = true)
|
if ((context.applicationContext as Application).privateSpaceLocked.value != true
|
||||||
|
|| !hidePrivateSpaceWhenLocked(context)
|
||||||
|
) {
|
||||||
|
openAppsList(context, private = true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
available = { _ ->
|
available = { _ ->
|
||||||
isPrivateSpaceSupported()
|
isPrivateSpaceSupported()
|
||||||
|
@ -82,22 +89,38 @@ enum class LauncherAction(
|
||||||
VOLUME_UP(
|
VOLUME_UP(
|
||||||
"volume_up",
|
"volume_up",
|
||||||
R.string.list_other_volume_up,
|
R.string.list_other_volume_up,
|
||||||
R.drawable.baseline_volume_up_24, ::audioVolumeUp
|
R.drawable.baseline_volume_up_24,
|
||||||
|
{ context -> audioVolumeAdjust(context, AudioManager.ADJUST_RAISE) }
|
||||||
),
|
),
|
||||||
VOLUME_DOWN(
|
VOLUME_DOWN(
|
||||||
"volume_down",
|
"volume_down",
|
||||||
R.string.list_other_volume_down,
|
R.string.list_other_volume_down,
|
||||||
R.drawable.baseline_volume_down_24, ::audioVolumeDown
|
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) }
|
||||||
),
|
),
|
||||||
TRACK_NEXT(
|
TRACK_NEXT(
|
||||||
"next_track",
|
"next_track",
|
||||||
R.string.list_other_track_next,
|
R.string.list_other_track_next,
|
||||||
R.drawable.baseline_skip_next_24, ::audioNextTrack
|
R.drawable.baseline_skip_next_24,
|
||||||
|
{ context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_NEXT) }
|
||||||
),
|
),
|
||||||
TRACK_PREV(
|
TRACK_PREV(
|
||||||
"previous_track",
|
"previous_track",
|
||||||
R.string.list_other_track_previous,
|
R.string.list_other_track_previous,
|
||||||
R.drawable.baseline_skip_previous_24, ::audioPreviousTrack
|
R.drawable.baseline_skip_previous_24,
|
||||||
|
{ context -> audioManagerPressKey(context, KeyEvent.KEYCODE_MEDIA_PREVIOUS) }
|
||||||
),
|
),
|
||||||
EXPAND_NOTIFICATIONS_PANEL(
|
EXPAND_NOTIFICATIONS_PANEL(
|
||||||
"expand_notifications_panel",
|
"expand_notifications_panel",
|
||||||
|
@ -111,6 +134,14 @@ enum class LauncherAction(
|
||||||
R.drawable.baseline_settings_applications_24,
|
R.drawable.baseline_settings_applications_24,
|
||||||
::expandSettingsPanel
|
::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(
|
||||||
"lock_screen",
|
"lock_screen",
|
||||||
R.string.list_other_lock_screen,
|
R.string.list_other_lock_screen,
|
||||||
|
@ -121,7 +152,13 @@ enum class LauncherAction(
|
||||||
"toggle_torch",
|
"toggle_torch",
|
||||||
R.string.list_other_torch,
|
R.string.list_other_torch,
|
||||||
R.drawable.baseline_flashlight_on_24,
|
R.drawable.baseline_flashlight_on_24,
|
||||||
::toggleTorch
|
::toggleTorch,
|
||||||
|
),
|
||||||
|
LAUNCH_OTHER_LAUNCHER(
|
||||||
|
"launcher_other_launcher",
|
||||||
|
R.string.list_other_launch_other_launcher,
|
||||||
|
R.drawable.baseline_home_24,
|
||||||
|
::launchOtherLauncher
|
||||||
),
|
),
|
||||||
NOP("nop", R.string.list_other_nop, R.drawable.baseline_not_interested_24, {});
|
NOP("nop", R.string.list_other_nop, R.drawable.baseline_not_interested_24, {});
|
||||||
|
|
||||||
|
@ -155,56 +192,28 @@ enum class LauncherAction(
|
||||||
|
|
||||||
|
|
||||||
/* Media player actions */
|
/* Media player actions */
|
||||||
|
private fun audioManagerPressKey(context: Context, key: Int) {
|
||||||
private fun audioNextTrack(context: Context) {
|
|
||||||
|
|
||||||
val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
val eventTime: Long = SystemClock.uptimeMillis()
|
val eventTime: Long = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
val downEvent =
|
val downEvent =
|
||||||
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT, 0)
|
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, key, 0)
|
||||||
mAudioManager.dispatchMediaKeyEvent(downEvent)
|
mAudioManager.dispatchMediaKeyEvent(downEvent)
|
||||||
|
val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, key, 0)
|
||||||
val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT, 0)
|
|
||||||
mAudioManager.dispatchMediaKeyEvent(upEvent)
|
mAudioManager.dispatchMediaKeyEvent(upEvent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun audioPreviousTrack(context: Context) {
|
private fun audioVolumeAdjust(context: Context, direction: Int) {
|
||||||
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 =
|
val audioManager =
|
||||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
audioManager.adjustStreamVolume(
|
audioManager.adjustStreamVolume(
|
||||||
AudioManager.STREAM_MUSIC,
|
AudioManager.STREAM_MUSIC,
|
||||||
AudioManager.ADJUST_RAISE,
|
direction,
|
||||||
AudioManager.FLAG_SHOW_UI
|
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 */
|
/* End media player actions */
|
||||||
|
|
||||||
private fun toggleTorch(context: Context) {
|
private fun toggleTorch(context: Context) {
|
||||||
|
@ -255,6 +264,15 @@ 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) {
|
private fun openSettings(context: Context) {
|
||||||
context.startActivity(Intent(context, SettingsActivity::class.java))
|
context.startActivity(Intent(context, SettingsActivity::class.java))
|
||||||
}
|
}
|
||||||
|
@ -320,5 +338,4 @@ private class LauncherActionSerializer : KSerializer<LauncherAction> {
|
||||||
encodeSerializableElement(descriptor, 0, String.serializer(), value.id)
|
encodeSerializableElement(descriptor, 0, String.serializer(), value.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,26 +22,44 @@ class LauncherAccessibilityService : AccessibilityService() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "Launcher Accessibility"
|
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_LOCK_SCREEN = "ACTION_LOCK_SCREEN"
|
||||||
|
const val ACTION_RECENT_APPS = "ACTION_RECENT_APPS"
|
||||||
|
|
||||||
fun lockScreen(context: Context) {
|
private fun invoke(context: Context, action: String, failureMessageRes: Int) {
|
||||||
try {
|
try {
|
||||||
context.startService(
|
context.startService(
|
||||||
Intent(
|
Intent(
|
||||||
context,
|
context,
|
||||||
LauncherAccessibilityService::class.java
|
LauncherAccessibilityService::class.java
|
||||||
).apply {
|
).apply {
|
||||||
action = ACTION_LOCK_SCREEN
|
this.action = action
|
||||||
})
|
})
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.alert_lock_screen_failed),
|
context.getString(failureMessageRes),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).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 {
|
fun isEnabled(context: Context): Boolean {
|
||||||
val enabledServices = Settings.Secure.getString(
|
val enabledServices = Settings.Secure.getString(
|
||||||
context.contentResolver,
|
context.contentResolver,
|
||||||
|
@ -58,7 +76,7 @@ class LauncherAccessibilityService : AccessibilityService() {
|
||||||
setView(R.layout.dialog_consent_accessibility)
|
setView(R.layout.dialog_consent_accessibility)
|
||||||
setTitle(R.string.dialog_consent_accessibility_title)
|
setTitle(R.string.dialog_consent_accessibility_title)
|
||||||
setPositiveButton(R.string.dialog_consent_accessibility_ok) { _, _ ->
|
setPositiveButton(R.string.dialog_consent_accessibility_ok) { _, _ ->
|
||||||
lockScreen(context)
|
invoke(context, ACTION_REQUEST_ENABLE, R.string.alert_enable_accessibility_failed)
|
||||||
}
|
}
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
|
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
|
||||||
}.create().also { it.show() }.apply {
|
}.create().also { it.show() }.apply {
|
||||||
|
@ -94,7 +112,9 @@ class LauncherAccessibilityService : AccessibilityService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
|
ACTION_REQUEST_ENABLE -> {} // do nothing
|
||||||
ACTION_LOCK_SCREEN -> handleLockScreen()
|
ACTION_LOCK_SCREEN -> handleLockScreen()
|
||||||
|
ACTION_RECENT_APPS -> performGlobalAction(GLOBAL_ACTION_RECENTS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
|
|
@ -6,10 +6,10 @@ import android.widget.Button
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import de.jrpie.android.launcher.BuildConfig
|
import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
|
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
enum class LockMethod(
|
enum class LockMethod(
|
||||||
private val lock: (Context) -> Unit,
|
private val lock: (Context) -> Unit,
|
||||||
private val isEnabled: (Context) -> Boolean,
|
private val isEnabled: (Context) -> Boolean,
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package de.jrpie.android.launcher.apps
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.util.Log
|
||||||
|
import de.jrpie.android.launcher.Application
|
||||||
|
import de.jrpie.android.launcher.actions.Action
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface is implemented by [DetailedAppInfo] and [DetailedPinnedShortcutInfo]
|
||||||
|
*/
|
||||||
|
sealed interface AbstractDetailedAppInfo {
|
||||||
|
fun getRawInfo(): AbstractAppInfo
|
||||||
|
fun getLabel(): String
|
||||||
|
fun getIcon(context: Context): Drawable
|
||||||
|
fun getUser(context: Context): UserHandle
|
||||||
|
fun isPrivate(): Boolean
|
||||||
|
fun isRemovable(): Boolean
|
||||||
|
fun getAction(): Action
|
||||||
|
|
||||||
|
|
||||||
|
fun getCustomLabel(context: Context): String {
|
||||||
|
val map = (context.applicationContext as? Application)?.getCustomAppNames()
|
||||||
|
return map?.get(getRawInfo()) ?: getLabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setCustomLabel(label: CharSequence?) {
|
||||||
|
Log.i("Launcher", "Setting custom label for ${this.getRawInfo()} to ${label}.")
|
||||||
|
val map = LauncherPreferences.apps().customNames() ?: HashMap<AbstractAppInfo, String>()
|
||||||
|
|
||||||
|
if (label.isNullOrEmpty()) {
|
||||||
|
map.remove(getRawInfo())
|
||||||
|
} else {
|
||||||
|
map[getRawInfo()] = label.toString()
|
||||||
|
}
|
||||||
|
LauncherPreferences.apps().customNames(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.AppAction
|
import de.jrpie.android.launcher.actions.AppAction
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
import de.jrpie.android.launcher.actions.ShortcutAction
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.text.Regex.Companion.escape
|
import kotlin.text.Regex.Companion.escape
|
||||||
|
@ -18,13 +19,14 @@ class AppFilter(
|
||||||
var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE
|
var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> {
|
operator fun invoke(apps: List<AbstractDetailedAppInfo>): List<AbstractDetailedAppInfo> {
|
||||||
var apps =
|
var apps =
|
||||||
apps.sortedBy { app -> app.getCustomLabel(context).toString().lowercase(Locale.ROOT) }
|
apps.sortedBy { app -> app.getCustomLabel(context).lowercase(Locale.ROOT) }
|
||||||
|
|
||||||
val hidden = LauncherPreferences.apps().hidden() ?: setOf()
|
val hidden = LauncherPreferences.apps().hidden() ?: setOf()
|
||||||
val favorites = LauncherPreferences.apps().favorites() ?: setOf()
|
val favorites = LauncherPreferences.apps().favorites() ?: setOf()
|
||||||
val private = apps.filter { it.isPrivateSpaceApp }.map { it.app }.toSet()
|
val private = apps.filter { it.isPrivate() }
|
||||||
|
.map { it.getRawInfo() }.toSet()
|
||||||
|
|
||||||
apps = apps.filter { info ->
|
apps = apps.filter { info ->
|
||||||
favoritesVisibility.predicate(favorites, info)
|
favoritesVisibility.predicate(favorites, info)
|
||||||
|
@ -35,9 +37,13 @@ class AppFilter(
|
||||||
if (LauncherPreferences.apps().hideBoundApps()) {
|
if (LauncherPreferences.apps().hideBoundApps()) {
|
||||||
val boundApps = Gesture.entries
|
val boundApps = Gesture.entries
|
||||||
.filter(Gesture::isEnabled)
|
.filter(Gesture::isEnabled)
|
||||||
.mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app }
|
.mapNotNull { g -> Action.forGesture(g) }
|
||||||
|
.mapNotNull {
|
||||||
|
(it as? AppAction)?.app
|
||||||
|
?: (it as? ShortcutAction)?.shortcut
|
||||||
|
}
|
||||||
.toSet()
|
.toSet()
|
||||||
apps = apps.filterNot { info -> boundApps.contains(info.app) }
|
apps = apps.filterNot { info -> boundApps.contains(info.getRawInfo()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize text for search
|
// normalize text for search
|
||||||
|
@ -57,11 +63,11 @@ class AppFilter(
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
return apps
|
return apps
|
||||||
} else {
|
} else {
|
||||||
val r: MutableList<DetailedAppInfo> = ArrayList()
|
val r: MutableList<AbstractDetailedAppInfo> = ArrayList()
|
||||||
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
|
val appsSecondary: MutableList<AbstractDetailedAppInfo> = ArrayList()
|
||||||
val normalizedQuery: String = normalize(query)
|
val normalizedQuery: String = normalize(query)
|
||||||
for (item in apps) {
|
for (item in apps) {
|
||||||
val itemLabel: String = normalize(item.getCustomLabel(context).toString())
|
val itemLabel: String = normalize(item.getCustomLabel(context))
|
||||||
|
|
||||||
if (itemLabel.startsWith(normalizedQuery)) {
|
if (itemLabel.startsWith(normalizedQuery)) {
|
||||||
r.add(item)
|
r.add(item)
|
||||||
|
@ -77,11 +83,11 @@ class AppFilter(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
enum class AppSetVisibility(
|
enum class AppSetVisibility(
|
||||||
val predicate: (set: Set<AppInfo>, DetailedAppInfo) -> Boolean
|
val predicate: (set: Set<AbstractAppInfo>, AbstractDetailedAppInfo) -> Boolean
|
||||||
) {
|
) {
|
||||||
VISIBLE({ _, _ -> true }),
|
VISIBLE({ _, _ -> true }),
|
||||||
HIDDEN({ set, appInfo -> !set.contains(appInfo.app) }),
|
HIDDEN({ set, appInfo -> !set.contains(appInfo.getRawInfo()) }),
|
||||||
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }),
|
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.getRawInfo()) }),
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,33 +4,18 @@ import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.LauncherActivityInfo
|
import android.content.pm.LauncherActivityInfo
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.getUserFromId
|
import de.jrpie.android.launcher.getUserFromId
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an app installed on the users device.
|
* Represents an app installed on the users device.
|
||||||
* Contains the minimal amount of data required to identify the app.
|
* Contains the minimal amount of data required to identify the app.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) {
|
@SerialName("app")
|
||||||
|
data class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER): AbstractAppInfo {
|
||||||
fun serialize(): String {
|
|
||||||
return Json.encodeToString(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
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(
|
fun getLauncherActivityInfo(
|
||||||
context: Context
|
context: Context
|
||||||
|
@ -41,17 +26,4 @@ class AppInfo(val packageName: String, val activityName: String?, val user: Int
|
||||||
return activityList.firstOrNull { app -> app.name == activityName }
|
return activityList.firstOrNull { app -> app.name == activityName }
|
||||||
?: activityList.firstOrNull()
|
?: 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,20 +4,21 @@ import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.LauncherActivityInfo
|
import android.content.pm.LauncherActivityInfo
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
import android.os.UserHandle
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.actions.AppAction
|
||||||
|
import de.jrpie.android.launcher.getUserFromId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows.
|
* Stores information used to create [de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter] rows.
|
||||||
*/
|
*/
|
||||||
class DetailedAppInfo(
|
class DetailedAppInfo(
|
||||||
val app: AppInfo,
|
private val app: AppInfo,
|
||||||
val label: CharSequence,
|
private val label: CharSequence,
|
||||||
val icon: Drawable,
|
private val icon: Drawable,
|
||||||
val isPrivateSpaceApp: Boolean,
|
private val privateSpace: Boolean,
|
||||||
val isSystemApp: Boolean = false,
|
private val removable: Boolean = true,
|
||||||
) {
|
): AbstractDetailedAppInfo {
|
||||||
|
|
||||||
constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this(
|
constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this(
|
||||||
AppInfo(
|
AppInfo(
|
||||||
|
@ -28,29 +29,41 @@ class DetailedAppInfo(
|
||||||
activityInfo.label,
|
activityInfo.label,
|
||||||
activityInfo.getBadgedIcon(0),
|
activityInfo.getBadgedIcon(0),
|
||||||
private,
|
private,
|
||||||
activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0
|
// App can be uninstalled iff it is not a system app
|
||||||
|
activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getCustomLabel(context: Context): CharSequence {
|
|
||||||
val map = (context.applicationContext as? Application)?.getCustomAppNames() ?: return label
|
|
||||||
|
|
||||||
return map[app] ?: label
|
|
||||||
|
override fun getLabel(): String {
|
||||||
|
return label.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomLabel(label: CharSequence?) {
|
override fun getIcon(context: Context): Drawable {
|
||||||
|
return icon
|
||||||
Log.i("Launcher", "Setting custom label for ${this.app} to ${label}.")
|
|
||||||
val map = LauncherPreferences.apps().customNames() ?: HashMap<AppInfo, String>()
|
|
||||||
|
|
||||||
if (label.isNullOrEmpty()) {
|
|
||||||
map.remove(app)
|
|
||||||
} else {
|
|
||||||
map[app] = label.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
LauncherPreferences.apps().customNames(map)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRawInfo(): AppInfo {
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUser(context: Context): UserHandle {
|
||||||
|
return getUserFromId(app.user, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isPrivate(): Boolean {
|
||||||
|
return privateSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRemovable(): Boolean {
|
||||||
|
return removable
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAction(): Action {
|
||||||
|
return AppAction(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? {
|
fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? {
|
||||||
return appInfo.getLauncherActivityInfo(context)?.let {
|
return appInfo.getLauncherActivityInfo(context)?.let {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,10 +91,17 @@ fun isPrivateSpaceLocked(context: Context): Boolean {
|
||||||
val privateSpaceUser = getPrivateSpaceUser(context) ?: return false
|
val privateSpaceUser = getPrivateSpaceUser(context) ?: return false
|
||||||
return userManager.isQuietModeEnabled(privateSpaceUser)
|
return userManager.isQuietModeEnabled(privateSpaceUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lockPrivateSpace(context: Context, lock: Boolean) {
|
fun lockPrivateSpace(context: Context, lock: Boolean) {
|
||||||
if (!isPrivateSpaceSupported()) {
|
if (!isPrivateSpaceSupported()) {
|
||||||
return
|
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 userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||||
val privateSpaceUser = getPrivateSpaceUser(context) ?: return
|
val privateSpaceUser = getPrivateSpaceUser(context) ?: return
|
||||||
userManager.requestQuietModeEnabled(lock, privateSpaceUser)
|
userManager.requestQuietModeEnabled(lock, privateSpaceUser)
|
||||||
|
@ -116,3 +123,18 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.core.graphics.green
|
||||||
import androidx.core.graphics.red
|
import androidx.core.graphics.red
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
|
||||||
class ColorPreference(context: Context, attrs: AttributeSet?) :
|
class ColorPreference(context: Context, attrs: AttributeSet?) :
|
||||||
Preference(context, attrs) {
|
Preference(context, attrs) {
|
||||||
|
@ -52,7 +53,7 @@ class ColorPreference(context: Context, attrs: AttributeSet?) :
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
||||||
setView(R.layout.dialog_choose_color)
|
setView(R.layout.dialog_choose_color)
|
||||||
setTitle(R.string.dialog_choose_color_title)
|
setTitle(R.string.dialog_choose_color_title)
|
||||||
setPositiveButton(R.string.dialog_select_color_ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
persistInt(currentColor)
|
persistInt(currentColor)
|
||||||
summary = currentColor.getHex()
|
summary = currentColor.getHex()
|
||||||
}
|
}
|
||||||
|
@ -83,10 +84,10 @@ class ColorPreference(context: Context, attrs: AttributeSet?) :
|
||||||
override fun onTextChanged(text: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
override fun onTextChanged(text: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||||
override fun afterTextChanged(editable: Editable?) {
|
override fun afterTextChanged(editable: Editable?) {
|
||||||
preview.hasFocus() || return
|
preview.hasFocus() || return
|
||||||
val newText = editable?.toString()
|
val newText = editable?.toString() ?: return
|
||||||
newText.isNullOrBlank() && return
|
newText.isBlank() && return
|
||||||
try {
|
try {
|
||||||
val newColor = Color.parseColor(newText.toString())
|
val newColor = newText.toColorInt()
|
||||||
currentColor = newColor
|
currentColor = newColor
|
||||||
updateColor(false)
|
updateColor(false)
|
||||||
} catch (_: IllegalArgumentException) {
|
} catch (_: IllegalArgumentException) {
|
||||||
|
|
|
@ -5,8 +5,9 @@ import java.util.Set;
|
||||||
|
|
||||||
import de.jrpie.android.launcher.R;
|
import de.jrpie.android.launcher.R;
|
||||||
import de.jrpie.android.launcher.actions.lock.LockMethod;
|
import de.jrpie.android.launcher.actions.lock.LockMethod;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
||||||
|
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background;
|
import de.jrpie.android.launcher.preferences.theme.Background;
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Font;
|
import de.jrpie.android.launcher.preferences.theme.Font;
|
||||||
|
@ -20,20 +21,24 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
r = R.class,
|
r = R.class,
|
||||||
value = {
|
value = {
|
||||||
@PreferenceGroup(name = "internal", prefix = "settings_internal_", suffix = "_key", 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", type = boolean.class, defaultValue = "false"),
|
||||||
@Preference(name = "started_time", type = long.class),
|
@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"),
|
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
||||||
@Preference(name = "favorites", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
|
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||||
@Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
|
@Preference(name = "hidden", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||||
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class),
|
@Preference(name = "pinned_shortcuts", type = Set.class, serializer = SetPinnedShortcutInfoPreferenceSerializer.class),
|
||||||
|
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAbstractAppInfoStringPreferenceSerializer.class),
|
||||||
@Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"),
|
@Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"),
|
||||||
@Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"),
|
@Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"),
|
||||||
@Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"),
|
@Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"),
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = {
|
@PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = {
|
||||||
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT")
|
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT"),
|
||||||
|
@Preference(name = "reverse_layout", type = boolean.class, defaultValue = "false")
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = {
|
@PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = {
|
||||||
}),
|
}),
|
||||||
|
@ -59,7 +64,8 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "display", prefix = "settings_display_", suffix = "_key", value = {
|
@PreferenceGroup(name = "display", prefix = "settings_display_", suffix = "_key", value = {
|
||||||
@Preference(name = "screen_timeout_disabled", type = boolean.class, defaultValue = "false"),
|
@Preference(name = "screen_timeout_disabled", type = boolean.class, defaultValue = "false"),
|
||||||
@Preference(name = "full_screen", type = boolean.class, defaultValue = "true"),
|
@Preference(name = "hide_status_bar", type = boolean.class, defaultValue = "true"),
|
||||||
|
@Preference(name = "hide_navigation_bar", type = boolean.class, defaultValue = "false"),
|
||||||
@Preference(name = "rotate_screen", type = boolean.class, defaultValue = "true"),
|
@Preference(name = "rotate_screen", type = boolean.class, defaultValue = "true"),
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "functionality", prefix = "settings_functionality_", suffix = "_key", value = {
|
@PreferenceGroup(name = "functionality", prefix = "settings_functionality_", suffix = "_key", value = {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.jrpie.android.launcher.preferences
|
package de.jrpie.android.launcher.preferences
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.TypedValue
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -27,8 +28,10 @@ enum class ListLayout(
|
||||||
GRID(
|
GRID(
|
||||||
{ c ->
|
{ c ->
|
||||||
val displayMetrics = c.resources.displayMetrics
|
val displayMetrics = c.resources.displayMetrics
|
||||||
val widthSp = displayMetrics.widthPixels / displayMetrics.scaledDensity
|
val widthColumnPx =
|
||||||
GridLayoutManager(c, (widthSp / 90).toInt())
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 90f, displayMetrics)
|
||||||
|
val numColumns = (displayMetrics.widthPixels / widthColumnPx).toInt()
|
||||||
|
GridLayoutManager(c, numColumns)
|
||||||
},
|
},
|
||||||
R.layout.list_apps_row_variant_grid,
|
R.layout.list_apps_row_variant_grid,
|
||||||
false
|
false
|
||||||
|
|
|
@ -5,9 +5,12 @@ import android.util.Log
|
||||||
import de.jrpie.android.launcher.BuildConfig
|
import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
|
||||||
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||||
import de.jrpie.android.launcher.ui.HomeActivity
|
import de.jrpie.android.launcher.ui.HomeActivity
|
||||||
|
|
||||||
|
@ -15,7 +18,7 @@ import de.jrpie.android.launcher.ui.HomeActivity
|
||||||
* Increase when breaking changes are introduced and write an appropriate case in
|
* Increase when breaking changes are introduced and write an appropriate case in
|
||||||
* `migratePreferencesToNewVersion`
|
* `migratePreferencesToNewVersion`
|
||||||
*/
|
*/
|
||||||
const val PREFERENCE_VERSION = 3
|
const val PREFERENCE_VERSION = 4
|
||||||
const val UNKNOWN_PREFERENCE_VERSION = -1
|
const val UNKNOWN_PREFERENCE_VERSION = -1
|
||||||
private const val TAG = "Launcher - Preferences"
|
private const val TAG = "Launcher - Preferences"
|
||||||
|
|
||||||
|
@ -44,6 +47,10 @@ fun migratePreferencesToNewVersion(context: Context) {
|
||||||
migratePreferencesFromVersion2()
|
migratePreferencesFromVersion2()
|
||||||
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
|
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
|
||||||
}
|
}
|
||||||
|
3 -> {
|
||||||
|
migratePreferencesFromVersion3()
|
||||||
|
Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).")
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(
|
Log.w(
|
||||||
|
@ -66,16 +73,16 @@ fun resetPreferences(context: Context) {
|
||||||
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
||||||
|
|
||||||
|
|
||||||
val hidden: MutableSet<AppInfo> = mutableSetOf()
|
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
||||||
val launcher = DetailedAppInfo.fromAppInfo(
|
val launcher = DetailedAppInfo.fromAppInfo(
|
||||||
AppInfo(
|
AppInfo(
|
||||||
BuildConfig.APPLICATION_ID,
|
BuildConfig.APPLICATION_ID,
|
||||||
HomeActivity::class.java.name,
|
HomeActivity::class.java.name,
|
||||||
AppInfo.INVALID_USER
|
INVALID_USER
|
||||||
), context
|
), context
|
||||||
)
|
)
|
||||||
launcher?.app?.let { hidden.add(it) }
|
launcher?.getRawInfo()?.let { hidden.add(it) }
|
||||||
Log.i(TAG,"Hiding ${launcher?.app}")
|
Log.i(TAG,"Hiding ${launcher?.getRawInfo()}")
|
||||||
LauncherPreferences.apps().hidden(hidden)
|
LauncherPreferences.apps().hidden(hidden)
|
||||||
|
|
||||||
Action.resetToDefaultActions(context)
|
Action.resetToDefaultActions(context)
|
||||||
|
|
|
@ -5,14 +5,27 @@ import de.jrpie.android.launcher.actions.AppAction
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
||||||
import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Suppress("unused")
|
||||||
|
private class LegacyMapEntry(val key: AppInfo, val value: String)
|
||||||
|
|
||||||
|
private fun serializeMapAppInfo(value: Map<AppInfo, String>?): Set<String>? {
|
||||||
|
return value?.map { (key, value) ->
|
||||||
|
Json.encodeToString(LegacyMapEntry(key, value))
|
||||||
|
}?.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val oldLauncherActionIds: Map<String, LauncherAction> =
|
val oldLauncherActionIds: Map<String, LauncherAction> =
|
||||||
mapOf(
|
mapOf(
|
||||||
|
@ -77,7 +90,7 @@ private fun Action.Companion.legacyFromPreference(id: String): Action? {
|
||||||
|
|
||||||
private fun migrateAppInfoStringMap(key: String) {
|
private fun migrateAppInfoStringMap(key: String) {
|
||||||
val preferences = LauncherPreferences.getSharedPreferences()
|
val preferences = LauncherPreferences.getSharedPreferences()
|
||||||
MapAppInfoStringPreferenceSerializer().serialize(
|
serializeMapAppInfo(
|
||||||
preferences.getStringSet(key, setOf())?.mapNotNull { entry ->
|
preferences.getStringSet(key, setOf())?.mapNotNull { entry ->
|
||||||
try {
|
try {
|
||||||
val obj = JSONObject(entry)
|
val obj = JSONObject(entry)
|
||||||
|
@ -89,7 +102,7 @@ private fun migrateAppInfoStringMap(key: String) {
|
||||||
}
|
}
|
||||||
}?.toMap(HashMap())
|
}?.toMap(HashMap())
|
||||||
)?.let {
|
)?.let {
|
||||||
preferences.edit().putStringSet(key, it as Set<String>).apply()
|
preferences.edit { putStringSet(key, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,16 +111,16 @@ private fun migrateAppInfoSet(key: String) {
|
||||||
.map(AppInfo.Companion::legacyDeserialize)
|
.map(AppInfo.Companion::legacyDeserialize)
|
||||||
.map(AppInfo::serialize)
|
.map(AppInfo::serialize)
|
||||||
.toSet()
|
.toSet()
|
||||||
.let { LauncherPreferences.getSharedPreferences().edit().putStringSet(key, it).apply() }
|
.let { LauncherPreferences.getSharedPreferences().edit { putStringSet(key, it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateAction(key: String) {
|
private fun migrateAction(key: String) {
|
||||||
Action.legacyFromPreference(key)?.let { action ->
|
Action.legacyFromPreference(key)?.let { action ->
|
||||||
LauncherPreferences.getSharedPreferences().edit()
|
LauncherPreferences.getSharedPreferences().edit {
|
||||||
.putString(key, Json.encodeToString(action))
|
putString(key, Json.encodeToString(action))
|
||||||
.remove("$key.app")
|
.remove("$key.app")
|
||||||
.remove("$key.user")
|
.remove("$key.user")
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
||||||
* (see [PREFERENCE_VERSION])
|
* (see [PREFERENCE_VERSION])
|
||||||
*/
|
*/
|
||||||
fun migratePreferencesFromVersion2() {
|
fun migratePreferencesFromVersion2() {
|
||||||
assert(PREFERENCE_VERSION == 3)
|
|
||||||
assert(LauncherPreferences.internal().versionCode() == 2)
|
assert(LauncherPreferences.internal().versionCode() == 2)
|
||||||
// previously there was no setting for this
|
// previously there was no setting for this
|
||||||
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
|
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
|
||||||
LauncherPreferences.internal().versionCode(3)
|
LauncherPreferences.internal().versionCode(3)
|
||||||
|
migratePreferencesFromVersion3()
|
||||||
}
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
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<String>?): Set<AppInfo>? {
|
||||||
|
return value?.map {
|
||||||
|
Json.decodeFromString<AppInfo>(it)
|
||||||
|
}?.toHashSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deserializeMap(value: Set<String>?): HashMap<AppInfo, String>? {
|
||||||
|
return value?.associateTo(HashMap()) {
|
||||||
|
val entry = Json.decodeFromString<MapEntry>(it)
|
||||||
|
Pair(entry.key, entry.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private class MapEntry(val key: AppInfo, val value: String)
|
||||||
|
|
||||||
|
private fun migrateSetAppInfo(key: String, preferences: SharedPreferences, editor: Editor) {
|
||||||
|
try {
|
||||||
|
val serializer = SetAbstractAppInfoPreferenceSerializer()
|
||||||
|
val set = HashSet<AbstractAppInfo>()
|
||||||
|
|
||||||
|
deserializeSet(preferences.getStringSet(key, null))?.let {
|
||||||
|
set.addAll(it)
|
||||||
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
editor.putStringSet(
|
||||||
|
key,
|
||||||
|
serializer.serialize(set as java.util.Set<AbstractAppInfo>) as Set<String>?
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
editor.putStringSet(key, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences, editor: Editor ) {
|
||||||
|
try {
|
||||||
|
val serializer = MapAbstractAppInfoStringPreferenceSerializer()
|
||||||
|
val map = HashMap<AbstractAppInfo, String>()
|
||||||
|
|
||||||
|
deserializeMap(preferences.getStringSet(key, null))?.let {
|
||||||
|
map.putAll(it)
|
||||||
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
editor.putStringSet(key, serializer.serialize(map) as Set<String>?)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
editor.putStringSet(key, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun migratePreferencesFromVersion3() {
|
||||||
|
assert(PREFERENCE_VERSION == 4)
|
||||||
|
assert(LauncherPreferences.internal().versionCode() == 3)
|
||||||
|
|
||||||
|
val preferences = LauncherPreferences.getSharedPreferences()
|
||||||
|
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)
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import android.util.Log
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background
|
import de.jrpie.android.launcher.preferences.theme.Background
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
private fun migrateStringPreference(
|
private fun migrateStringPreference(
|
||||||
|
@ -64,318 +64,317 @@ fun migratePreferencesFromVersionUnknown(context: Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val newPrefs = LauncherPreferences.getSharedPreferences().edit()
|
LauncherPreferences.getSharedPreferences().edit {
|
||||||
|
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"startedBefore",
|
"startedBefore",
|
||||||
"internal.started_before",
|
"internal.started_before",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_volumeUpApp",
|
"action_volumeUpApp",
|
||||||
"action.volume_up.app",
|
"action.volume_up.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_volumeUpApp_user",
|
"action_volumeUpApp_user",
|
||||||
"action.volume_up.user",
|
"action.volume_up.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_volumeDownApp",
|
"action_volumeDownApp",
|
||||||
"action.volume_down.app",
|
"action.volume_down.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_volumeDownApp_user",
|
"action_volumeDownApp_user",
|
||||||
"action.volume_down.user",
|
"action.volume_down.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_timeApp", "action.time.app", "")
|
migrateStringPreference(oldPrefs, this, "action_timeApp", "action.time.app", "")
|
||||||
migrateIntPreference(oldPrefs, newPrefs, "action_timeApp_user", "action.time.user", -1)
|
migrateIntPreference(oldPrefs, this, "action_timeApp_user", "action.time.user", -1)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_dateApp", "action.date.app", "")
|
migrateStringPreference(oldPrefs, this, "action_dateApp", "action.date.app", "")
|
||||||
migrateIntPreference(oldPrefs, newPrefs, "action_dateApp_user", "action.date.user", -1)
|
migrateIntPreference(oldPrefs, this, "action_dateApp_user", "action.date.user", -1)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_longClickApp",
|
"action_longClickApp",
|
||||||
"action.long_click.app",
|
"action.long_click.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_longClickApp_user",
|
"action_longClickApp_user",
|
||||||
"action.long_click.user",
|
"action.long_click.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleClickApp",
|
"action_doubleClickApp",
|
||||||
"action.double_click.app",
|
"action.double_click.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleClickApp_user",
|
"action_doubleClickApp_user",
|
||||||
"action.double_click.user",
|
"action.double_click.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_upApp", "action.up.app", "")
|
migrateStringPreference(oldPrefs, this, "action_upApp", "action.up.app", "")
|
||||||
migrateIntPreference(oldPrefs, newPrefs, "action_upApp_user", "action.up.user", -1)
|
migrateIntPreference(oldPrefs, this, "action_upApp_user", "action.up.user", -1)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_up_leftApp",
|
"action_up_leftApp",
|
||||||
"action.up_left.app",
|
"action.up_left.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_up_leftApp_user",
|
"action_up_leftApp_user",
|
||||||
"action.up_left.user",
|
"action.up_left.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_up_rightApp",
|
"action_up_rightApp",
|
||||||
"action.up_right.app",
|
"action.up_right.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_up_rightApp_user",
|
"action_up_rightApp_user",
|
||||||
"action.up_right.user",
|
"action.up_right.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleUpApp",
|
"action_doubleUpApp",
|
||||||
"action.double_up.app",
|
"action.double_up.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleUpApp_user",
|
"action_doubleUpApp_user",
|
||||||
"action.double_up.user",
|
"action.double_up.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_downApp", "action.down.app", "")
|
migrateStringPreference(oldPrefs, this, "action_downApp", "action.down.app", "")
|
||||||
migrateIntPreference(oldPrefs, newPrefs, "action_downApp_user", "action.down.user", -1)
|
migrateIntPreference(oldPrefs, this, "action_downApp_user", "action.down.user", -1)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_down_leftApp",
|
"action_down_leftApp",
|
||||||
"action.down_left.app",
|
"action.down_left.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_down_leftApp_user",
|
"action_down_leftApp_user",
|
||||||
"action.down_left.user",
|
"action.down_left.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_down_rightApp",
|
"action_down_rightApp",
|
||||||
"action.down_right.app",
|
"action.down_right.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_down_rightApp_user",
|
"action_down_rightApp_user",
|
||||||
"action.down_right.user",
|
"action.down_right.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleDownApp",
|
"action_doubleDownApp",
|
||||||
"action.double_down.app",
|
"action.double_down.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleDownApp_user",
|
"action_doubleDownApp_user",
|
||||||
"action.double_down.user",
|
"action.double_down.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_leftApp", "action.left.app", "")
|
migrateStringPreference(oldPrefs, this, "action_leftApp", "action.left.app", "")
|
||||||
migrateIntPreference(oldPrefs, newPrefs, "action_leftApp_user", "action.left.user", -1)
|
migrateIntPreference(oldPrefs, this, "action_leftApp_user", "action.left.user", -1)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_left_topApp",
|
"action_left_topApp",
|
||||||
"action.left_top.app",
|
"action.left_top.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_left_topApp_user",
|
"action_left_topApp_user",
|
||||||
"action.left_top.user",
|
"action.left_top.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_left_bottomApp",
|
"action_left_bottomApp",
|
||||||
"action.left_bottom.app",
|
"action.left_bottom.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_left_bottomApp_user",
|
"action_left_bottomApp_user",
|
||||||
"action.left_bottom.user",
|
"action.left_bottom.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleLeftApp",
|
"action_doubleLeftApp",
|
||||||
"action.double_left.app",
|
"action.double_left.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleLeftApp_user",
|
"action_doubleLeftApp_user",
|
||||||
"action.double_left.user",
|
"action.double_left.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(oldPrefs, newPrefs, "action_rightApp", "action.right.app", "")
|
migrateStringPreference(oldPrefs, this, "action_rightApp", "action.right.app", "")
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_rightApp_user",
|
"action_rightApp_user",
|
||||||
"action.right.user",
|
"action.right.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_right_topApp",
|
"action_right_topApp",
|
||||||
"action.right_top.app",
|
"action.right_top.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_right_topApp_user",
|
"action_right_topApp_user",
|
||||||
"action.right_top.user",
|
"action.right_top.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_right_bottomApp",
|
"action_right_bottomApp",
|
||||||
"action.right_bottom.app",
|
"action.right_bottom.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_right_bottomApp_user",
|
"action_right_bottomApp_user",
|
||||||
"action.right_bottom.user",
|
"action.right_bottom.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateStringPreference(
|
migrateStringPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleRightApp",
|
"action_doubleRightApp",
|
||||||
"action.double_right.app",
|
"action.double_right.app",
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
migrateIntPreference(
|
migrateIntPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"action_doubleRightApp_user",
|
"action_doubleRightApp_user",
|
||||||
"action.double_right.user",
|
"action.double_right.user",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(oldPrefs, newPrefs, "timeVisible", "clock.time_visible", true)
|
migrateBooleanPreference(oldPrefs, this, "timeVisible", "clock.time_visible", true)
|
||||||
migrateBooleanPreference(oldPrefs, newPrefs, "dateVisible", "clock.date_visible", true)
|
migrateBooleanPreference(oldPrefs, this, "dateVisible", "clock.date_visible", true)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"dateLocalized",
|
"dateLocalized",
|
||||||
"clock.date_localized",
|
"clock.date_localized",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"dateTimeFlip",
|
"dateTimeFlip",
|
||||||
"clock.date_time_flip",
|
"clock.date_time_flip",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"disableTimeout",
|
"disableTimeout",
|
||||||
"display.disable_timeout",
|
"display.disable_timeout",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"useFullScreen",
|
"useFullScreen",
|
||||||
"display.use_full_screen",
|
"display.use_full_screen",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"enableDoubleActions",
|
"enableDoubleActions",
|
||||||
"enabled_gestures.double_actions",
|
"enabled_gestures.double_actions",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"enableEdgeActions",
|
"enableEdgeActions",
|
||||||
"enabled_gestures.edge_actions",
|
"enabled_gestures.edge_actions",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"searchAutoLaunch",
|
"searchAutoLaunch",
|
||||||
"functionality.search_auto_launch",
|
"functionality.search_auto_launch",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
migrateBooleanPreference(
|
migrateBooleanPreference(
|
||||||
oldPrefs,
|
oldPrefs,
|
||||||
newPrefs,
|
this,
|
||||||
"searchAutoKeyboard",
|
"searchAutoKeyboard",
|
||||||
"functionality.search_auto_keyboard",
|
"functionality.search_auto_keyboard",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
}
|
||||||
newPrefs.apply()
|
|
||||||
|
|
||||||
when (oldPrefs.getString("theme", "finn")) {
|
when (oldPrefs.getString("theme", "finn")) {
|
||||||
"finn" -> {
|
"finn" -> {
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
package de.jrpie.android.launcher.preferences.serialization
|
package de.jrpie.android.launcher.preferences.serialization
|
||||||
|
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -10,40 +11,61 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
|
||||||
// Serializers for [LauncherPreference$Config]
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class SetAppInfoPreferenceSerializer :
|
class SetAbstractAppInfoPreferenceSerializer :
|
||||||
PreferenceSerializer<java.util.Set<AppInfo>?, java.util.Set<java.lang.String>?> {
|
PreferenceSerializer<java.util.Set<AbstractAppInfo>?, java.util.Set<java.lang.String>?> {
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun serialize(value: java.util.Set<AppInfo>?): java.util.Set<java.lang.String> {
|
override fun serialize(value: java.util.Set<AbstractAppInfo>?): java.util.Set<java.lang.String> {
|
||||||
return value?.map(AppInfo::serialize)?.toHashSet() as java.util.Set<java.lang.String>
|
return value?.map(AbstractAppInfo::serialize)
|
||||||
|
?.toHashSet() as java.util.Set<java.lang.String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<AppInfo>? {
|
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<AbstractAppInfo>? {
|
||||||
return value?.map (java.lang.String::toString)?.map(AppInfo::deserialize)?.toHashSet() as? java.util.Set<AppInfo>
|
return value?.map(java.lang.String::toString)?.map(AbstractAppInfo::deserialize)
|
||||||
|
?.toHashSet() as? java.util.Set<AbstractAppInfo>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class MapAppInfoStringPreferenceSerializer :
|
class SetPinnedShortcutInfoPreferenceSerializer :
|
||||||
PreferenceSerializer<java.util.HashMap<AppInfo, String>?, java.util.Set<java.lang.String>?> {
|
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
||||||
|
@Throws(PreferenceSerializationException::class)
|
||||||
@Serializable
|
override fun serialize(value: java.util.Set<PinnedShortcutInfo>?): java.util.Set<java.lang.String> {
|
||||||
private class MapEntry(val key: AppInfo, val value: String)
|
return value?.map { Json.encodeToString<PinnedShortcutInfo>(it) }
|
||||||
|
?.toHashSet() as java.util.Set<java.lang.String>
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun serialize(value: java.util.HashMap<AppInfo, String>?): java.util.Set<java.lang.String>? {
|
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<PinnedShortcutInfo>? {
|
||||||
|
return value?.map(java.lang.String::toString)
|
||||||
|
?.map { Json.decodeFromString<PinnedShortcutInfo>(it) }
|
||||||
|
?.toHashSet() as? java.util.Set<PinnedShortcutInfo>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class MapAbstractAppInfoStringPreferenceSerializer :
|
||||||
|
PreferenceSerializer<java.util.HashMap<AbstractAppInfo, String>?, java.util.Set<java.lang.String>?> {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private class MapEntry(val key: AbstractAppInfo, val value: String)
|
||||||
|
|
||||||
|
@Throws(PreferenceSerializationException::class)
|
||||||
|
override fun serialize(value: java.util.HashMap<AbstractAppInfo, String>?): java.util.Set<java.lang.String>? {
|
||||||
return value?.map { (key, value) ->
|
return value?.map { (key, value) ->
|
||||||
Json.encodeToString(MapEntry(key, value))
|
Json.encodeToString(MapEntry(key, value))
|
||||||
}?.toHashSet() as? java.util.Set<java.lang.String>
|
}?.toHashSet() as? java.util.Set<java.lang.String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.HashMap<AppInfo, String>? {
|
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.HashMap<AbstractAppInfo, String>? {
|
||||||
return value?.associateTo(HashMap()) {
|
return value?.associateTo(HashMap()) {
|
||||||
val entry = Json.decodeFromString<MapEntry>(it.toString())
|
val entry = Json.decodeFromString<MapEntry>(it.toString())
|
||||||
Pair(entry.key, entry.value)
|
Pair(entry.key, entry.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.res.Resources
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
enum class ColorTheme(
|
enum class ColorTheme(
|
||||||
private val id: Int,
|
private val id: Int,
|
||||||
private val labelResource: Int,
|
private val labelResource: Int,
|
||||||
|
|
|
@ -27,10 +27,14 @@ fun View.blink(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Taken from: https://stackoverflow.com/a/30340794/12787264
|
// Taken from: https://stackoverflow.com/a/30340794/12787264
|
||||||
fun ImageView.transformGrayscale() {
|
fun ImageView.transformGrayscale(grayscale: Boolean) {
|
||||||
this.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
|
this.colorFilter = if (grayscale) {
|
||||||
setSaturation(0f)
|
ColorMatrixColorFilter(ColorMatrix().apply {
|
||||||
})
|
setSaturation(0f)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package de.jrpie.android.launcher.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -17,6 +17,7 @@ import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.databinding.HomeBinding
|
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.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -36,7 +37,7 @@ import java.util.Locale
|
||||||
class HomeActivity : UIObject, AppCompatActivity() {
|
class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: HomeBinding
|
private lateinit var binding: HomeBinding
|
||||||
private lateinit var touchGestureDetector: TouchGestureDetector
|
private var touchGestureDetector: TouchGestureDetector? = null
|
||||||
|
|
||||||
private var sharedPreferencesListener =
|
private var sharedPreferencesListener =
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
||||||
|
@ -56,23 +57,10 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
super<UIObject>.onCreate()
|
super<UIObject>.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
|
// Initialise layout
|
||||||
binding = HomeBinding.inflate(layoutInflater)
|
binding = HomeBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
|
||||||
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
// Handle back key / gesture on Android 13+, cf. onKeyDown()
|
// Handle back key / gesture on Android 13+, cf. onKeyDown()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
@ -85,8 +73,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
binding.buttonFallbackSettings.setOnClickListener {
|
binding.buttonFallbackSettings.setOnClickListener {
|
||||||
LauncherAction.SETTINGS.invoke(this)
|
LauncherAction.SETTINGS.invoke(this)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
touchGestureDetector?.updateScreenSize(windowManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -94,11 +85,25 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
|
|
||||||
super<UIObject>.onStart()
|
super<UIObject>.onStart()
|
||||||
|
|
||||||
|
// If the tutorial was not finished, start it
|
||||||
|
if (!LauncherPreferences.internal().started()) {
|
||||||
|
openTutorial(this)
|
||||||
|
}
|
||||||
|
|
||||||
LauncherPreferences.getSharedPreferences()
|
LauncherPreferences.getSharedPreferences()
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
super.onWindowFocusChanged(hasFocus)
|
||||||
|
|
||||||
|
if (hasFocus && LauncherPreferences.display().hideNavigationBar()) {
|
||||||
|
hideNavigationBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun updateSettingsFallbackButtonVisibility() {
|
private fun updateSettingsFallbackButtonVisibility() {
|
||||||
// If µLauncher settings can not be reached from any action bound to an enabled gesture,
|
// If µLauncher settings can not be reached from any action bound to an enabled gesture,
|
||||||
// show the fallback button.
|
// show the fallback button.
|
||||||
|
@ -166,8 +171,28 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
touchGestureDetector.edgeWidth =
|
/* 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,
|
||||||
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
|
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()
|
initClock()
|
||||||
updateSettingsFallbackButtonVisibility()
|
updateSettingsFallbackButtonVisibility()
|
||||||
|
@ -186,6 +211,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
// Only used pre Android 13, cf. onBackInvokedDispatcher
|
// Only used pre Android 13, cf. onBackInvokedDispatcher
|
||||||
handleBack()
|
handleBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (Action.forGesture(Gesture.VOLUME_UP) == LauncherAction.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
|
// Let the OS handle the key event. This works better with some custom ROMs
|
||||||
|
@ -207,7 +233,8 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
return touchGestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
|
touchGestureDetector?.onTouchEvent(event)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setOnClicks() {
|
override fun setOnClicks() {
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
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<AppCompatActivity>.onCreate(savedInstanceState)
|
||||||
|
super<UIObject>.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<RecyclerView>(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<AppCompatActivity>.onStart()
|
||||||
|
super<UIObject>.onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
super.onDestroy()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(binding.pinShortcutSwitchVisible.isChecked) {
|
||||||
|
if(!isBound) {
|
||||||
|
request?.accept()
|
||||||
|
}
|
||||||
|
request?.shortcutInfo?.let {
|
||||||
|
val set = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf()
|
||||||
|
set.add(PinnedShortcutInfo(it))
|
||||||
|
LauncherPreferences.apps().pinnedShortcuts(set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTheme(): Resources.Theme {
|
||||||
|
return modifyTheme(super.getTheme())
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class GestureRecyclerAdapter(val context: Context, val onClick: (Gesture) -> Unit): RecyclerView.Adapter<GestureRecyclerAdapter.ViewHolder>() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,15 @@
|
||||||
package de.jrpie.android.launcher.ui
|
package de.jrpie.android.launcher.ui
|
||||||
|
|
||||||
import android.content.Context
|
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.MotionEvent
|
||||||
import android.view.ViewConfiguration
|
import android.view.ViewConfiguration
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
@ -12,8 +19,8 @@ import kotlin.math.tan
|
||||||
|
|
||||||
class TouchGestureDetector(
|
class TouchGestureDetector(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
val width: Int,
|
var width: Int,
|
||||||
val height: Int,
|
var height: Int,
|
||||||
var edgeWidth: Float
|
var edgeWidth: Float
|
||||||
) {
|
) {
|
||||||
private val ANGULAR_THRESHOLD = tan(Math.PI / 6)
|
private val ANGULAR_THRESHOLD = tan(Math.PI / 6)
|
||||||
|
@ -27,20 +34,31 @@ class TouchGestureDetector(
|
||||||
|
|
||||||
private val MIN_TRIANGLE_HEIGHT = 250
|
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) {
|
data class Vector(val x: Float, val y: Float) {
|
||||||
fun absSquared(): Float {
|
fun absSquared(): Float {
|
||||||
return this.x * this.x + this.y * this.y
|
return this.x * this.x + this.y * this.y
|
||||||
}
|
}
|
||||||
|
|
||||||
fun plus(vector: Vector): Vector {
|
fun plus(vector: Vector): Vector {
|
||||||
return Vector(this.x + vector.x, this.y + vector.y)
|
return Vector(this.x + vector.x, this.y + vector.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun max(other: Vector): Vector {
|
fun max(other: Vector): Vector {
|
||||||
return Vector(max(this.x, other.x), max(this.y, other.y))
|
return Vector(max(this.x, other.x), max(this.y, other.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun min(other: Vector): Vector {
|
fun min(other: Vector): Vector {
|
||||||
return Vector(min(this.x, other.x), min(this.y, other.y))
|
return Vector(min(this.x, other.x), min(this.y, other.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun minus(vector: Vector): Vector {
|
operator fun minus(vector: Vector): Vector {
|
||||||
return Vector(this.x - vector.x, this.y - vector.y)
|
return Vector(this.x - vector.x, this.y - vector.y)
|
||||||
}
|
}
|
||||||
|
@ -57,16 +75,35 @@ class TouchGestureDetector(
|
||||||
fun sizeSquared(): Float {
|
fun sizeSquared(): Float {
|
||||||
return (max - min).absSquared()
|
return (max - min).absSquared()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDirection(): Vector {
|
fun getDirection(): Vector {
|
||||||
return last - start
|
return last - start
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(vector: Vector) {
|
fun update(vector: Vector) {
|
||||||
min = min.min(vector)
|
min = min.min(vector)
|
||||||
max = max.max(vector)
|
max = max.max(vector)
|
||||||
last = 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 {
|
private fun PointerPath.isTap(): Boolean {
|
||||||
|
if (intersectsSystemGestureInsets()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return sizeSquared() < TOUCH_SLOP_SQUARE
|
return sizeSquared() < TOUCH_SLOP_SQUARE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,20 +121,48 @@ class TouchGestureDetector(
|
||||||
|
|
||||||
private var paths = HashMap<Int, PointerPath>()
|
private var paths = HashMap<Int, PointerPath>()
|
||||||
|
|
||||||
|
/* 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 lastTappedTime = 0L
|
||||||
private var lastTappedLocation: Vector? = null
|
private var lastTappedLocation: Vector? = null
|
||||||
|
|
||||||
fun onTouchEvent(event: MotionEvent): Boolean {
|
fun onTouchEvent(event: MotionEvent) {
|
||||||
|
|
||||||
|
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||||
|
synchronized(this@TouchGestureDetector) {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val pointerIdToIndex =
|
val pointerIdToIndex =
|
||||||
(0..<event.pointerCount).associateBy { event.getPointerId(it) }
|
(0..<event.pointerCount).associateBy { event.getPointerId(it) }
|
||||||
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
paths = HashMap()
|
synchronized(this@TouchGestureDetector) {
|
||||||
|
paths = HashMap()
|
||||||
|
cancelled = false
|
||||||
|
}
|
||||||
|
longPressHandler.postDelayed({
|
||||||
|
synchronized(this@TouchGestureDetector) {
|
||||||
|
if (cancelled) {
|
||||||
|
return@postDelayed
|
||||||
|
}
|
||||||
|
if (paths.entries.size == 1 && paths.entries.firstOrNull()?.value?.isTap() == true) {
|
||||||
|
cancelled = true
|
||||||
|
Gesture.LONG_CLICK.invoke(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, LONG_PRESS_TIMEOUT.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new pointers
|
// add new pointers
|
||||||
for(i in 0..<event.pointerCount){
|
for (i in 0..<event.pointerCount) {
|
||||||
if(paths.containsKey(event.getPointerId(i))) {
|
if (paths.containsKey(event.getPointerId(i))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val index = pointerIdToIndex[i] ?: continue
|
val index = pointerIdToIndex[i] ?: continue
|
||||||
|
@ -122,9 +187,17 @@ class TouchGestureDetector(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
||||||
|
synchronized(this@TouchGestureDetector) {
|
||||||
|
// if the long press handler is still running, kill it
|
||||||
|
longPressHandler.removeCallbacksAndMessages(null)
|
||||||
|
// if the gesture was already detected as a long click, there is nothing to do
|
||||||
|
if (cancelled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
classifyPaths(paths, event.downTime, event.eventTime)
|
classifyPaths(paths, event.downTime, event.eventTime)
|
||||||
}
|
}
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGestureForDirection(direction: Vector): Gesture? {
|
private fun getGestureForDirection(direction: Vector): Gesture? {
|
||||||
|
@ -152,9 +225,8 @@ class TouchGestureDetector(
|
||||||
|
|
||||||
val mainPointerPath = paths.entries.firstOrNull { it.value.number == 0 }?.value ?: return
|
val mainPointerPath = paths.entries.firstOrNull { it.value.number == 0 }?.value ?: return
|
||||||
|
|
||||||
// Ignore swipes at the very top, since this interferes with the status bar.
|
// Ignore swipes starting at the very top and the very bottom
|
||||||
// TODO: replace 100px by sensible dp value (e.g. twice the height of the status bar)
|
if (paths.entries.any { it.value.startIntersectsSystemGestureInsets() }) {
|
||||||
if (paths.entries.any { it.value.start.y < 100 }) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,17 +236,14 @@ class TouchGestureDetector(
|
||||||
if (duration in 0..TAP_TIMEOUT) {
|
if (duration in 0..TAP_TIMEOUT) {
|
||||||
if (timeStart - lastTappedTime < DOUBLE_TAP_TIMEOUT &&
|
if (timeStart - lastTappedTime < DOUBLE_TAP_TIMEOUT &&
|
||||||
lastTappedLocation?.let {
|
lastTappedLocation?.let {
|
||||||
(mainPointerPath.last - it).absSquared() < DOUBLE_TAP_SLOP_SQUARE} == true
|
(mainPointerPath.last - it).absSquared() < DOUBLE_TAP_SLOP_SQUARE
|
||||||
|
} == true
|
||||||
) {
|
) {
|
||||||
Gesture.DOUBLE_CLICK.invoke(context)
|
Gesture.DOUBLE_CLICK.invoke(context)
|
||||||
} else {
|
} else {
|
||||||
lastTappedTime = timeEnd
|
lastTappedTime = timeEnd
|
||||||
lastTappedLocation = mainPointerPath.last
|
lastTappedLocation = mainPointerPath.last
|
||||||
}
|
}
|
||||||
} else if (duration > 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 {
|
} else {
|
||||||
// detect swipes
|
// detect swipes
|
||||||
|
@ -197,34 +266,38 @@ class TouchGestureDetector(
|
||||||
val startEndMax = mainPointerPath.start.max(mainPointerPath.last)
|
val startEndMax = mainPointerPath.start.max(mainPointerPath.last)
|
||||||
when (gesture) {
|
when (gesture) {
|
||||||
Gesture.SWIPE_DOWN -> {
|
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
|
gesture = Gesture.SWIPE_LARGER
|
||||||
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
||||||
gesture = Gesture.SWIPE_SMALLER
|
gesture = Gesture.SWIPE_SMALLER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gesture.SWIPE_UP -> {
|
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
|
gesture = Gesture.SWIPE_LARGER_REVERSE
|
||||||
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
||||||
gesture = Gesture.SWIPE_SMALLER_REVERSE
|
gesture = Gesture.SWIPE_SMALLER_REVERSE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gesture.SWIPE_RIGHT -> {
|
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
|
gesture = Gesture.SWIPE_V
|
||||||
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
||||||
gesture = Gesture.SWIPE_LAMBDA
|
gesture = Gesture.SWIPE_LAMBDA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gesture.SWIPE_LEFT -> {
|
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
|
gesture = Gesture.SWIPE_V_REVERSE
|
||||||
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
||||||
gesture = Gesture.SWIPE_LAMBDA_REVERSE
|
gesture = Gesture.SWIPE_LAMBDA_REVERSE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> { }
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edgeActions) {
|
if (edgeActions) {
|
||||||
|
@ -240,7 +313,27 @@ class TouchGestureDetector(
|
||||||
gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM)
|
gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeStart - lastTappedTime < 2 * DOUBLE_TAP_TIMEOUT) {
|
||||||
|
gesture = gesture?.getTapComboVariant()
|
||||||
|
}
|
||||||
gesture?.invoke(context)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,11 @@ package de.jrpie.android.launcher.ui
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
|
||||||
|
@ -11,10 +15,12 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
* An interface implemented by every [Activity], Fragment etc. in Launcher.
|
* 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.
|
* 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) {
|
fun setWindowFlags(window: Window, homeScreen: Boolean) {
|
||||||
window.setFlags(0, 0) // clear flags
|
window.setFlags(0, 0) // clear flags
|
||||||
|
|
||||||
// Display notification bar
|
// Display notification bar
|
||||||
if (LauncherPreferences.display().fullScreen())
|
if (LauncherPreferences.display().hideStatusBar())
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||||
|
@ -36,17 +42,19 @@ fun setWindowFlags(window: Window, homeScreen: Boolean) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface UIObject {
|
interface UIObject {
|
||||||
fun onCreate() {
|
fun onCreate() {
|
||||||
if (this is Activity) {
|
if (this !is Activity) {
|
||||||
setWindowFlags(window, isHomeScreen())
|
return
|
||||||
|
}
|
||||||
if (!LauncherPreferences.display().rotateScreen()) {
|
setWindowFlags(window, isHomeScreen())
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!LauncherPreferences.display().rotateScreen()) {
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStart() {
|
fun onStart() {
|
||||||
setOnClicks()
|
setOnClicks()
|
||||||
adjustLayout()
|
adjustLayout()
|
||||||
|
@ -70,4 +78,26 @@ interface UIObject {
|
||||||
fun isHomeScreen(): Boolean {
|
fun isHomeScreen(): Boolean {
|
||||||
return false
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,27 +1,20 @@
|
||||||
package de.jrpie.android.launcher.ui.list
|
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.content.res.Resources
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import android.window.OnBackInvokedDispatcher
|
import android.window.OnBackInvokedDispatcher
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
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.Application
|
||||||
import de.jrpie.android.launcher.R
|
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.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
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.isPrivateSpaceLocked
|
||||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSetUp
|
import de.jrpie.android.launcher.apps.isPrivateSpaceSetUp
|
||||||
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
||||||
|
@ -32,14 +25,6 @@ import de.jrpie.android.launcher.ui.list.apps.ListFragmentApps
|
||||||
import de.jrpie.android.launcher.ui.list.other.ListFragmentOther
|
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:
|
* The [ListActivity] is the most general purpose activity in Launcher:
|
||||||
* - used to view all apps and edit their settings
|
* - used to view all apps and edit their settings
|
||||||
|
@ -49,9 +34,34 @@ var forGesture: String? = null
|
||||||
*/
|
*/
|
||||||
class ListActivity : AppCompatActivity(), UIObject {
|
class ListActivity : AppCompatActivity(), UIObject {
|
||||||
private lateinit var binding: ListBinding
|
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) {
|
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(
|
binding.listLock.setImageDrawable(
|
||||||
AppCompatResources.getDrawable(
|
AppCompatResources.getDrawable(
|
||||||
this,
|
this,
|
||||||
|
@ -74,7 +84,6 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum class ListActivityIntention(val titleResource: Int) {
|
enum class ListActivityIntention(val titleResource: Int) {
|
||||||
VIEW(R.string.list_title_view), /* view list of apps */
|
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 */
|
PICK(R.string.list_title_pick) /* choose app or action to associate to a gesture */
|
||||||
|
@ -99,10 +108,13 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
?.let { ListActivityIntention.valueOf(it) }
|
?.let { ListActivityIntention.valueOf(it) }
|
||||||
?: ListActivityIntention.VIEW
|
?: ListActivityIntention.VIEW
|
||||||
|
|
||||||
|
@Suppress("deprecation") // required to support API level < 33
|
||||||
favoritesVisibility = bundle.getSerializable("favoritesVisibility")
|
favoritesVisibility = bundle.getSerializable("favoritesVisibility")
|
||||||
as? AppFilter.Companion.AppSetVisibility ?: favoritesVisibility
|
as? AppFilter.Companion.AppSetVisibility ?: favoritesVisibility
|
||||||
|
@Suppress("deprecation") // required to support API level < 33
|
||||||
privateSpaceVisibility = bundle.getSerializable("privateSpaceVisibility")
|
privateSpaceVisibility = bundle.getSerializable("privateSpaceVisibility")
|
||||||
as? AppFilter.Companion.AppSetVisibility ?: privateSpaceVisibility
|
as? AppFilter.Companion.AppSetVisibility ?: privateSpaceVisibility
|
||||||
|
@Suppress("deprecation") // required to support API level < 33
|
||||||
hiddenVisibility = bundle.getSerializable("hiddenVisibility")
|
hiddenVisibility = bundle.getSerializable("hiddenVisibility")
|
||||||
as? AppFilter.Companion.AppSetVisibility ?: hiddenVisibility
|
as? AppFilter.Companion.AppSetVisibility ?: hiddenVisibility
|
||||||
|
|
||||||
|
@ -119,20 +131,6 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
LauncherAction.SETTINGS.launch(this@ListActivity)
|
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) {
|
if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||||
isPrivateSpaceSetUp(this, showToast = true, launchSettings = true)
|
isPrivateSpaceSetUp(this, showToast = true, launchSettings = true)
|
||||||
|
@ -155,7 +153,7 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
binding.listContainer.context.resources.displayMetrics.heightPixels
|
binding.listContainer.context.resources.displayMetrics.heightPixels
|
||||||
val diff = height - r.bottom
|
val diff = height - r.bottom
|
||||||
if (diff != 0 &&
|
if (diff != 0 &&
|
||||||
LauncherPreferences.display().fullScreen()
|
LauncherPreferences.display().hideStatusBar()
|
||||||
) {
|
) {
|
||||||
if (binding.listContainer.paddingBottom != diff) {
|
if (binding.listContainer.paddingBottom != diff) {
|
||||||
binding.listContainer.setPadding(0, 0, 0, diff)
|
binding.listContainer.setPadding(0, 0, 0, diff)
|
||||||
|
@ -183,32 +181,19 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
finish()
|
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() {
|
fun updateTitle() {
|
||||||
var titleResource = intention.titleResource
|
var titleResource = intention.titleResource
|
||||||
if (intention == ListActivityIntention.VIEW) {
|
if (intention == ListActivityIntention.VIEW) {
|
||||||
titleResource = if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
titleResource =
|
||||||
R.string.list_title_hidden
|
if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||||
} else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
R.string.list_title_hidden
|
||||||
R.string.list_title_private_space
|
} else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||||
} else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
R.string.list_title_private_space
|
||||||
R.string.list_title_favorite
|
} else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||||
} else {
|
R.string.list_title_favorite
|
||||||
R.string.list_title_view
|
} else {
|
||||||
}
|
R.string.list_title_view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.listHeading.text = getString(titleResource)
|
binding.listHeading.text = getString(titleResource)
|
||||||
|
@ -238,11 +223,11 @@ class ListActivity : AppCompatActivity(), UIObject {
|
||||||
|
|
||||||
updateTitle()
|
updateTitle()
|
||||||
|
|
||||||
val sectionsPagerAdapter = ListSectionsPagerAdapter(this, supportFragmentManager)
|
val sectionsPagerAdapter = ListSectionsPagerAdapter(this)
|
||||||
val viewPager: ViewPager = findViewById(R.id.list_viewpager)
|
binding.listViewpager.let {
|
||||||
viewPager.adapter = sectionsPagerAdapter
|
it.adapter = sectionsPagerAdapter
|
||||||
val tabs: TabLayout = findViewById(R.id.list_tabs)
|
binding.listTabs.setupWithViewPager(it)
|
||||||
tabs.setupWithViewPager(viewPager)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,9 +239,15 @@ private val TAB_TITLES = arrayOf(
|
||||||
/**
|
/**
|
||||||
* The [ListSectionsPagerAdapter] returns the fragment,
|
* The [ListSectionsPagerAdapter] returns the fragment,
|
||||||
* which corresponds to the selected tab in [ListActivity].
|
* 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
|
||||||
*/
|
*/
|
||||||
class ListSectionsPagerAdapter(private val context: Context, fm: FragmentManager) :
|
@Suppress("deprecation")
|
||||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
class ListSectionsPagerAdapter(private val activity: ListActivity) :
|
||||||
|
FragmentPagerAdapter(activity.supportFragmentManager) {
|
||||||
|
|
||||||
override fun getItem(position: Int): Fragment {
|
override fun getItem(position: Int): Fragment {
|
||||||
return when (position) {
|
return when (position) {
|
||||||
|
@ -267,11 +258,11 @@ class ListSectionsPagerAdapter(private val context: Context, fm: FragmentManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence {
|
override fun getPageTitle(position: Int): CharSequence {
|
||||||
return context.resources.getString(TAB_TITLES[position])
|
return activity.resources.getString(TAB_TITLES[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return when (intention) {
|
return when (activity.intention) {
|
||||||
ListActivity.ListActivityIntention.VIEW -> 1
|
ListActivity.ListActivityIntention.VIEW -> 1
|
||||||
else -> 2
|
else -> 2
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.ui.list.apps
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -15,12 +14,12 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.Application
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.AppAction
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
import de.jrpie.android.launcher.apps.AppFilter
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||||
import de.jrpie.android.launcher.getUserFromId
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.ListLayout
|
import de.jrpie.android.launcher.preferences.ListLayout
|
||||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
|
@ -47,7 +46,8 @@ class AppsRecyclerAdapter(
|
||||||
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
|
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val apps = (activity.applicationContext as Application).apps
|
private val apps = (activity.applicationContext as Application).apps
|
||||||
private val appsListDisplayed: MutableList<DetailedAppInfo> = mutableListOf()
|
private val appsListDisplayed: MutableList<AbstractDetailedAppInfo> = mutableListOf()
|
||||||
|
private val grayscale = LauncherPreferences.theme().monochromeIcons()
|
||||||
|
|
||||||
// temporarily disable auto launch
|
// temporarily disable auto launch
|
||||||
var disableAutoLaunch: Boolean = false
|
var disableAutoLaunch: Boolean = false
|
||||||
|
@ -68,7 +68,7 @@ class AppsRecyclerAdapter(
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
val rect = Rect()
|
val rect = Rect()
|
||||||
img.getGlobalVisibleRect(rect)
|
img.getGlobalVisibleRect(rect)
|
||||||
selectItem(adapterPosition, rect)
|
selectItem(bindingAdapterPosition, rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -80,20 +80,19 @@ class AppsRecyclerAdapter(
|
||||||
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
||||||
var appLabel = appsListDisplayed[i].getCustomLabel(activity)
|
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) {
|
if (layout.useBadgedText) {
|
||||||
appLabel = activity.packageManager.getUserBadgedLabel(
|
appLabel = activity.packageManager.getUserBadgedLabel(
|
||||||
appLabel,
|
appLabel,
|
||||||
getUserFromId(appsListDisplayed[i].app.user, activity)
|
appsListDisplayed[i].getUser(activity)
|
||||||
).toString()
|
).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val appIcon = appsListDisplayed[i].icon
|
|
||||||
|
|
||||||
viewHolder.textView.text = appLabel
|
viewHolder.textView.text = appLabel
|
||||||
viewHolder.img.setImageDrawable(appIcon)
|
|
||||||
|
|
||||||
if (LauncherPreferences.theme().monochromeIcons())
|
|
||||||
viewHolder.img.transformGrayscale()
|
|
||||||
|
|
||||||
// decide when to show the options popup menu about
|
// decide when to show the options popup menu about
|
||||||
if (intention == ListActivity.ListActivityIntention.VIEW) {
|
if (intention == ListActivity.ListActivityIntention.VIEW) {
|
||||||
|
@ -118,22 +117,26 @@ class AppsRecyclerAdapter(
|
||||||
@Suppress("SameReturnValue")
|
@Suppress("SameReturnValue")
|
||||||
private fun showOptionsPopup(
|
private fun showOptionsPopup(
|
||||||
viewHolder: ViewHolder,
|
viewHolder: ViewHolder,
|
||||||
appInfo: DetailedAppInfo
|
appInfo: AbstractDetailedAppInfo
|
||||||
): Boolean {
|
): Boolean {
|
||||||
//create the popup menu
|
//create the popup menu
|
||||||
|
|
||||||
val popup = PopupMenu(activity, viewHolder.img)
|
val popup = PopupMenu(activity, viewHolder.img)
|
||||||
popup.inflate(R.menu.menu_app)
|
popup.inflate(R.menu.menu_app)
|
||||||
|
|
||||||
if (appInfo.isSystemApp) {
|
if (!appInfo.isRemovable()) {
|
||||||
popup.menu.findItem(R.id.app_menu_delete).setVisible(false)
|
popup.menu.findItem(R.id.app_menu_delete).setVisible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LauncherPreferences.apps().hidden()?.contains(appInfo.app) == true) {
|
if (appInfo !is DetailedAppInfo) {
|
||||||
|
popup.menu.findItem(R.id.app_menu_info).setVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LauncherPreferences.apps().hidden()?.contains(appInfo.getRawInfo()) == true) {
|
||||||
popup.menu.findItem(R.id.app_menu_hidden).setTitle(R.string.list_app_hidden_remove)
|
popup.menu.findItem(R.id.app_menu_hidden).setTitle(R.string.list_app_hidden_remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LauncherPreferences.apps().favorites()?.contains(appInfo.app) == true) {
|
if (LauncherPreferences.apps().favorites()?.contains(appInfo.getRawInfo()) == true) {
|
||||||
popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove)
|
popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,19 +144,19 @@ class AppsRecyclerAdapter(
|
||||||
popup.setOnMenuItemClickListener {
|
popup.setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.app_menu_delete -> {
|
R.id.app_menu_delete -> {
|
||||||
appInfo.app.uninstall(activity); true
|
appInfo.getRawInfo().uninstall(activity); true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.app_menu_info -> {
|
R.id.app_menu_info -> {
|
||||||
appInfo.app.openSettings(activity); true
|
(appInfo.getRawInfo() as? AppInfo)?.openSettings(activity); true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.app_menu_favorite -> {
|
R.id.app_menu_favorite -> {
|
||||||
appInfo.app.toggleFavorite(); true
|
appInfo.getRawInfo().toggleFavorite(); true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.app_menu_hidden -> {
|
R.id.app_menu_hidden -> {
|
||||||
appInfo.app.toggleHidden(root); true
|
appInfo.getRawInfo().toggleHidden(root); true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.app_menu_rename -> {
|
R.id.app_menu_rename -> {
|
||||||
|
@ -188,15 +191,14 @@ class AppsRecyclerAdapter(
|
||||||
val appInfo = appsListDisplayed[pos]
|
val appInfo = appsListDisplayed[pos]
|
||||||
when (intention) {
|
when (intention) {
|
||||||
ListActivity.ListActivityIntention.VIEW -> {
|
ListActivity.ListActivityIntention.VIEW -> {
|
||||||
AppAction(appInfo.app).invoke(activity, rect)
|
appInfo.getAction().invoke(activity, rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListActivity.ListActivityIntention.PICK -> {
|
ListActivity.ListActivityIntention.PICK -> {
|
||||||
val returnIntent = Intent()
|
|
||||||
AppAction(appInfo.app).writeToIntent(returnIntent)
|
|
||||||
returnIntent.putExtra("forGesture", forGesture)
|
|
||||||
activity.setResult(REQUEST_CHOOSE_APP, returnIntent)
|
|
||||||
activity.finish()
|
activity.finish()
|
||||||
|
forGesture ?: return
|
||||||
|
val gesture = Gesture.byId(forGesture) ?: return
|
||||||
|
Action.setActionForGesture(gesture, appInfo.getAction())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,8 +213,8 @@ class AppsRecyclerAdapter(
|
||||||
&& !disableAutoLaunch
|
&& !disableAutoLaunch
|
||||||
&& LauncherPreferences.functionality().searchAutoLaunch()
|
&& LauncherPreferences.functionality().searchAutoLaunch()
|
||||||
) {
|
) {
|
||||||
val info = appsListDisplayed[0]
|
val app = appsListDisplayed[0]
|
||||||
AppAction(info.app).invoke(activity)
|
app.getAction().invoke(activity)
|
||||||
|
|
||||||
val inputMethodManager =
|
val inputMethodManager =
|
||||||
activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
|
activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package de.jrpie.android.launcher.ui.list.apps
|
package de.jrpie.android.launcher.ui.list.apps
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -13,11 +13,13 @@ import android.widget.EditText
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.REQUEST_UNINSTALL
|
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
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.getUserFromId
|
import de.jrpie.android.launcher.getUserFromId
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
private const val LOG_TAG = "AppContextMenu"
|
private const val LOG_TAG = "AppContextMenu"
|
||||||
|
|
||||||
|
@ -32,27 +34,29 @@ fun AppInfo.openSettings(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppInfo.uninstall(activity: android.app.Activity) {
|
fun AbstractAppInfo.uninstall(activity: Activity) {
|
||||||
val packageName = this.packageName
|
if (this is AppInfo) {
|
||||||
val userId = this.user
|
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_UNINSTALL_PACKAGE)
|
val intent = Intent(Intent.ACTION_DELETE)
|
||||||
intent.data = Uri.parse("package:$packageName")
|
intent.data = "package:$packageName".toUri()
|
||||||
getUserFromId(userId, activity).let { user ->
|
getUserFromId(userId, activity).let { user ->
|
||||||
intent.putExtra(Intent.EXTRA_USER, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
|
||||||
activity.startActivityForResult(
|
|
||||||
intent,
|
|
||||||
REQUEST_UNINSTALL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppInfo.toggleFavorite() {
|
fun AbstractAppInfo.toggleFavorite() {
|
||||||
val favorites: MutableSet<AppInfo> =
|
val favorites: MutableSet<AbstractAppInfo> =
|
||||||
LauncherPreferences.apps().favorites() ?: mutableSetOf()
|
LauncherPreferences.apps().favorites() ?: mutableSetOf()
|
||||||
|
|
||||||
if (favorites.contains(this)) {
|
if (favorites.contains(this)) {
|
||||||
|
@ -69,8 +73,8 @@ fun AppInfo.toggleFavorite() {
|
||||||
/**
|
/**
|
||||||
* @param view: used to show a snackbar letting the user undo the action
|
* @param view: used to show a snackbar letting the user undo the action
|
||||||
*/
|
*/
|
||||||
fun AppInfo.toggleHidden(view: View) {
|
fun AbstractAppInfo.toggleHidden(view: View) {
|
||||||
val hidden: MutableSet<AppInfo> =
|
val hidden: MutableSet<AbstractAppInfo> =
|
||||||
LauncherPreferences.apps().hidden() ?: mutableSetOf()
|
LauncherPreferences.apps().hidden() ?: mutableSetOf()
|
||||||
if (hidden.contains(this)) {
|
if (hidden.contains(this)) {
|
||||||
hidden.remove(this)
|
hidden.remove(this)
|
||||||
|
@ -87,12 +91,12 @@ fun AppInfo.toggleHidden(view: View) {
|
||||||
LauncherPreferences.apps().hidden(hidden)
|
LauncherPreferences.apps().hidden(hidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DetailedAppInfo.showRenameDialog(context: Context) {
|
fun AbstractDetailedAppInfo.showRenameDialog(context: Context) {
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
||||||
setTitle(context.getString(R.string.dialog_rename_title, label))
|
setTitle(context.getString(R.string.dialog_rename_title, getLabel()))
|
||||||
setView(R.layout.dialog_rename_app)
|
setView(R.layout.dialog_rename_app)
|
||||||
setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() }
|
setNegativeButton(android.R.string.cancel) { d, _ -> d.cancel() }
|
||||||
setPositiveButton(R.string.dialog_rename_ok) { d, _ ->
|
setPositiveButton(android.R.string.ok) { d, _ ->
|
||||||
setCustomLabel(
|
setCustomLabel(
|
||||||
(d as? AlertDialog)
|
(d as? AlertDialog)
|
||||||
?.findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
?.findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
||||||
|
@ -102,7 +106,7 @@ fun DetailedAppInfo.showRenameDialog(context: Context) {
|
||||||
}.create().also { it.show() }.apply {
|
}.create().also { it.show() }.apply {
|
||||||
val input = findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
val input = findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
||||||
input?.setText(getCustomLabel(context))
|
input?.setText(getCustomLabel(context))
|
||||||
input?.hint = label
|
input?.hint = getLabel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,14 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
import de.jrpie.android.launcher.apps.AppFilter
|
||||||
import de.jrpie.android.launcher.databinding.ListAppsBinding
|
import de.jrpie.android.launcher.databinding.ListAppsBinding
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
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
|
import de.jrpie.android.launcher.ui.openSoftKeyboard
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +49,7 @@ class ListFragmentApps : Fragment(), UIObject {
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
|
||||||
binding.listAppsCheckBoxFavorites.isChecked =
|
binding.listAppsCheckBoxFavorites.isChecked =
|
||||||
(favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE)
|
((activity as? ListActivity)?.favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
|
@ -65,25 +62,33 @@ class ListFragmentApps : Fragment(), UIObject {
|
||||||
override fun setOnClicks() {}
|
override fun setOnClicks() {}
|
||||||
|
|
||||||
override fun adjustLayout() {
|
override fun adjustLayout() {
|
||||||
|
val listActivity = activity as? ListActivity ?: return
|
||||||
|
|
||||||
appsRecyclerAdapter =
|
appsRecyclerAdapter =
|
||||||
AppsRecyclerAdapter(
|
AppsRecyclerAdapter(
|
||||||
requireActivity(), binding.root, intention, forGesture,
|
listActivity, binding.root, listActivity.intention, listActivity.forGesture,
|
||||||
appFilter = AppFilter(
|
appFilter = AppFilter(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
"",
|
"",
|
||||||
favoritesVisibility = favoritesVisibility,
|
favoritesVisibility = listActivity.favoritesVisibility,
|
||||||
privateSpaceVisibility = privateSpaceVisibility,
|
privateSpaceVisibility = listActivity.privateSpaceVisibility,
|
||||||
hiddenVisibility = hiddenVisibility
|
hiddenVisibility = listActivity.hiddenVisibility
|
||||||
),
|
),
|
||||||
layout = LauncherPreferences.list().layout()
|
layout = LauncherPreferences.list().layout()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// set up the list / recycler
|
// set up the list / recycler
|
||||||
binding.listAppsRview.apply {
|
binding.listAppsRview.apply {
|
||||||
// improve performance (since content changes don't change the layout size)
|
// improve performance (since content changes don't change the layout size)
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
layoutManager = LauncherPreferences.list().layout().layoutManager(context)
|
layoutManager = LauncherPreferences.list().layout().layoutManager(context)
|
||||||
|
.also {
|
||||||
|
if (LauncherPreferences.list().reverseLayout()) {
|
||||||
|
(it as? LinearLayoutManager)?.reverseLayout = true
|
||||||
|
(it as? GridLayoutManager)?.reverseLayout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
adapter = appsRecyclerAdapter
|
adapter = appsRecyclerAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +120,8 @@ class ListFragmentApps : Fragment(), UIObject {
|
||||||
|
|
||||||
if (newText == " " &&
|
if (newText == " " &&
|
||||||
!appsRecyclerAdapter.disableAutoLaunch &&
|
!appsRecyclerAdapter.disableAutoLaunch &&
|
||||||
intention == ListActivity.ListActivityIntention.VIEW &&
|
(activity as? ListActivity)?.intention
|
||||||
|
== ListActivity.ListActivityIntention.VIEW &&
|
||||||
LauncherPreferences.functionality().searchAutoLaunch()
|
LauncherPreferences.functionality().searchAutoLaunch()
|
||||||
) {
|
) {
|
||||||
appsRecyclerAdapter.disableAutoLaunch = true
|
appsRecyclerAdapter.disableAutoLaunch = true
|
||||||
|
@ -132,17 +138,17 @@ class ListFragmentApps : Fragment(), UIObject {
|
||||||
})
|
})
|
||||||
|
|
||||||
binding.listAppsCheckBoxFavorites.setOnClickListener {
|
binding.listAppsCheckBoxFavorites.setOnClickListener {
|
||||||
favoritesVisibility =
|
listActivity.favoritesVisibility =
|
||||||
if (binding.listAppsCheckBoxFavorites.isChecked) {
|
if (binding.listAppsCheckBoxFavorites.isChecked) {
|
||||||
AppFilter.Companion.AppSetVisibility.EXCLUSIVE
|
AppFilter.Companion.AppSetVisibility.EXCLUSIVE
|
||||||
} else {
|
} else {
|
||||||
AppFilter.Companion.AppSetVisibility.VISIBLE
|
AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||||
}
|
}
|
||||||
appsRecyclerAdapter.setFavoritesVisibility(favoritesVisibility)
|
appsRecyclerAdapter.setFavoritesVisibility(listActivity.favoritesVisibility)
|
||||||
(activity as? ListActivity)?.updateTitle()
|
(activity as? ListActivity)?.updateTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intention == ListActivity.ListActivityIntention.VIEW
|
if (listActivity.intention == ListActivity.ListActivityIntention.VIEW
|
||||||
&& LauncherPreferences.functionality().searchAutoOpenKeyboard()
|
&& LauncherPreferences.functionality().searchAutoOpenKeyboard()
|
||||||
) {
|
) {
|
||||||
binding.listAppsSearchview.openSoftKeyboard(requireContext())
|
binding.listAppsSearchview.openSoftKeyboard(requireContext())
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package de.jrpie.android.launcher.ui.list.other
|
package de.jrpie.android.launcher.ui.list.other
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -9,9 +8,10 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
import de.jrpie.android.launcher.actions.Action
|
||||||
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.ui.list.forGesture
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [OtherRecyclerAdapter] will only be displayed in the ListActivity,
|
* The [OtherRecyclerAdapter] will only be displayed in the ListActivity,
|
||||||
|
@ -33,10 +33,13 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
||||||
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
val pos = adapterPosition
|
val pos = bindingAdapterPosition
|
||||||
val content = othersList[pos]
|
val content = othersList[pos]
|
||||||
|
|
||||||
forGesture?.let { returnChoiceIntent(it, content) }
|
activity.finish()
|
||||||
|
val gestureId = (activity as? ListActivity)?.forGesture ?: return
|
||||||
|
val gesture = Gesture.byId(gestureId) ?: return
|
||||||
|
Action.setActionForGesture(gesture, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -61,12 +64,4 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
||||||
val view: View = inflater.inflate(R.layout.list_other_row, parent, false)
|
val view: View = inflater.inflate(R.layout.list_other_row, parent, false)
|
||||||
return ViewHolder(view)
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package de.jrpie.android.launcher.ui.settings
|
package de.jrpie.android.launcher.ui.settings
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
@ -8,17 +7,14 @@ import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager.widget.ViewPager
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
|
||||||
import de.jrpie.android.launcher.databinding.SettingsBinding
|
import de.jrpie.android.launcher.databinding.SettingsBinding
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background
|
import de.jrpie.android.launcher.preferences.theme.Background
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||||
import de.jrpie.android.launcher.saveListActivityChoice
|
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
import de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActions
|
import de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActions
|
||||||
import de.jrpie.android.launcher.ui.settings.launcher.SettingsFragmentLauncher
|
import de.jrpie.android.launcher.ui.settings.launcher.SettingsFragmentLauncher
|
||||||
|
@ -49,15 +45,15 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
||||||
// This ugly workaround causes a jump to the top of the list, but at least
|
// This ugly workaround causes a jump to the top of the list, but at least
|
||||||
// the text stays readable.
|
// the text stays readable.
|
||||||
val i = Intent(this, SettingsActivity::class.java)
|
val i = Intent(this, SettingsActivity::class.java)
|
||||||
.also { it.putExtra("tab", 1) }
|
.also { it.putExtra(EXTRA_TAB, 1) }
|
||||||
finish()
|
finish()
|
||||||
startActivity(i)
|
startActivity(i)
|
||||||
} else
|
} else
|
||||||
if (prefKey?.startsWith("theme.") == true ||
|
if (prefKey?.startsWith("theme.") == true ||
|
||||||
prefKey?.startsWith("display.") == true
|
prefKey?.startsWith("display.") == true
|
||||||
) {
|
) {
|
||||||
recreate()
|
recreate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private lateinit var binding: SettingsBinding
|
private lateinit var binding: SettingsBinding
|
||||||
|
|
||||||
|
@ -71,15 +67,14 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
// set up tabs and swiping in settings
|
// set up tabs and swiping in settings
|
||||||
val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this, supportFragmentManager)
|
val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this)
|
||||||
val viewPager: ViewPager = findViewById(R.id.settings_viewpager)
|
binding.settingsViewpager.apply {
|
||||||
viewPager.adapter = sectionsPagerAdapter
|
adapter = sectionsPagerAdapter
|
||||||
|
setCurrentItem(intent.getIntExtra(EXTRA_TAB, 0), false)
|
||||||
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() {
|
override fun onStart() {
|
||||||
|
@ -108,24 +103,21 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
companion object {
|
||||||
when (requestCode) {
|
private const val EXTRA_TAB = "tab"
|
||||||
REQUEST_CHOOSE_APP -> saveListActivityChoice(data)
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TAB_TITLES = arrayOf(
|
private val TAB_TITLES = arrayOf(
|
||||||
R.string.settings_tab_app,
|
R.string.settings_tab_actions,
|
||||||
R.string.settings_tab_launcher,
|
R.string.settings_tab_launcher,
|
||||||
R.string.settings_tab_meta
|
R.string.settings_tab_meta
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsSectionsPagerAdapter(private val context: Context, fm: FragmentManager) :
|
class SettingsSectionsPagerAdapter(private val activity: FragmentActivity) :
|
||||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
FragmentStateAdapter(activity) {
|
||||||
|
|
||||||
override fun getItem(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when (position) {
|
return when (position) {
|
||||||
0 -> SettingsFragmentActions()
|
0 -> SettingsFragmentActions()
|
||||||
1 -> SettingsFragmentLauncher()
|
1 -> SettingsFragmentLauncher()
|
||||||
|
@ -134,11 +126,11 @@ class SettingsSectionsPagerAdapter(private val context: Context, fm: FragmentMan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence {
|
fun getPageTitle(position: Int): CharSequence {
|
||||||
return context.resources.getString(TAB_TITLES[position])
|
return activity.resources.getString(TAB_TITLES[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@ import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
import de.jrpie.android.launcher.apps.AppFilter
|
||||||
|
@ -94,6 +94,8 @@ class SettingsFragmentActionsRecycler : Fragment(), UIObject {
|
||||||
class ActionsRecyclerAdapter(val activity: Activity) :
|
class ActionsRecyclerAdapter(val activity: Activity) :
|
||||||
RecyclerView.Adapter<ActionsRecyclerAdapter.ViewHolder>() {
|
RecyclerView.Adapter<ActionsRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val drawableUnknown = AppCompatResources.getDrawable(activity, R.drawable.baseline_question_mark_24)
|
||||||
|
|
||||||
private val gesturesList: ArrayList<Gesture> =
|
private val gesturesList: ArrayList<Gesture> =
|
||||||
Gesture.entries.filter(Gesture::isEnabled) as ArrayList<Gesture>
|
Gesture.entries.filter(Gesture::isEnabled) as ArrayList<Gesture>
|
||||||
|
|
||||||
|
@ -115,15 +117,18 @@ class ActionsRecyclerAdapter(val activity: Activity) :
|
||||||
|
|
||||||
private fun updateViewHolder(gesture: Gesture, viewHolder: ViewHolder) {
|
private fun updateViewHolder(gesture: Gesture, viewHolder: ViewHolder) {
|
||||||
val action = Action.forGesture(gesture)
|
val action = Action.forGesture(gesture)
|
||||||
val drawable = action?.getIcon(activity)
|
|
||||||
|
|
||||||
if (action == null || drawable == null) {
|
if (action == null) {
|
||||||
viewHolder.img.visibility = View.INVISIBLE
|
viewHolder.img.visibility = View.INVISIBLE
|
||||||
viewHolder.removeAction.visibility = View.GONE
|
viewHolder.removeAction.visibility = View.GONE
|
||||||
viewHolder.chooseButton.visibility = View.VISIBLE
|
viewHolder.chooseButton.visibility = View.VISIBLE
|
||||||
return
|
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.img.visibility = View.VISIBLE
|
||||||
viewHolder.removeAction.visibility = View.VISIBLE
|
viewHolder.removeAction.visibility = View.VISIBLE
|
||||||
viewHolder.chooseButton.visibility = View.INVISIBLE
|
viewHolder.chooseButton.visibility = View.INVISIBLE
|
||||||
|
@ -137,9 +142,7 @@ class ActionsRecyclerAdapter(val activity: Activity) :
|
||||||
val description = gesture.getDescription(activity)
|
val description = gesture.getDescription(activity)
|
||||||
viewHolder.descriptionTextView.text = description
|
viewHolder.descriptionTextView.text = description
|
||||||
|
|
||||||
|
viewHolder.img.transformGrayscale(LauncherPreferences.theme().monochromeIcons())
|
||||||
if (LauncherPreferences.theme().monochromeIcons())
|
|
||||||
viewHolder.img.transformGrayscale()
|
|
||||||
|
|
||||||
updateViewHolder(gesture, viewHolder)
|
updateViewHolder(gesture, viewHolder)
|
||||||
viewHolder.img.setOnClickListener { chooseApp(gesture) }
|
viewHolder.img.setOnClickListener { chooseApp(gesture) }
|
||||||
|
@ -175,9 +178,6 @@ class ActionsRecyclerAdapter(val activity: Activity) :
|
||||||
intent.putExtra("intention", ListActivity.ListActivityIntention.PICK.toString())
|
intent.putExtra("intention", ListActivity.ListActivityIntention.PICK.toString())
|
||||||
intent.putExtra("hiddenVisibility", AppFilter.Companion.AppSetVisibility.VISIBLE)
|
intent.putExtra("hiddenVisibility", AppFilter.Companion.AppSetVisibility.VISIBLE)
|
||||||
intent.putExtra("forGesture", gesture.id) // for which action we choose the app
|
intent.putExtra("forGesture", gesture.id) // for which action we choose the app
|
||||||
activity.startActivityForResult(
|
activity.startActivity(intent)
|
||||||
intent,
|
|
||||||
REQUEST_CHOOSE_APP
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.ui.settings.meta
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -16,10 +15,10 @@ import de.jrpie.android.launcher.copyToClipboard
|
||||||
import de.jrpie.android.launcher.databinding.SettingsMetaBinding
|
import de.jrpie.android.launcher.databinding.SettingsMetaBinding
|
||||||
import de.jrpie.android.launcher.getDeviceInfo
|
import de.jrpie.android.launcher.getDeviceInfo
|
||||||
import de.jrpie.android.launcher.openInBrowser
|
import de.jrpie.android.launcher.openInBrowser
|
||||||
|
import de.jrpie.android.launcher.openTutorial
|
||||||
import de.jrpie.android.launcher.preferences.resetPreferences
|
import de.jrpie.android.launcher.preferences.resetPreferences
|
||||||
import de.jrpie.android.launcher.ui.LegalInfoActivity
|
import de.jrpie.android.launcher.ui.LegalInfoActivity
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
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.
|
* The [SettingsFragmentMeta] is a used as a tab in the SettingsActivity.
|
||||||
|
@ -47,8 +46,17 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
||||||
|
|
||||||
override fun setOnClicks() {
|
override fun setOnClicks() {
|
||||||
|
|
||||||
|
fun bindURL(view: View, urlRes: Int) {
|
||||||
|
view.setOnClickListener {
|
||||||
|
openInBrowser(
|
||||||
|
getString(urlRes),
|
||||||
|
requireContext()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.settingsMetaButtonViewTutorial.setOnClickListener {
|
binding.settingsMetaButtonViewTutorial.setOnClickListener {
|
||||||
startActivity(Intent(this.context, TutorialActivity::class.java))
|
openTutorial(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
// prompting for settings-reset confirmation
|
// prompting for settings-reset confirmation
|
||||||
|
@ -69,12 +77,7 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
||||||
|
|
||||||
|
|
||||||
// view code
|
// view code
|
||||||
binding.settingsMetaButtonViewCode.setOnClickListener {
|
bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github)
|
||||||
openInBrowser(
|
|
||||||
getString(R.string.settings_meta_link_github),
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// report a bug
|
// report a bug
|
||||||
binding.settingsMetaButtonReportBug.setOnClickListener {
|
binding.settingsMetaButtonReportBug.setOnClickListener {
|
||||||
|
@ -110,37 +113,19 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// join chat
|
// join chat
|
||||||
binding.settingsMetaButtonJoinChat.setOnClickListener {
|
bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url)
|
||||||
openInBrowser(
|
|
||||||
getString(R.string.settings_meta_chat_url),
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// contact developer
|
// contact developer
|
||||||
binding.settingsMetaButtonContact.setOnClickListener {
|
// bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url)
|
||||||
openInBrowser(
|
|
||||||
getString(R.string.settings_meta_contact_url),
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// contact fork developer
|
// contact fork developer
|
||||||
binding.settingsMetaButtonForkContact.setOnClickListener {
|
bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url)
|
||||||
openInBrowser(
|
|
||||||
getString(R.string.settings_meta_fork_contact_url),
|
// donate
|
||||||
requireContext()
|
bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// privacy policy
|
// privacy policy
|
||||||
binding.settingsMetaButtonPrivacy.setOnClickListener {
|
bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url)
|
||||||
openInBrowser(
|
|
||||||
getString(R.string.settings_meta_privacy_url),
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// legal info
|
// legal info
|
||||||
binding.settingsMetaButtonLicenses.setOnClickListener {
|
binding.settingsMetaButtonLicenses.setOnClickListener {
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
package de.jrpie.android.launcher.ui.tutorial
|
package de.jrpie.android.launcher.ui.tutorial
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.window.OnBackInvokedDispatcher
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.databinding.TutorialBinding
|
||||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
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.UIObject
|
||||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentConcept
|
import de.jrpie.android.launcher.ui.blink
|
||||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentFinish
|
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment0Start
|
||||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentSetup
|
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment1Concept
|
||||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentStart
|
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment2Usage
|
||||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentUsage
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialActivity] is displayed automatically on new installations.
|
* The [TutorialActivity] is displayed automatically on new installations.
|
||||||
|
@ -29,19 +31,75 @@ import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentUsage
|
||||||
*/
|
*/
|
||||||
class TutorialActivity : AppCompatActivity(), UIObject {
|
class TutorialActivity : AppCompatActivity(), UIObject {
|
||||||
|
|
||||||
|
private lateinit var binding: TutorialBinding
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
super<AppCompatActivity>.onCreate(savedInstanceState)
|
||||||
super<UIObject>.onCreate()
|
super<UIObject>.onCreate()
|
||||||
|
|
||||||
// Initialise layout
|
// Initialise layout
|
||||||
setContentView(R.layout.tutorial)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// set up tabs and swiping in settings
|
// set up tabs and swiping in settings
|
||||||
val sectionsPagerAdapter = TutorialSectionsPagerAdapter(supportFragmentManager)
|
val sectionsPagerAdapter = TutorialSectionsPagerAdapter(this)
|
||||||
val viewPager: ViewPager = findViewById(R.id.tutorial_viewpager)
|
binding.tutorialViewpager.apply {
|
||||||
viewPager.adapter = sectionsPagerAdapter
|
adapter = sectionsPagerAdapter
|
||||||
val tabs: TabLayout = findViewById(R.id.tutorial_tabs)
|
currentItem = 0
|
||||||
tabs.setupWithViewPager(viewPager)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
override fun getTheme(): Resources.Theme {
|
||||||
|
@ -53,14 +111,9 @@ class TutorialActivity : AppCompatActivity(), UIObject {
|
||||||
super<UIObject>.onStart()
|
super<UIObject>.onStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
// prevent going back when the tutorial is shown for the first time
|
||||||
when (requestCode) {
|
@Deprecated("Deprecated in Java", ReplaceWith("use anyway"))
|
||||||
REQUEST_CHOOSE_APP -> saveListActivityChoice(data)
|
@Suppress("deprecation") // support API level < 33
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: prevent going back, allow if viewed again later
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (LauncherPreferences.internal().started())
|
if (LauncherPreferences.internal().started())
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
|
@ -74,26 +127,22 @@ class TutorialActivity : AppCompatActivity(), UIObject {
|
||||||
*
|
*
|
||||||
* Tabs: (Start | Concept | Usage | Setup | Finish)
|
* Tabs: (Start | Concept | Usage | Setup | Finish)
|
||||||
*/
|
*/
|
||||||
class TutorialSectionsPagerAdapter(fm: FragmentManager) :
|
class TutorialSectionsPagerAdapter(activity: FragmentActivity) :
|
||||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
FragmentStateAdapter(activity) {
|
||||||
|
|
||||||
override fun getItem(position: Int): Fragment {
|
override fun getItemCount(): Int {
|
||||||
|
return 6
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when (position) {
|
return when (position) {
|
||||||
0 -> TutorialFragmentStart()
|
0 -> TutorialFragment0Start()
|
||||||
1 -> TutorialFragmentConcept()
|
1 -> TutorialFragment1Concept()
|
||||||
2 -> TutorialFragmentUsage()
|
2 -> TutorialFragment2Usage()
|
||||||
3 -> TutorialFragmentSetup()
|
3 -> TutorialFragment3AppList()
|
||||||
4 -> TutorialFragmentFinish()
|
4 -> TutorialFragment4Setup()
|
||||||
|
5 -> TutorialFragment5Finish()
|
||||||
else -> Fragment()
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,24 +5,22 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import de.jrpie.android.launcher.databinding.TutorialStartBinding
|
import de.jrpie.android.launcher.databinding.Tutorial0StartBinding
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
import de.jrpie.android.launcher.ui.blink
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialFragmentStart] is a used as a tab in the TutorialActivity.
|
* The [TutorialFragment0Start] is a used as a tab in the TutorialActivity.
|
||||||
*
|
*
|
||||||
* It displays info about the app and gets the user into the tutorial
|
* It displays info about the app and gets the user into the tutorial
|
||||||
*/
|
*/
|
||||||
class TutorialFragmentStart : Fragment(), UIObject {
|
class TutorialFragment0Start : Fragment(), UIObject {
|
||||||
|
|
||||||
private lateinit var binding: TutorialStartBinding
|
private lateinit var binding: Tutorial0StartBinding
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = TutorialStartBinding.inflate(inflater, container, false)
|
binding = Tutorial0StartBinding.inflate(inflater, container, false)
|
||||||
binding.tutorialStartIconRight.blink()
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,22 +6,22 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import de.jrpie.android.launcher.BuildConfig
|
import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.databinding.TutorialConceptBinding
|
import de.jrpie.android.launcher.databinding.Tutorial1ConceptBinding
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialFragmentConcept] is a used as a tab in the TutorialActivity.
|
* The [TutorialFragment1Concept] is a used as a tab in the TutorialActivity.
|
||||||
*
|
*
|
||||||
* It is used to display info about Launchers concept (open source, efficiency ...)
|
* It is used to display info about Launchers concept (open source, efficiency ...)
|
||||||
*/
|
*/
|
||||||
class TutorialFragmentConcept : Fragment(), UIObject {
|
class TutorialFragment1Concept : Fragment(), UIObject {
|
||||||
private lateinit var binding: TutorialConceptBinding
|
private lateinit var binding: Tutorial1ConceptBinding
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = TutorialConceptBinding.inflate(inflater, container, false)
|
binding = Tutorial1ConceptBinding.inflate(inflater, container, false)
|
||||||
binding.tutorialConceptBadgeVersion.text = BuildConfig.VERSION_NAME
|
binding.tutorialConceptBadgeVersion.text = BuildConfig.VERSION_NAME
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
|
@ -9,17 +9,17 @@ import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialFragmentUsage] is a used as a tab in the TutorialActivity.
|
* The [TutorialFragment2Usage] is a used as a tab in the TutorialActivity.
|
||||||
*
|
*
|
||||||
* Tells the user how his screen will look and how the app can be used
|
* Tells the user how his screen will look and how the app can be used
|
||||||
*/
|
*/
|
||||||
class TutorialFragmentUsage : Fragment(), UIObject {
|
class TutorialFragment2Usage : Fragment(), UIObject {
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
return inflater.inflate(R.layout.tutorial_usage, container, false)
|
return inflater.inflate(R.layout.tutorial_2_usage, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
|
@ -0,0 +1,30 @@
|
||||||
|
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<Fragment>.onStart()
|
||||||
|
super<UIObject>.onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,17 +9,17 @@ import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialFragmentSetup] is a used as a tab in the TutorialActivity.
|
* The [TutorialFragment4Setup] is a used as a tab in the TutorialActivity.
|
||||||
*
|
*
|
||||||
* It is used to display info in the tutorial
|
* It is used to display info in the tutorial
|
||||||
*/
|
*/
|
||||||
class TutorialFragmentSetup : Fragment(), UIObject {
|
class TutorialFragment4Setup : Fragment(), UIObject {
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
return inflater.inflate(R.layout.tutorial_setup, container, false)
|
return inflater.inflate(R.layout.tutorial_4_setup, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
|
@ -6,25 +6,25 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import de.jrpie.android.launcher.BuildConfig.VERSION_CODE
|
import de.jrpie.android.launcher.BuildConfig.VERSION_CODE
|
||||||
import de.jrpie.android.launcher.databinding.TutorialFinishBinding
|
import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.setDefaultHomeScreen
|
import de.jrpie.android.launcher.setDefaultHomeScreen
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [TutorialFragmentFinish] is a used as a tab in the TutorialActivity.
|
* The [TutorialFragment5Finish] is a used as a tab in the TutorialActivity.
|
||||||
*
|
*
|
||||||
* It is used to display further resources and let the user start Launcher
|
* It is used to display further resources and let the user start Launcher
|
||||||
*/
|
*/
|
||||||
class TutorialFragmentFinish : Fragment(), UIObject {
|
class TutorialFragment5Finish : Fragment(), UIObject {
|
||||||
|
|
||||||
private lateinit var binding: TutorialFinishBinding
|
private lateinit var binding: Tutorial5FinishBinding
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = TutorialFinishBinding.inflate(inflater, container, false)
|
binding = Tutorial5FinishBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ class HtmlTextView(context: Context, attr: AttributeSet?, int: Int) :
|
||||||
constructor(context: Context) : this(context, null, 0)
|
constructor(context: Context) : this(context, null, 0)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@Suppress("deprecation") // required to support API level < 24
|
||||||
text = Html.fromHtml(text.toString())
|
text = Html.fromHtml(text.toString())
|
||||||
movementMethod = LinkMovementMethod.getInstance()
|
movementMethod = LinkMovementMethod.getInstance()
|
||||||
}
|
}
|
||||||
|
|
BIN
app/src/main/res/drawable-mdpi/tutorial_app_list.png
Normal file
BIN
app/src/main/res/drawable-mdpi/tutorial_app_list.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
BIN
app/src/main/res/drawable-mdpi/tutorial_home_screen.png
Normal file
BIN
app/src/main/res/drawable-mdpi/tutorial_home_screen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
11
app/src/main/res/drawable/baseline_apps_24.xml
Normal file
11
app/src/main/res/drawable/baseline_apps_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z" />
|
||||||
|
|
||||||
|
</vector>
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?android:textColor"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
11
app/src/main/res/drawable/baseline_home_24.xml
Normal file
11
app/src/main/res/drawable/baseline_home_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
|
||||||
|
|
||||||
|
</vector>
|
|
@ -2,9 +2,8 @@
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="960">
|
||||||
android:tint="?attr/colorControlNormal">
|
<path
|
||||||
<path
|
android:fillColor="?android:textColor"
|
||||||
android:fillColor="?android:textColor"
|
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,400Q720,400 720,400Q720,400 720,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320ZM240,800Q240,800 240,800Q240,800 240,800L240,400Q240,400 240,400Q240,400 240,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800Z" />
|
||||||
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,400Q720,400 720,400Q720,400 720,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320ZM240,800Q240,800 240,800Q240,800 240,800L240,400Q240,400 240,400Q240,400 240,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800Z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:textColor" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
<path android:fillColor="?android:textColor" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z" />
|
||||||
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="?android:textColor" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
11
app/src/main/res/drawable/baseline_navigate_before_24.xml
Normal file
11
app/src/main/res/drawable/baseline_navigate_before_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z" />
|
||||||
|
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/baseline_navigate_next_24.xml
Normal file
11
app/src/main/res/drawable/baseline_navigate_next_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
|
||||||
|
|
||||||
|
</vector>
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
12
app/src/main/res/drawable/baseline_play_arrow_24.xml
Normal file
12
app/src/main/res/drawable/baseline_play_arrow_24.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M8,5v14l11,-7z" />
|
||||||
|
|
||||||
|
</vector>
|
12
app/src/main/res/drawable/baseline_question_mark_24.xml
Normal file
12
app/src/main/res/drawable/baseline_question_mark_24.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44c0.91,-1.29 0.4,-3.7 -2.18,-3.7c-1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41c0.7,1.15 1.11,3.3 0.03,4.9c-1.2,1.77 -2.35,2.31 -2.97,3.45c-0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2S14,18.9 14,20z" />
|
||||||
|
|
||||||
|
</vector>
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:width="24dp">
|
android:width="24dp">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
16
app/src/main/res/drawable/baseline_volume_adjust_24.xml
Normal file
16
app/src/main/res/drawable/baseline_volume_adjust_24.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="m 3,9 v 6 h 4 l 5,5 V 4 L 7,9 Z m 13.5,3 C 16.5,10.23 15.48,8.71 14,7.97 v 8.05 c 1.48,-0.73 2.5,-2.25 2.5,-4.02 z" />
|
||||||
|
<path
|
||||||
|
android:fillAlpha="0.5"
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="m 14,3.23 v 2.06 c 2.89,0.86 5,3.54 5,6.71 0,3.17 -2.11,5.85 -5,6.71 v 2.06 C 18.01,19.86 21,16.28 21,12 21,7.72 18.01,4.14 14,3.23 Z"
|
||||||
|
android:strokeAlpha="0.5" />
|
||||||
|
|
||||||
|
</vector>
|
|
@ -2,7 +2,6 @@
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:autoMirrored="true"
|
android:autoMirrored="true"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:autoMirrored="true"
|
android:autoMirrored="true"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 159 KiB |
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#252827"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
</vector>
|
|
122
app/src/main/res/layout/activity_pin_shortcut.xml
Normal file
122
app/src/main/res/layout/activity_pin_shortcut.xml
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/list_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context=".ui.PinShortcutActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/pin_shortcut_appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@null"
|
||||||
|
|
||||||
|
android:gravity="center"
|
||||||
|
app:elevation="0dp"
|
||||||
|
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_heading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minHeight="?actionBarSize"
|
||||||
|
android:padding="@dimen/appbar_padding"
|
||||||
|
android:text="@string/pin_shortcut_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/pin_shortcut_close"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pin_shortcut_close"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="true"
|
||||||
|
android:paddingLeft="16sp"
|
||||||
|
android:paddingRight="16sp"
|
||||||
|
android:src="@drawable/baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pin_shortcut_appbar">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pin_shortcut_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="50dp"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
tools:drawableLeft="@drawable/baseline_settings_24"
|
||||||
|
tools:text="Shortcut name" />
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/pin_shortcut_switch_visible"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:textColor"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="@string/pin_shortcut_switch_visible" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pin_shortcut_button_bind"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pin_shortcut_button_bind" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pin_shortcut_button_ok"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
27
app/src/main/res/layout/dialog_select_gesture.xml
Normal file
27
app/src/main/res/layout/dialog_select_gesture.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/dialog_select_gesture"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/dialog_select_gesture_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fadeScrollbars="false"
|
||||||
|
app:fastScrollEnabled="true"
|
||||||
|
app:fastScrollHorizontalThumbDrawable="@drawable/fast_scroll_thumb_drawable"
|
||||||
|
app:fastScrollHorizontalTrackDrawable="@drawable/fast_scroll_track_drawable"
|
||||||
|
app:fastScrollVerticalThumbDrawable="@drawable/fast_scroll_thumb_drawable"
|
||||||
|
app:fastScrollVerticalTrackDrawable="@drawable/fast_scroll_track_drawable" >
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
53
app/src/main/res/layout/dialog_select_gesture_row.xml
Normal file
53
app/src/main/res/layout/dialog_select_gesture_row.xml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/dialog_select_gesture_row_container"
|
||||||
|
style="@style/AlertDialogCustom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/dialog_select_gesture_row_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_select_gesture_row_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:gravity="start"
|
||||||
|
android:textSize="15sp"
|
||||||
|
tools:text="Action label" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_select_gesture_row_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:text="A verbose description of how to perform the action" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dialog_select_gesture_row_icon"
|
||||||
|
android:layout_width="@dimen/app_icon_side"
|
||||||
|
android:layout_height="@dimen/app_icon_side"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@drawable/baseline_flashlight_on_24"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -40,8 +40,7 @@
|
||||||
android:src="@drawable/baseline_settings_24"
|
android:src="@drawable/baseline_settings_24"
|
||||||
custom:layout_constraintBottom_toBottomOf="parent"
|
custom:layout_constraintBottom_toBottomOf="parent"
|
||||||
custom:layout_constraintStart_toStartOf="parent"
|
custom:layout_constraintStart_toStartOf="parent"
|
||||||
custom:layout_constraintTop_toTopOf="parent"
|
custom:layout_constraintTop_toTopOf="parent" />
|
||||||
custom:type="solid" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/list_heading"
|
android:id="@+id/list_heading"
|
||||||
|
@ -70,8 +69,7 @@
|
||||||
android:src="@drawable/baseline_close_24"
|
android:src="@drawable/baseline_close_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
custom:type="solid" />
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/list_lock"
|
android:id="@+id/list_lock"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -85,8 +83,7 @@
|
||||||
android:src="@drawable/baseline_lock_24"
|
android:src="@drawable/baseline_lock_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/list_close"
|
app:layout_constraintEnd_toStartOf="@id/list_close"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
custom:type="solid" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
@ -98,6 +95,11 @@
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Should be replaced by androidx.viewpager2.widget.ViewPager2
|
||||||
|
but there is an issue with opening the keyboard:
|
||||||
|
https://github.com/jrpie/launcher/issues/130
|
||||||
|
-->
|
||||||
<androidx.viewpager.widget.ViewPager
|
<androidx.viewpager.widget.ViewPager
|
||||||
android:id="@+id/list_viewpager"
|
android:id="@+id/list_viewpager"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -15,18 +15,21 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/list_apps_row_name"
|
android:id="@+id/list_apps_row_name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="60sp"
|
android:layout_marginStart="20sp"
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:text=""
|
android:text=""
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
|
tools:text="@string/app_name"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@id/list_apps_row_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -15,6 +15,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
android:paddingTop="5dp"
|
android:paddingTop="5dp"
|
||||||
android:text=""
|
android:text=""
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
tools:text="some app"
|
tools:text="@string/app_name"
|
||||||
app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon"
|
app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -32,6 +32,6 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="some app" />
|
tools:text="@string/app_name" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -47,8 +47,7 @@
|
||||||
android:src="@drawable/baseline_close_24"
|
android:src="@drawable/baseline_close_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
custom:type="solid" />
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/settings_system"
|
android:id="@+id/settings_system"
|
||||||
|
@ -63,8 +62,7 @@
|
||||||
android:src="@drawable/baseline_settings_applications_24"
|
android:src="@drawable/baseline_settings_applications_24"
|
||||||
custom:layout_constraintBottom_toBottomOf="parent"
|
custom:layout_constraintBottom_toBottomOf="parent"
|
||||||
custom:layout_constraintStart_toStartOf="parent"
|
custom:layout_constraintStart_toStartOf="parent"
|
||||||
custom:layout_constraintTop_toTopOf="parent"
|
custom:layout_constraintTop_toTopOf="parent" />
|
||||||
custom:type="solid" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
@ -75,7 +73,7 @@
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/settings_viewpager"
|
android:id="@+id/settings_viewpager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -43,13 +43,15 @@
|
||||||
android:id="@+id/settings_actions_row_button_choose"
|
android:id="@+id/settings_actions_row_button_choose"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:maxWidth="100dp"
|
||||||
android:text="@string/settings_apps_choose"
|
android:text="@string/settings_apps_choose"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
/>
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/settings_actions_row_icon_img"
|
android:id="@+id/settings_actions_row_icon_img"
|
||||||
|
|
|
@ -59,12 +59,12 @@
|
||||||
android:text="@string/settings_meta_join_chat"
|
android:text="@string/settings_meta_join_chat"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
<Button
|
<!--<Button
|
||||||
android:id="@+id/settings_meta_button_contact"
|
android:id="@+id/settings_meta_button_contact"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/settings_meta_contact"
|
android:text="@string/settings_meta_contact"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />-->
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/settings_meta_button_fork_contact"
|
android:id="@+id/settings_meta_button_fork_contact"
|
||||||
|
@ -73,6 +73,13 @@
|
||||||
android:text="@string/settings_meta_fork_contact"
|
android:text="@string/settings_meta_fork_contact"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/settings_meta_button_donate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/settings_meta_donate"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="64sp" />
|
android:layout_height="64sp" />
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
tools:context=".ui.tutorial.TutorialActivity">
|
tools:context=".ui.tutorial.TutorialActivity">
|
||||||
|
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/tutorial_viewpager"
|
android:id="@+id/tutorial_viewpager"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
@ -22,15 +22,37 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.0" />
|
app:layout_constraintVertical_bias="0.0" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/tutorial_button_back"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:src="@drawable/baseline_navigate_before_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tutorial_viewpager" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tutorial_tabs"
|
android:id="@+id/tutorial_tabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="50dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/tutorial_button_next"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/tutorial_button_back"
|
||||||
app:tabBackground="@drawable/tutorial_tab_selector"
|
app:tabBackground="@drawable/tutorial_tab_selector"
|
||||||
app:tabGravity="center"
|
app:tabGravity="center"
|
||||||
app:tabIndicatorHeight="0dp"
|
app:tabIndicatorHeight="0dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/tutorial_button_next"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@drawable/baseline_navigate_next_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintTop_toBottomOf="@+id/tutorial_viewpager" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -8,7 +8,7 @@
|
||||||
android:paddingRight="32sp"
|
android:paddingRight="32sp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.tutorial.tabs.TutorialFragmentStart">
|
tools:context=".ui.tutorial.tabs.TutorialFragment0Start">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_start_text"
|
android:id="@+id/tutorial_start_text"
|
||||||
|
@ -21,16 +21,4 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tutorial_start_icon_right"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text=">>>>>>"
|
|
||||||
android:textSize="64sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tutorial_start_text" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -8,7 +8,7 @@
|
||||||
android:paddingRight="32sp"
|
android:paddingRight="32sp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.tutorial.tabs.TutorialFragmentConcept">
|
tools:context=".ui.tutorial.tabs.TutorialFragment1Concept">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_concept_title"
|
android:id="@+id/tutorial_concept_title"
|
||||||
|
@ -28,11 +28,10 @@
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/tutorial_concept_text"
|
android:text="@string/tutorial_concept_text"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tutorial_concept_title"
|
app:layout_constraintTop_toBottomOf="@+id/tutorial_concept_title"
|
||||||
app:layout_constraintVertical_bias="0.19999999" />
|
app:layout_constraintBottom_toTopOf="@id/tutorial_concept_badge_version_label"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_concept_text_2"
|
android:id="@+id/tutorial_concept_text_2"
|
||||||
|
@ -60,6 +59,22 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tutorial_concept_text"
|
app:layout_constraintTop_toBottomOf="@+id/tutorial_concept_text"
|
||||||
|
tools:text="0.0.7"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tutorial_concept_badge_version_label"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/tutorial_concept_label_version"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/tutorial_concept_badge_version"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,7 +7,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingLeft="32sp"
|
android:paddingLeft="32sp"
|
||||||
android:paddingRight="32sp"
|
android:paddingRight="32sp"
|
||||||
tools:context=".ui.tutorial.tabs.TutorialFragmentUsage">
|
tools:context=".ui.tutorial.tabs.TutorialFragment2Usage">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_usage_title"
|
android:id="@+id/tutorial_usage_title"
|
||||||
|
@ -39,12 +39,12 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:src="@drawable/home_round_screen"
|
android:src="@drawable/tutorial_home_screen"
|
||||||
app:layout_constraintBottom_toTopOf="@id/tutorial_usage_text_2"
|
app:layout_constraintBottom_toTopOf="@id/tutorial_usage_text_2"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@+id/tutorial_usage_text"
|
app:layout_constraintStart_toStartOf="@+id/tutorial_usage_text"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tutorial_usage_text"
|
app:layout_constraintTop_toBottomOf="@id/tutorial_usage_text"
|
||||||
app:srcCompat="@drawable/home_round_screen"
|
app:srcCompat="@drawable/tutorial_home_screen"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
64
app/src/main/res/layout/tutorial_3_app_list.xml
Normal file
64
app/src/main/res/layout/tutorial_3_app_list.xml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/tutorial_usage_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingLeft="32sp"
|
||||||
|
android:paddingRight="32sp"
|
||||||
|
tools:context=".ui.tutorial.tabs.TutorialFragment3AppList">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tutorial_app_list_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/tutorial_app_list_title"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tutorial_app_list_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/tutorial_app_list_text"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/tutorial_app_list_screen"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tutorial_app_list_title" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/tutorial_app_list_screen"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/tutorial_app_list"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/tutorial_app_list_text_2"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/tutorial_app_list_text"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tutorial_app_list_text"
|
||||||
|
app:srcCompat="@drawable/tutorial_app_list"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tutorial_app_list_text_2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/tutorial_app_list_text_2"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tutorial_app_list_screen" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -8,7 +8,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingLeft="32sp"
|
android:paddingLeft="32sp"
|
||||||
android:paddingRight="32sp"
|
android:paddingRight="32sp"
|
||||||
tools:context=".ui.tutorial.tabs.TutorialFragmentSetup">
|
tools:context=".ui.tutorial.tabs.TutorialFragment4Setup">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_setup_title"
|
android:id="@+id/tutorial_setup_title"
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tutorial_setup_title" />
|
app:layout_constraintTop_toBottomOf="@id/tutorial_setup_title" />
|
||||||
|
|
||||||
<fragment
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/tutorial_setup_actions_rview_fragment"
|
android:id="@+id/tutorial_setup_actions_rview_fragment"
|
||||||
android:name="de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActionsRecycler"
|
android:name="de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActionsRecycler"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
|
@ -7,7 +7,7 @@
|
||||||
android:paddingRight="32sp"
|
android:paddingRight="32sp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.tutorial.tabs.TutorialFragmentFinish">
|
tools:context=".ui.tutorial.tabs.TutorialFragment5Finish">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tutorial_finish_title"
|
android:id="@+id/tutorial_finish_title"
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
android:id="@+id/tutorial_finish_text"
|
android:id="@+id/tutorial_finish_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/tutorial_finish_text"
|
android:text="@string/tutorial_finish_text"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/tutorial_finish_button_start"
|
app:layout_constraintBottom_toTopOf="@id/tutorial_finish_button_start"
|
Binary file not shown.
Before Width: | Height: | Size: 612 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue