mirror of
https://github.com/jrpie/Launcher.git
synced 2025-04-11 14:54:31 +02:00
Compare commits
66 commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
132 changed files with 2800 additions and 1182 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
|
||||
|
|
|
@ -59,6 +59,7 @@ The following gestures are available:
|
|||
- swipe up / down / left / right,
|
||||
- swipe with two fingers,
|
||||
- swipe on the left / right resp. top / bottom edge,
|
||||
- tap, then swipe up / down / left / right,
|
||||
- draw < / > / V / Λ
|
||||
- click on date / time,
|
||||
- double click,
|
||||
|
|
|
@ -23,8 +23,8 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
compileSdk 35
|
||||
versionCode 38
|
||||
versionName "0.0.22"
|
||||
versionCode 43
|
||||
versionName "0.1.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
@ -85,16 +85,17 @@ android {
|
|||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.activity:activity-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'androidx.core:core-ktx:1.15.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||
|
|
|
@ -19,6 +19,16 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/launcherBaseTheme"
|
||||
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
|
||||
android:name=".ui.HomeActivity"
|
||||
android:clearTaskOnLaunch="true"
|
||||
|
@ -75,7 +85,7 @@
|
|||
<service
|
||||
android:name=".actions.lock.LauncherAccessibilityService"
|
||||
android:exported="true"
|
||||
android:label="@string/accessibility_service_name"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
|
@ -87,4 +97,4 @@
|
|||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.IntentFilter
|
|||
import android.content.SharedPreferences
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.os.UserHandle
|
||||
|
@ -15,15 +14,18 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.preference.PreferenceManager
|
||||
import de.jrpie.android.launcher.actions.TorchManager
|
||||
import de.jrpie.android.launcher.apps.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.isPrivateSpaceLocked
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
|
||||
import de.jrpie.android.launcher.preferences.resetPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Application : android.app.Application() {
|
||||
val apps = MutableLiveData<List<DetailedAppInfo>>()
|
||||
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
||||
val privateSpaceLocked = MutableLiveData<Boolean>()
|
||||
|
||||
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
@ -82,10 +84,12 @@ class Application : android.app.Application() {
|
|||
}
|
||||
|
||||
var torchManager: TorchManager? = null
|
||||
private var customAppNames: HashMap<AppInfo, String>? = null
|
||||
private var customAppNames: HashMap<AbstractAppInfo, String>? = null
|
||||
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, pref ->
|
||||
if (pref == getString(R.string.settings_apps_custom_names_key)) {
|
||||
customAppNames = LauncherPreferences.apps().customNames()
|
||||
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
|
||||
loadApps()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,12 +110,10 @@ class Application : android.app.Application() {
|
|||
// Try to restore old preferences
|
||||
migratePreferencesToNewVersion(this)
|
||||
|
||||
// First time opening the app: set defaults and start tutorial
|
||||
// First time opening the app: set defaults
|
||||
// The tutorial is started from HomeActivity#onStart, as starting it here is blocked by android
|
||||
if (!LauncherPreferences.internal().started()) {
|
||||
resetPreferences(this)
|
||||
|
||||
LauncherPreferences.internal().started(true)
|
||||
openTutorial(this)
|
||||
}
|
||||
|
||||
|
||||
|
@ -132,21 +134,27 @@ class Application : android.app.Application() {
|
|||
it.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
|
||||
}
|
||||
}
|
||||
ContextCompat.registerReceiver(this, profileAvailabilityBroadcastReceiver, filter,
|
||||
ContextCompat.registerReceiver(
|
||||
this, profileAvailabilityBroadcastReceiver, filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
|
||||
removeUnusedShortcuts(this)
|
||||
}
|
||||
loadApps()
|
||||
}
|
||||
|
||||
fun getCustomAppNames(): HashMap<AppInfo, String> {
|
||||
fun getCustomAppNames(): HashMap<AbstractAppInfo, String> {
|
||||
return (customAppNames ?: LauncherPreferences.apps().customNames() ?: HashMap())
|
||||
.also { customAppNames = it }
|
||||
}
|
||||
|
||||
private fun loadApps() {
|
||||
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.Intent
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.LauncherApps.ShortcutQuery
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.UserHandle
|
||||
|
@ -18,25 +19,27 @@ import android.os.UserManager
|
|||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.actions.ShortcutAction
|
||||
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo
|
||||
import de.jrpie.android.launcher.apps.AppInfo
|
||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||
import de.jrpie.android.launcher.apps.DetailedPinnedShortcutInfo
|
||||
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||
import de.jrpie.android.launcher.apps.getPrivateSpaceUser
|
||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||
import androidx.core.net.toUri
|
||||
|
||||
|
||||
/* 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 REQUEST_SET_DEFAULT_HOME = 42
|
||||
|
||||
fun isDefaultHomeScreen(context: Context): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
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
|
||||
&& 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)
|
||||
context.startActivityForResult(
|
||||
|
@ -81,9 +84,43 @@ fun getUserFromId(userId: Int?, context: Context): UserHandle {
|
|||
return profiles.firstOrNull { it.hashCode() == userId } ?: profiles[0]
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
fun removeUnusedShortcuts(context: Context) {
|
||||
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
fun getShortcuts(profile: UserHandle): List<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) {
|
||||
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) })
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
|
@ -93,18 +130,19 @@ fun openInBrowser(url: String, context: Context) {
|
|||
}
|
||||
|
||||
fun openTutorial(context: Context) {
|
||||
context.startActivity(Intent(context, TutorialActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
})
|
||||
context.startActivity(Intent(context, TutorialActivity::class.java))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load all apps.
|
||||
*/
|
||||
fun getApps(packageManager: PackageManager, context: Context): MutableList<DetailedAppInfo> {
|
||||
val start = System.currentTimeMillis()
|
||||
val loadList = mutableListOf<DetailedAppInfo>()
|
||||
fun getApps(
|
||||
packageManager: PackageManager,
|
||||
context: Context
|
||||
): MutableList<AbstractDetailedAppInfo> {
|
||||
var start = System.currentTimeMillis()
|
||||
val loadList = mutableListOf<AbstractDetailedAppInfo>()
|
||||
|
||||
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
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)
|
||||
val allApps = packageManager.queryIntentActivities(i, 0)
|
||||
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(
|
||||
app,
|
||||
ri.loadLabel(packageManager),
|
||||
|
@ -151,22 +189,24 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList<Detai
|
|||
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)")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
start = System.currentTimeMillis()
|
||||
LauncherPreferences.apps().pinnedShortcuts()
|
||||
?.mapNotNull { DetailedPinnedShortcutInfo.fromPinnedShortcutInfo(it, context) }
|
||||
?.let {
|
||||
end = System.currentTimeMillis()
|
||||
Log.i(LOG_TAG, "${it.size} shortcuts loaded (${end - start}ms)")
|
||||
loadList.addAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
return loadList
|
||||
}
|
||||
|
||||
|
||||
// Used in Tutorial and Settings `ActivityOnResult`
|
||||
fun saveListActivityChoice(data: Intent?) {
|
||||
val forGesture = data?.getStringExtra("forGesture") ?: return
|
||||
Gesture.byId(forGesture)?.let { Action.setActionForGesture(it, Action.fromIntent(data)) }
|
||||
}
|
||||
|
||||
|
||||
// used for the bug report button
|
||||
fun getDeviceInfo(): String {
|
||||
return """
|
||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.actions
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences.Editor
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
|
@ -12,6 +11,7 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import androidx.core.content.edit
|
||||
|
||||
|
||||
@Serializable
|
||||
|
@ -29,10 +29,6 @@ sealed interface Action {
|
|||
prefEditor.putString(id, Json.encodeToString(this))
|
||||
}
|
||||
|
||||
fun writeToIntent(intent: Intent) {
|
||||
intent.putExtra("action", Json.encodeToString(this))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun forGesture(gesture: Gesture): Action? {
|
||||
|
@ -44,23 +40,23 @@ sealed interface Action {
|
|||
}
|
||||
|
||||
fun resetToDefaultActions(context: Context) {
|
||||
val editor = LauncherPreferences.getSharedPreferences().edit()
|
||||
val boundActions = HashSet<String>()
|
||||
Gesture.entries.forEach { gesture ->
|
||||
context.resources
|
||||
.getStringArray(gesture.defaultsResource)
|
||||
.filterNot { boundActions.contains(it) }
|
||||
.map { Pair(it, Json.decodeFromString<Action>(it)) }
|
||||
.firstOrNull { it.second.isAvailable(context) }
|
||||
?.apply {
|
||||
// allow to bind CHOOSE to multiple gestures
|
||||
if (second != LauncherAction.CHOOSE) {
|
||||
boundActions.add(first)
|
||||
LauncherPreferences.getSharedPreferences().edit {
|
||||
val boundActions = HashSet<String>()
|
||||
Gesture.entries.forEach { gesture ->
|
||||
context.resources
|
||||
.getStringArray(gesture.defaultsResource)
|
||||
.filterNot { boundActions.contains(it) }
|
||||
.map { Pair(it, Json.decodeFromString<Action>(it)) }
|
||||
.firstOrNull { it.second.isAvailable(context) }
|
||||
?.apply {
|
||||
// allow to bind CHOOSE to multiple gestures
|
||||
if (second != LauncherAction.CHOOSE) {
|
||||
boundActions.add(first)
|
||||
}
|
||||
second.bindToGesture(this@edit, gesture.id)
|
||||
}
|
||||
second.bindToGesture(editor, gesture.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun setActionForGesture(gesture: Gesture, action: Action?) {
|
||||
|
@ -68,15 +64,15 @@ sealed interface Action {
|
|||
clearActionForGesture(gesture)
|
||||
return
|
||||
}
|
||||
val editor = LauncherPreferences.getSharedPreferences().edit()
|
||||
action.bindToGesture(editor, gesture.id)
|
||||
editor.apply()
|
||||
LauncherPreferences.getSharedPreferences().edit {
|
||||
action.bindToGesture(this, gesture.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearActionForGesture(gesture: Gesture) {
|
||||
LauncherPreferences.getSharedPreferences().edit()
|
||||
.remove(gesture.id)
|
||||
.apply()
|
||||
LauncherPreferences.getSharedPreferences().edit {
|
||||
remove(gesture.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun launch(
|
||||
|
@ -87,6 +83,9 @@ sealed interface Action {
|
|||
) {
|
||||
if (action != null && action.invoke(context)) {
|
||||
if (context is Activity) {
|
||||
// There does not seem to be a good alternative to overridePendingTransition.
|
||||
// Note that we can't use overrideActivityTransition here.
|
||||
@Suppress("deprecation")
|
||||
context.overridePendingTransition(animationIn, animationOut)
|
||||
}
|
||||
} else {
|
||||
|
@ -97,10 +96,5 @@ sealed interface Action {
|
|||
).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 de.jrpie.android.launcher.R
|
||||
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.ui.list.apps.openSettings
|
||||
import kotlinx.serialization.SerialName
|
||||
|
@ -67,7 +67,7 @@ class AppAction(val app: AppInfo) : Action {
|
|||
}
|
||||
|
||||
override fun getIcon(context: Context): Drawable? {
|
||||
return DetailedAppInfo.fromAppInfo(app, context)?.icon
|
||||
return DetailedAppInfo.fromAppInfo(app, context)?.getIcon(context)
|
||||
}
|
||||
|
||||
override fun isAvailable(context: Context): Boolean {
|
||||
|
|
|
@ -79,6 +79,13 @@ enum class Gesture(
|
|||
R.array.default_up_right,
|
||||
R.anim.bottom_up
|
||||
),
|
||||
TAP_AND_SWIPE_UP(
|
||||
"action.tap_up",
|
||||
R.string.settings_gesture_tap_up,
|
||||
R.string.settings_gesture_description_tap_up,
|
||||
R.array.default_up,
|
||||
R.anim.bottom_up
|
||||
),
|
||||
SWIPE_UP_DOUBLE(
|
||||
"action.double_up",
|
||||
R.string.settings_gesture_double_up,
|
||||
|
@ -107,6 +114,13 @@ enum class Gesture(
|
|||
R.array.default_down_right,
|
||||
R.anim.top_down
|
||||
),
|
||||
TAP_AND_SWIPE_DOWN(
|
||||
"action.tap_down",
|
||||
R.string.settings_gesture_tap_down,
|
||||
R.string.settings_gesture_description_tap_down,
|
||||
R.array.default_down,
|
||||
R.anim.bottom_up
|
||||
),
|
||||
SWIPE_DOWN_DOUBLE(
|
||||
"action.double_down",
|
||||
R.string.settings_gesture_double_down,
|
||||
|
@ -135,6 +149,13 @@ enum class Gesture(
|
|||
R.array.default_messengers,
|
||||
R.anim.right_left
|
||||
),
|
||||
TAP_AND_SWIPE_LEFT(
|
||||
"action.tap_left",
|
||||
R.string.settings_gesture_tap_left,
|
||||
R.string.settings_gesture_description_tap_left,
|
||||
R.array.default_messengers,
|
||||
R.anim.right_left
|
||||
),
|
||||
SWIPE_LEFT_DOUBLE(
|
||||
"action.double_left",
|
||||
R.string.settings_gesture_double_left,
|
||||
|
@ -163,6 +184,13 @@ enum class Gesture(
|
|||
R.array.default_right_bottom,
|
||||
R.anim.left_right
|
||||
),
|
||||
TAP_AND_SWIPE_RIGHT(
|
||||
"action.tap_right",
|
||||
R.string.settings_gesture_tap_right,
|
||||
R.string.settings_gesture_description_tap_right,
|
||||
R.array.default_right,
|
||||
R.anim.left_right
|
||||
),
|
||||
SWIPE_RIGHT_DOUBLE(
|
||||
"action.double_right",
|
||||
R.string.settings_gesture_double_right,
|
||||
|
@ -222,7 +250,7 @@ enum class Gesture(
|
|||
"action.back",
|
||||
R.string.settings_gesture_back,
|
||||
R.string.settings_gesture_description_back,
|
||||
R.array.default_up
|
||||
R.array.default_back
|
||||
);
|
||||
|
||||
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 {
|
||||
return when (this) {
|
||||
SWIPE_UP_DOUBLE,
|
||||
|
|
|
@ -11,8 +11,11 @@ import android.view.KeyEvent
|
|||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import de.jrpie.android.launcher.Application
|
||||
import de.jrpie.android.launcher.BuildConfig
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
|
||||
import de.jrpie.android.launcher.apps.AppFilter
|
||||
import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked
|
||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
||||
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
|
@ -66,7 +69,11 @@ enum class LauncherAction(
|
|||
R.string.list_other_list_private_space,
|
||||
R.drawable.baseline_security_24,
|
||||
{ context ->
|
||||
openAppsList(context, private = true)
|
||||
if ((context.applicationContext as Application).privateSpaceLocked.value != true
|
||||
|| !hidePrivateSpaceWhenLocked(context)
|
||||
) {
|
||||
openAppsList(context, private = true)
|
||||
}
|
||||
},
|
||||
available = { _ ->
|
||||
isPrivateSpaceSupported()
|
||||
|
@ -82,22 +89,38 @@ enum class LauncherAction(
|
|||
VOLUME_UP(
|
||||
"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",
|
||||
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(
|
||||
"next_track",
|
||||
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(
|
||||
"previous_track",
|
||||
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",
|
||||
|
@ -111,6 +134,14 @@ enum class LauncherAction(
|
|||
R.drawable.baseline_settings_applications_24,
|
||||
::expandSettingsPanel
|
||||
),
|
||||
RECENT_APPS(
|
||||
"recent_apps",
|
||||
R.string.list_other_recent_apps,
|
||||
R.drawable.baseline_apps_24,
|
||||
LauncherAccessibilityService::openRecentApps,
|
||||
false,
|
||||
{ _ -> BuildConfig.USE_ACCESSIBILITY_SERVICE }
|
||||
),
|
||||
LOCK_SCREEN(
|
||||
"lock_screen",
|
||||
R.string.list_other_lock_screen,
|
||||
|
@ -121,7 +152,13 @@ enum class LauncherAction(
|
|||
"toggle_torch",
|
||||
R.string.list_other_torch,
|
||||
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, {});
|
||||
|
||||
|
@ -155,56 +192,28 @@ enum class LauncherAction(
|
|||
|
||||
|
||||
/* Media player actions */
|
||||
|
||||
private fun audioNextTrack(context: Context) {
|
||||
|
||||
private fun audioManagerPressKey(context: Context, key: Int) {
|
||||
val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
val eventTime: Long = SystemClock.uptimeMillis()
|
||||
|
||||
val downEvent =
|
||||
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT, 0)
|
||||
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, key, 0)
|
||||
mAudioManager.dispatchMediaKeyEvent(downEvent)
|
||||
|
||||
val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT, 0)
|
||||
val upEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, key, 0)
|
||||
mAudioManager.dispatchMediaKeyEvent(upEvent)
|
||||
|
||||
}
|
||||
|
||||
private fun audioPreviousTrack(context: Context) {
|
||||
val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
val eventTime: Long = SystemClock.uptimeMillis()
|
||||
|
||||
val downEvent =
|
||||
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0)
|
||||
mAudioManager.dispatchMediaKeyEvent(downEvent)
|
||||
|
||||
val upEvent =
|
||||
KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0)
|
||||
mAudioManager.dispatchMediaKeyEvent(upEvent)
|
||||
}
|
||||
|
||||
private fun audioVolumeUp(context: Context) {
|
||||
private fun audioVolumeAdjust(context: Context, direction: Int) {
|
||||
val audioManager =
|
||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
audioManager.adjustStreamVolume(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_RAISE,
|
||||
direction,
|
||||
AudioManager.FLAG_SHOW_UI
|
||||
)
|
||||
}
|
||||
|
||||
private fun audioVolumeDown(context: Context) {
|
||||
val audioManager =
|
||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
audioManager.adjustStreamVolume(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_LOWER,
|
||||
AudioManager.FLAG_SHOW_UI
|
||||
)
|
||||
}
|
||||
/* End media player actions */
|
||||
|
||||
private fun toggleTorch(context: Context) {
|
||||
|
@ -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) {
|
||||
context.startActivity(Intent(context, SettingsActivity::class.java))
|
||||
}
|
||||
|
@ -320,5 +338,4 @@ private class LauncherActionSerializer : KSerializer<LauncherAction> {
|
|||
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 {
|
||||
private const val TAG = "Launcher Accessibility"
|
||||
private const val ACTION_REQUEST_ENABLE = "ACTION_REQUEST_ENABLE"
|
||||
const val ACTION_LOCK_SCREEN = "ACTION_LOCK_SCREEN"
|
||||
const val ACTION_RECENT_APPS = "ACTION_RECENT_APPS"
|
||||
|
||||
fun lockScreen(context: Context) {
|
||||
private fun invoke(context: Context, action: String, failureMessageRes: Int) {
|
||||
try {
|
||||
context.startService(
|
||||
Intent(
|
||||
context,
|
||||
LauncherAccessibilityService::class.java
|
||||
).apply {
|
||||
action = ACTION_LOCK_SCREEN
|
||||
this.action = action
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.alert_lock_screen_failed),
|
||||
context.getString(failureMessageRes),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun lockScreen(context: Context) {
|
||||
if (!isEnabled(context)) {
|
||||
showEnableDialog(context)
|
||||
} else {
|
||||
invoke(context, ACTION_LOCK_SCREEN, R.string.alert_lock_screen_failed)
|
||||
}
|
||||
}
|
||||
|
||||
fun openRecentApps(context: Context) {
|
||||
if (!isEnabled(context)) {
|
||||
showEnableDialog(context)
|
||||
} else {
|
||||
invoke(context, ACTION_RECENT_APPS, R.string.alert_recent_apps_failed)
|
||||
}
|
||||
}
|
||||
|
||||
fun isEnabled(context: Context): Boolean {
|
||||
val enabledServices = Settings.Secure.getString(
|
||||
context.contentResolver,
|
||||
|
@ -58,7 +76,7 @@ class LauncherAccessibilityService : AccessibilityService() {
|
|||
setView(R.layout.dialog_consent_accessibility)
|
||||
setTitle(R.string.dialog_consent_accessibility_title)
|
||||
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) { _, _ -> }
|
||||
}.create().also { it.show() }.apply {
|
||||
|
@ -94,7 +112,9 @@ class LauncherAccessibilityService : AccessibilityService() {
|
|||
}
|
||||
|
||||
when (action) {
|
||||
ACTION_REQUEST_ENABLE -> {} // do nothing
|
||||
ACTION_LOCK_SCREEN -> handleLockScreen()
|
||||
ACTION_RECENT_APPS -> performGlobalAction(GLOBAL_ACTION_RECENTS)
|
||||
}
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
|
|
|
@ -6,10 +6,10 @@ import android.widget.Button
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import de.jrpie.android.launcher.BuildConfig
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
enum class LockMethod(
|
||||
private val lock: (Context) -> Unit,
|
||||
private val isEnabled: (Context) -> Boolean,
|
||||
|
|
|
@ -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.AppAction
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.actions.ShortcutAction
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import java.util.Locale
|
||||
import kotlin.text.Regex.Companion.escape
|
||||
|
@ -18,13 +19,14 @@ class AppFilter(
|
|||
var privateSpaceVisibility: AppSetVisibility = AppSetVisibility.VISIBLE
|
||||
) {
|
||||
|
||||
operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> {
|
||||
operator fun invoke(apps: List<AbstractDetailedAppInfo>): List<AbstractDetailedAppInfo> {
|
||||
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 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 ->
|
||||
favoritesVisibility.predicate(favorites, info)
|
||||
|
@ -35,9 +37,13 @@ class AppFilter(
|
|||
if (LauncherPreferences.apps().hideBoundApps()) {
|
||||
val boundApps = Gesture.entries
|
||||
.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()
|
||||
apps = apps.filterNot { info -> boundApps.contains(info.app) }
|
||||
apps = apps.filterNot { info -> boundApps.contains(info.getRawInfo()) }
|
||||
}
|
||||
|
||||
// normalize text for search
|
||||
|
@ -57,11 +63,11 @@ class AppFilter(
|
|||
if (query.isEmpty()) {
|
||||
return apps
|
||||
} else {
|
||||
val r: MutableList<DetailedAppInfo> = ArrayList()
|
||||
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
|
||||
val r: MutableList<AbstractDetailedAppInfo> = ArrayList()
|
||||
val appsSecondary: MutableList<AbstractDetailedAppInfo> = ArrayList()
|
||||
val normalizedQuery: String = normalize(query)
|
||||
for (item in apps) {
|
||||
val itemLabel: String = normalize(item.getCustomLabel(context).toString())
|
||||
val itemLabel: String = normalize(item.getCustomLabel(context))
|
||||
|
||||
if (itemLabel.startsWith(normalizedQuery)) {
|
||||
r.add(item)
|
||||
|
@ -77,11 +83,11 @@ class AppFilter(
|
|||
|
||||
companion object {
|
||||
enum class AppSetVisibility(
|
||||
val predicate: (set: Set<AppInfo>, DetailedAppInfo) -> Boolean
|
||||
val predicate: (set: Set<AbstractAppInfo>, AbstractDetailedAppInfo) -> Boolean
|
||||
) {
|
||||
VISIBLE({ _, _ -> true }),
|
||||
HIDDEN({ set, appInfo -> !set.contains(appInfo.app) }),
|
||||
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.app) }),
|
||||
HIDDEN({ set, appInfo -> !set.contains(appInfo.getRawInfo()) }),
|
||||
EXCLUSIVE({ set, appInfo -> set.contains(appInfo.getRawInfo()) }),
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,33 +4,18 @@ import android.app.Service
|
|||
import android.content.Context
|
||||
import android.content.pm.LauncherActivityInfo
|
||||
import android.content.pm.LauncherApps
|
||||
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||
import de.jrpie.android.launcher.getUserFromId
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Represents an app installed on the users device.
|
||||
* Contains the minimal amount of data required to identify the app.
|
||||
*/
|
||||
@Serializable
|
||||
class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) {
|
||||
|
||||
fun serialize(): String {
|
||||
return Json.encodeToString(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other is AppInfo) {
|
||||
return other.user == user && other.packageName == packageName
|
||||
&& other.activityName == activityName
|
||||
}
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return packageName.hashCode()
|
||||
}
|
||||
@SerialName("app")
|
||||
data class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER): AbstractAppInfo {
|
||||
|
||||
fun getLauncherActivityInfo(
|
||||
context: Context
|
||||
|
@ -41,17 +26,4 @@ class AppInfo(val packageName: String, val activityName: String?, val user: Int
|
|||
return activityList.firstOrNull { app -> app.name == activityName }
|
||||
?: activityList.firstOrNull()
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "AppInfo {package=$packageName, activity=$activityName, user=$user}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INVALID_USER = -1
|
||||
|
||||
fun deserialize(serialized: String): AppInfo {
|
||||
return Json.decodeFromString(serialized)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,20 +4,21 @@ import android.content.Context
|
|||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.LauncherActivityInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import de.jrpie.android.launcher.Application
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import android.os.UserHandle
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
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.
|
||||
*/
|
||||
class DetailedAppInfo(
|
||||
val app: AppInfo,
|
||||
val label: CharSequence,
|
||||
val icon: Drawable,
|
||||
val isPrivateSpaceApp: Boolean,
|
||||
val isSystemApp: Boolean = false,
|
||||
) {
|
||||
private val app: AppInfo,
|
||||
private val label: CharSequence,
|
||||
private val icon: Drawable,
|
||||
private val privateSpace: Boolean,
|
||||
private val removable: Boolean = true,
|
||||
): AbstractDetailedAppInfo {
|
||||
|
||||
constructor(activityInfo: LauncherActivityInfo, private: Boolean) : this(
|
||||
AppInfo(
|
||||
|
@ -28,29 +29,41 @@ class DetailedAppInfo(
|
|||
activityInfo.label,
|
||||
activityInfo.getBadgedIcon(0),
|
||||
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?) {
|
||||
|
||||
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 getIcon(context: Context): Drawable {
|
||||
return icon
|
||||
}
|
||||
|
||||
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 {
|
||||
fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? {
|
||||
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
|
||||
return userManager.isQuietModeEnabled(privateSpaceUser)
|
||||
}
|
||||
|
||||
fun lockPrivateSpace(context: Context, lock: Boolean) {
|
||||
if (!isPrivateSpaceSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
// silently return when trying to unlock but hide when locked is set
|
||||
if (!lock && hidePrivateSpaceWhenLocked(context)) {
|
||||
return
|
||||
}
|
||||
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val privateSpaceUser = getPrivateSpaceUser(context) ?: return
|
||||
userManager.requestQuietModeEnabled(lock, privateSpaceUser)
|
||||
|
@ -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.preference.Preference
|
||||
import de.jrpie.android.launcher.R
|
||||
import androidx.core.graphics.toColorInt
|
||||
|
||||
class ColorPreference(context: Context, attrs: AttributeSet?) :
|
||||
Preference(context, attrs) {
|
||||
|
@ -52,7 +53,7 @@ class ColorPreference(context: Context, attrs: AttributeSet?) :
|
|||
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
||||
setView(R.layout.dialog_choose_color)
|
||||
setTitle(R.string.dialog_choose_color_title)
|
||||
setPositiveButton(R.string.dialog_select_color_ok) { _, _ ->
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
persistInt(currentColor)
|
||||
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 afterTextChanged(editable: Editable?) {
|
||||
preview.hasFocus() || return
|
||||
val newText = editable?.toString()
|
||||
newText.isNullOrBlank() && return
|
||||
val newText = editable?.toString() ?: return
|
||||
newText.isBlank() && return
|
||||
try {
|
||||
val newColor = Color.parseColor(newText.toString())
|
||||
val newColor = newText.toColorInt()
|
||||
currentColor = newColor
|
||||
updateColor(false)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
|
|
|
@ -5,8 +5,9 @@ import java.util.Set;
|
|||
|
||||
import de.jrpie.android.launcher.R;
|
||||
import de.jrpie.android.launcher.actions.lock.LockMethod;
|
||||
import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer;
|
||||
import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer;
|
||||
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
||||
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
||||
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
||||
import de.jrpie.android.launcher.preferences.theme.Background;
|
||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
||||
import de.jrpie.android.launcher.preferences.theme.Font;
|
||||
|
@ -20,20 +21,24 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
|||
r = R.class,
|
||||
value = {
|
||||
@PreferenceGroup(name = "internal", prefix = "settings_internal_", suffix = "_key", value = {
|
||||
// set after the user finished the tutorial
|
||||
@Preference(name = "started", type = boolean.class, defaultValue = "false"),
|
||||
@Preference(name = "started_time", type = long.class),
|
||||
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
|
||||
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
||||
}),
|
||||
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
||||
@Preference(name = "favorites", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
|
||||
@Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
|
||||
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class),
|
||||
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||
@Preference(name = "hidden", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||
@Preference(name = "pinned_shortcuts", type = Set.class, serializer = SetPinnedShortcutInfoPreferenceSerializer.class),
|
||||
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAbstractAppInfoStringPreferenceSerializer.class),
|
||||
@Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"),
|
||||
@Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"),
|
||||
@Preference(name = "hide_private_space_apps", type = boolean.class, defaultValue = "false"),
|
||||
}),
|
||||
@PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = {
|
||||
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT")
|
||||
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT"),
|
||||
@Preference(name = "reverse_layout", type = boolean.class, defaultValue = "false")
|
||||
}),
|
||||
@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 = {
|
||||
@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"),
|
||||
}),
|
||||
@PreferenceGroup(name = "functionality", prefix = "settings_functionality_", suffix = "_key", value = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package de.jrpie.android.launcher.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -27,8 +28,10 @@ enum class ListLayout(
|
|||
GRID(
|
||||
{ c ->
|
||||
val displayMetrics = c.resources.displayMetrics
|
||||
val widthSp = displayMetrics.widthPixels / displayMetrics.scaledDensity
|
||||
GridLayoutManager(c, (widthSp / 90).toInt())
|
||||
val widthColumnPx =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 90f, displayMetrics)
|
||||
val numColumns = (displayMetrics.widthPixels / widthColumnPx).toInt()
|
||||
GridLayoutManager(c, numColumns)
|
||||
},
|
||||
R.layout.list_apps_row_variant_grid,
|
||||
false
|
||||
|
|
|
@ -5,9 +5,12 @@ import android.util.Log
|
|||
import de.jrpie.android.launcher.BuildConfig
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
import de.jrpie.android.launcher.apps.AppInfo
|
||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||
import de.jrpie.android.launcher.ui.HomeActivity
|
||||
|
||||
|
@ -15,7 +18,7 @@ import de.jrpie.android.launcher.ui.HomeActivity
|
|||
* Increase when breaking changes are introduced and write an appropriate case in
|
||||
* `migratePreferencesToNewVersion`
|
||||
*/
|
||||
const val PREFERENCE_VERSION = 3
|
||||
const val PREFERENCE_VERSION = 4
|
||||
const val UNKNOWN_PREFERENCE_VERSION = -1
|
||||
private const val TAG = "Launcher - Preferences"
|
||||
|
||||
|
@ -44,6 +47,10 @@ fun migratePreferencesToNewVersion(context: Context) {
|
|||
migratePreferencesFromVersion2()
|
||||
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
|
||||
}
|
||||
3 -> {
|
||||
migratePreferencesFromVersion3()
|
||||
Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).")
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
|
@ -66,16 +73,16 @@ fun resetPreferences(context: Context) {
|
|||
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
||||
|
||||
|
||||
val hidden: MutableSet<AppInfo> = mutableSetOf()
|
||||
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
||||
val launcher = DetailedAppInfo.fromAppInfo(
|
||||
AppInfo(
|
||||
BuildConfig.APPLICATION_ID,
|
||||
HomeActivity::class.java.name,
|
||||
AppInfo.INVALID_USER
|
||||
INVALID_USER
|
||||
), context
|
||||
)
|
||||
launcher?.app?.let { hidden.add(it) }
|
||||
Log.i(TAG,"Hiding ${launcher?.app}")
|
||||
launcher?.getRawInfo()?.let { hidden.add(it) }
|
||||
Log.i(TAG,"Hiding ${launcher?.getRawInfo()}")
|
||||
LauncherPreferences.apps().hidden(hidden)
|
||||
|
||||
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.LauncherAction
|
||||
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.PREFERENCE_VERSION
|
||||
import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import androidx.core.content.edit
|
||||
|
||||
|
||||
@Serializable
|
||||
@Suppress("unused")
|
||||
private class LegacyMapEntry(val key: AppInfo, val value: String)
|
||||
|
||||
private fun serializeMapAppInfo(value: Map<AppInfo, String>?): Set<String>? {
|
||||
return value?.map { (key, value) ->
|
||||
Json.encodeToString(LegacyMapEntry(key, value))
|
||||
}?.toSet()
|
||||
}
|
||||
|
||||
|
||||
val oldLauncherActionIds: Map<String, LauncherAction> =
|
||||
mapOf(
|
||||
|
@ -77,7 +90,7 @@ private fun Action.Companion.legacyFromPreference(id: String): Action? {
|
|||
|
||||
private fun migrateAppInfoStringMap(key: String) {
|
||||
val preferences = LauncherPreferences.getSharedPreferences()
|
||||
MapAppInfoStringPreferenceSerializer().serialize(
|
||||
serializeMapAppInfo(
|
||||
preferences.getStringSet(key, setOf())?.mapNotNull { entry ->
|
||||
try {
|
||||
val obj = JSONObject(entry)
|
||||
|
@ -89,7 +102,7 @@ private fun migrateAppInfoStringMap(key: String) {
|
|||
}
|
||||
}?.toMap(HashMap())
|
||||
)?.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::serialize)
|
||||
.toSet()
|
||||
.let { LauncherPreferences.getSharedPreferences().edit().putStringSet(key, it).apply() }
|
||||
.let { LauncherPreferences.getSharedPreferences().edit { putStringSet(key, it) } }
|
||||
}
|
||||
|
||||
private fun migrateAction(key: String) {
|
||||
Action.legacyFromPreference(key)?.let { action ->
|
||||
LauncherPreferences.getSharedPreferences().edit()
|
||||
.putString(key, Json.encodeToString(action))
|
||||
.remove("$key.app")
|
||||
.remove("$key.user")
|
||||
.apply()
|
||||
LauncherPreferences.getSharedPreferences().edit {
|
||||
putString(key, Json.encodeToString(action))
|
||||
.remove("$key.app")
|
||||
.remove("$key.user")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
|||
* (see [PREFERENCE_VERSION])
|
||||
*/
|
||||
fun migratePreferencesFromVersion2() {
|
||||
assert(PREFERENCE_VERSION == 3)
|
||||
assert(LauncherPreferences.internal().versionCode() == 2)
|
||||
// previously there was no setting for this
|
||||
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
|
||||
LauncherPreferences.internal().versionCode(3)
|
||||
migratePreferencesFromVersion3()
|
||||
}
|
|
@ -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.theme.Background
|
||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||
|
||||
import androidx.core.content.edit
|
||||
|
||||
|
||||
private fun migrateStringPreference(
|
||||
|
@ -64,318 +64,317 @@ fun migratePreferencesFromVersionUnknown(context: Context) {
|
|||
return
|
||||
}
|
||||
|
||||
val newPrefs = LauncherPreferences.getSharedPreferences().edit()
|
||||
LauncherPreferences.getSharedPreferences().edit {
|
||||
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"startedBefore",
|
||||
"internal.started_before",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"startedBefore",
|
||||
"internal.started_before",
|
||||
false
|
||||
)
|
||||
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_volumeUpApp",
|
||||
"action.volume_up.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_volumeUpApp_user",
|
||||
"action.volume_up.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_volumeDownApp",
|
||||
"action.volume_down.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_volumeDownApp_user",
|
||||
"action.volume_down.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_timeApp", "action.time.app", "")
|
||||
migrateIntPreference(oldPrefs, newPrefs, "action_timeApp_user", "action.time.user", -1)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_dateApp", "action.date.app", "")
|
||||
migrateIntPreference(oldPrefs, newPrefs, "action_dateApp_user", "action.date.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_longClickApp",
|
||||
"action.long_click.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_longClickApp_user",
|
||||
"action.long_click.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleClickApp",
|
||||
"action.double_click.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleClickApp_user",
|
||||
"action.double_click.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_upApp", "action.up.app", "")
|
||||
migrateIntPreference(oldPrefs, newPrefs, "action_upApp_user", "action.up.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_up_leftApp",
|
||||
"action.up_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_up_leftApp_user",
|
||||
"action.up_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_up_rightApp",
|
||||
"action.up_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_up_rightApp_user",
|
||||
"action.up_right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleUpApp",
|
||||
"action.double_up.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleUpApp_user",
|
||||
"action.double_up.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_downApp", "action.down.app", "")
|
||||
migrateIntPreference(oldPrefs, newPrefs, "action_downApp_user", "action.down.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_down_leftApp",
|
||||
"action.down_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_down_leftApp_user",
|
||||
"action.down_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_down_rightApp",
|
||||
"action.down_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_down_rightApp_user",
|
||||
"action.down_right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleDownApp",
|
||||
"action.double_down.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleDownApp_user",
|
||||
"action.double_down.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_leftApp", "action.left.app", "")
|
||||
migrateIntPreference(oldPrefs, newPrefs, "action_leftApp_user", "action.left.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_left_topApp",
|
||||
"action.left_top.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_left_topApp_user",
|
||||
"action.left_top.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_left_bottomApp",
|
||||
"action.left_bottom.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_left_bottomApp_user",
|
||||
"action.left_bottom.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleLeftApp",
|
||||
"action.double_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleLeftApp_user",
|
||||
"action.double_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, newPrefs, "action_rightApp", "action.right.app", "")
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_rightApp_user",
|
||||
"action.right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_right_topApp",
|
||||
"action.right_top.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_right_topApp_user",
|
||||
"action.right_top.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_right_bottomApp",
|
||||
"action.right_bottom.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_right_bottomApp_user",
|
||||
"action.right_bottom.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleRightApp",
|
||||
"action.double_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"action_doubleRightApp_user",
|
||||
"action.double_right.user",
|
||||
-1
|
||||
)
|
||||
migrateBooleanPreference(oldPrefs, newPrefs, "timeVisible", "clock.time_visible", true)
|
||||
migrateBooleanPreference(oldPrefs, newPrefs, "dateVisible", "clock.date_visible", true)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"dateLocalized",
|
||||
"clock.date_localized",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"dateTimeFlip",
|
||||
"clock.date_time_flip",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"disableTimeout",
|
||||
"display.disable_timeout",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"useFullScreen",
|
||||
"display.use_full_screen",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"enableDoubleActions",
|
||||
"enabled_gestures.double_actions",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"enableEdgeActions",
|
||||
"enabled_gestures.edge_actions",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"searchAutoLaunch",
|
||||
"functionality.search_auto_launch",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
newPrefs,
|
||||
"searchAutoKeyboard",
|
||||
"functionality.search_auto_keyboard",
|
||||
true
|
||||
)
|
||||
|
||||
newPrefs.apply()
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_volumeUpApp",
|
||||
"action.volume_up.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_volumeUpApp_user",
|
||||
"action.volume_up.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_volumeDownApp",
|
||||
"action.volume_down.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_volumeDownApp_user",
|
||||
"action.volume_down.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, this, "action_timeApp", "action.time.app", "")
|
||||
migrateIntPreference(oldPrefs, this, "action_timeApp_user", "action.time.user", -1)
|
||||
migrateStringPreference(oldPrefs, this, "action_dateApp", "action.date.app", "")
|
||||
migrateIntPreference(oldPrefs, this, "action_dateApp_user", "action.date.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_longClickApp",
|
||||
"action.long_click.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_longClickApp_user",
|
||||
"action.long_click.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleClickApp",
|
||||
"action.double_click.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleClickApp_user",
|
||||
"action.double_click.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, this, "action_upApp", "action.up.app", "")
|
||||
migrateIntPreference(oldPrefs, this, "action_upApp_user", "action.up.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_up_leftApp",
|
||||
"action.up_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_up_leftApp_user",
|
||||
"action.up_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_up_rightApp",
|
||||
"action.up_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_up_rightApp_user",
|
||||
"action.up_right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleUpApp",
|
||||
"action.double_up.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleUpApp_user",
|
||||
"action.double_up.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, this, "action_downApp", "action.down.app", "")
|
||||
migrateIntPreference(oldPrefs, this, "action_downApp_user", "action.down.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_down_leftApp",
|
||||
"action.down_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_down_leftApp_user",
|
||||
"action.down_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_down_rightApp",
|
||||
"action.down_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_down_rightApp_user",
|
||||
"action.down_right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleDownApp",
|
||||
"action.double_down.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleDownApp_user",
|
||||
"action.double_down.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, this, "action_leftApp", "action.left.app", "")
|
||||
migrateIntPreference(oldPrefs, this, "action_leftApp_user", "action.left.user", -1)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_left_topApp",
|
||||
"action.left_top.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_left_topApp_user",
|
||||
"action.left_top.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_left_bottomApp",
|
||||
"action.left_bottom.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_left_bottomApp_user",
|
||||
"action.left_bottom.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleLeftApp",
|
||||
"action.double_left.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleLeftApp_user",
|
||||
"action.double_left.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(oldPrefs, this, "action_rightApp", "action.right.app", "")
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_rightApp_user",
|
||||
"action.right.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_right_topApp",
|
||||
"action.right_top.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_right_topApp_user",
|
||||
"action.right_top.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_right_bottomApp",
|
||||
"action.right_bottom.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_right_bottomApp_user",
|
||||
"action.right_bottom.user",
|
||||
-1
|
||||
)
|
||||
migrateStringPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleRightApp",
|
||||
"action.double_right.app",
|
||||
""
|
||||
)
|
||||
migrateIntPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"action_doubleRightApp_user",
|
||||
"action.double_right.user",
|
||||
-1
|
||||
)
|
||||
migrateBooleanPreference(oldPrefs, this, "timeVisible", "clock.time_visible", true)
|
||||
migrateBooleanPreference(oldPrefs, this, "dateVisible", "clock.date_visible", true)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"dateLocalized",
|
||||
"clock.date_localized",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"dateTimeFlip",
|
||||
"clock.date_time_flip",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"disableTimeout",
|
||||
"display.disable_timeout",
|
||||
false
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"useFullScreen",
|
||||
"display.use_full_screen",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"enableDoubleActions",
|
||||
"enabled_gestures.double_actions",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"enableEdgeActions",
|
||||
"enabled_gestures.edge_actions",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"searchAutoLaunch",
|
||||
"functionality.search_auto_launch",
|
||||
true
|
||||
)
|
||||
migrateBooleanPreference(
|
||||
oldPrefs,
|
||||
this,
|
||||
"searchAutoKeyboard",
|
||||
"functionality.search_auto_keyboard",
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
when (oldPrefs.getString("theme", "finn")) {
|
||||
"finn" -> {
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
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.PreferenceSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@ -10,40 +11,61 @@ import kotlinx.serialization.encodeToString
|
|||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
||||
// Serializers for [LauncherPreference$Config]
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class SetAppInfoPreferenceSerializer :
|
||||
PreferenceSerializer<java.util.Set<AppInfo>?, java.util.Set<java.lang.String>?> {
|
||||
class SetAbstractAppInfoPreferenceSerializer :
|
||||
PreferenceSerializer<java.util.Set<AbstractAppInfo>?, java.util.Set<java.lang.String>?> {
|
||||
@Throws(PreferenceSerializationException::class)
|
||||
override fun serialize(value: java.util.Set<AppInfo>?): java.util.Set<java.lang.String> {
|
||||
return value?.map(AppInfo::serialize)?.toHashSet() as java.util.Set<java.lang.String>
|
||||
override fun serialize(value: java.util.Set<AbstractAppInfo>?): java.util.Set<java.lang.String> {
|
||||
return value?.map(AbstractAppInfo::serialize)
|
||||
?.toHashSet() as java.util.Set<java.lang.String>
|
||||
}
|
||||
|
||||
@Throws(PreferenceSerializationException::class)
|
||||
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<AppInfo>? {
|
||||
return value?.map (java.lang.String::toString)?.map(AppInfo::deserialize)?.toHashSet() as? 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(AbstractAppInfo::deserialize)
|
||||
?.toHashSet() as? java.util.Set<AbstractAppInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MapAppInfoStringPreferenceSerializer :
|
||||
PreferenceSerializer<java.util.HashMap<AppInfo, String>?, java.util.Set<java.lang.String>?> {
|
||||
|
||||
@Serializable
|
||||
private class MapEntry(val key: AppInfo, val value: String)
|
||||
class SetPinnedShortcutInfoPreferenceSerializer :
|
||||
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
||||
@Throws(PreferenceSerializationException::class)
|
||||
override fun serialize(value: java.util.Set<PinnedShortcutInfo>?): java.util.Set<java.lang.String> {
|
||||
return value?.map { Json.encodeToString<PinnedShortcutInfo>(it) }
|
||||
?.toHashSet() as java.util.Set<java.lang.String>
|
||||
}
|
||||
|
||||
@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) ->
|
||||
Json.encodeToString(MapEntry(key, value))
|
||||
}?.toHashSet() as? java.util.Set<java.lang.String>
|
||||
}
|
||||
|
||||
@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()) {
|
||||
val entry = Json.decodeFromString<MapEntry>(it.toString())
|
||||
Pair(entry.key, entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.content.res.Resources
|
|||
import com.google.android.material.color.DynamicColors
|
||||
import de.jrpie.android.launcher.R
|
||||
|
||||
@Suppress("unused")
|
||||
enum class ColorTheme(
|
||||
private val id: Int,
|
||||
private val labelResource: Int,
|
||||
|
|
|
@ -27,10 +27,14 @@ fun View.blink(
|
|||
}
|
||||
|
||||
// Taken from: https://stackoverflow.com/a/30340794/12787264
|
||||
fun ImageView.transformGrayscale() {
|
||||
this.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
|
||||
setSaturation(0f)
|
||||
})
|
||||
fun ImageView.transformGrayscale(grayscale: Boolean) {
|
||||
this.colorFilter = if (grayscale) {
|
||||
ColorMatrixColorFilter(ColorMatrix().apply {
|
||||
setSaturation(0f)
|
||||
})
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ package de.jrpie.android.launcher.ui
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
@ -17,6 +17,7 @@ import de.jrpie.android.launcher.actions.Action
|
|||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.actions.LauncherAction
|
||||
import de.jrpie.android.launcher.databinding.HomeBinding
|
||||
import de.jrpie.android.launcher.openTutorial
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||
import java.util.Locale
|
||||
|
@ -55,24 +56,28 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
|||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
||||
super<UIObject>.onCreate()
|
||||
|
||||
|
||||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
|
||||
val width = displayMetrics.widthPixels
|
||||
val height = displayMetrics.heightPixels
|
||||
|
||||
touchGestureDetector = TouchGestureDetector(
|
||||
this,
|
||||
width,
|
||||
height,
|
||||
this, 0, 0,
|
||||
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
|
||||
)
|
||||
touchGestureDetector.updateScreenSize(windowManager)
|
||||
|
||||
// Initialise layout
|
||||
binding = HomeBinding.inflate(layoutInflater)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handle back key / gesture on Android 13+, cf. onKeyDown()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
@ -85,8 +90,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
|||
binding.buttonFallbackSettings.setOnClickListener {
|
||||
LauncherAction.SETTINGS.invoke(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
touchGestureDetector.updateScreenSize(windowManager)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -94,11 +102,25 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
|||
|
||||
super<UIObject>.onStart()
|
||||
|
||||
// If the tutorial was not finished, start it
|
||||
if (!LauncherPreferences.internal().started()) {
|
||||
openTutorial(this)
|
||||
}
|
||||
|
||||
LauncherPreferences.getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
|
||||
if (hasFocus && LauncherPreferences.display().hideNavigationBar()) {
|
||||
hideNavigationBar()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun updateSettingsFallbackButtonVisibility() {
|
||||
// If µLauncher settings can not be reached from any action bound to an enabled gesture,
|
||||
// show the fallback button.
|
||||
|
@ -186,6 +208,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
|||
// Only used pre Android 13, cf. onBackInvokedDispatcher
|
||||
handleBack()
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (Action.forGesture(Gesture.VOLUME_UP) == LauncherAction.VOLUME_UP) {
|
||||
// Let the OS handle the key event. This works better with some custom ROMs
|
||||
|
@ -207,7 +230,8 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return touchGestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
|
||||
touchGestureDetector.onTouchEvent(event)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setOnClicks() {
|
||||
|
|
|
@ -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
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Insets
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import kotlin.math.abs
|
||||
|
@ -12,8 +19,8 @@ import kotlin.math.tan
|
|||
|
||||
class TouchGestureDetector(
|
||||
private val context: Context,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
var width: Int,
|
||||
var height: Int,
|
||||
var edgeWidth: Float
|
||||
) {
|
||||
private val ANGULAR_THRESHOLD = tan(Math.PI / 6)
|
||||
|
@ -27,20 +34,31 @@ class TouchGestureDetector(
|
|||
|
||||
private val MIN_TRIANGLE_HEIGHT = 250
|
||||
|
||||
private val longPressHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
private var systemGestureInsetTop = 100
|
||||
private var systemGestureInsetBottom = 0
|
||||
private var systemGestureInsetLeft = 0
|
||||
private var systemGestureInsetRight = 0
|
||||
|
||||
|
||||
data class Vector(val x: Float, val y: Float) {
|
||||
fun absSquared(): Float {
|
||||
return this.x * this.x + this.y * this.y
|
||||
}
|
||||
|
||||
fun plus(vector: Vector): Vector {
|
||||
return Vector(this.x + vector.x, this.y + vector.y)
|
||||
}
|
||||
|
||||
fun max(other: Vector): Vector {
|
||||
return Vector(max(this.x, other.x), max(this.y, other.y))
|
||||
}
|
||||
|
||||
fun min(other: Vector): Vector {
|
||||
return Vector(min(this.x, other.x), min(this.y, other.y))
|
||||
}
|
||||
|
||||
operator fun minus(vector: Vector): Vector {
|
||||
return Vector(this.x - vector.x, this.y - vector.y)
|
||||
}
|
||||
|
@ -57,16 +75,35 @@ class TouchGestureDetector(
|
|||
fun sizeSquared(): Float {
|
||||
return (max - min).absSquared()
|
||||
}
|
||||
|
||||
fun getDirection(): Vector {
|
||||
return last - start
|
||||
}
|
||||
|
||||
fun update(vector: Vector) {
|
||||
min = min.min(vector)
|
||||
max = max.max(vector)
|
||||
last = vector
|
||||
}
|
||||
}
|
||||
|
||||
private fun PointerPath.startIntersectsSystemGestureInsets(): Boolean {
|
||||
// ignore x, since this makes edge swipes very hard to execute
|
||||
return start.y < systemGestureInsetTop
|
||||
|| start.y > height - systemGestureInsetBottom
|
||||
}
|
||||
|
||||
private fun PointerPath.intersectsSystemGestureInsets(): Boolean {
|
||||
return min.x < systemGestureInsetLeft
|
||||
|| min.y < systemGestureInsetTop
|
||||
|| max.x > width - systemGestureInsetRight
|
||||
|| max.y > height - systemGestureInsetBottom
|
||||
}
|
||||
|
||||
private fun PointerPath.isTap(): Boolean {
|
||||
if (intersectsSystemGestureInsets()) {
|
||||
return false
|
||||
}
|
||||
return sizeSquared() < TOUCH_SLOP_SQUARE
|
||||
}
|
||||
|
||||
|
@ -84,20 +121,48 @@ class TouchGestureDetector(
|
|||
|
||||
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 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 =
|
||||
(0..<event.pointerCount).associateBy { event.getPointerId(it) }
|
||||
|
||||
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
|
||||
for(i in 0..<event.pointerCount){
|
||||
if(paths.containsKey(event.getPointerId(i))) {
|
||||
for (i in 0..<event.pointerCount) {
|
||||
if (paths.containsKey(event.getPointerId(i))) {
|
||||
continue
|
||||
}
|
||||
val index = pointerIdToIndex[i] ?: continue
|
||||
|
@ -122,9 +187,17 @@ class TouchGestureDetector(
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
private fun getGestureForDirection(direction: Vector): Gesture? {
|
||||
|
@ -152,9 +225,8 @@ class TouchGestureDetector(
|
|||
|
||||
val mainPointerPath = paths.entries.firstOrNull { it.value.number == 0 }?.value ?: return
|
||||
|
||||
// Ignore swipes at the very top, since this interferes with the status bar.
|
||||
// TODO: replace 100px by sensible dp value (e.g. twice the height of the status bar)
|
||||
if (paths.entries.any { it.value.start.y < 100 }) {
|
||||
// Ignore swipes starting at the very top and the very bottom
|
||||
if (paths.entries.any { it.value.startIntersectsSystemGestureInsets() }) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -164,17 +236,14 @@ class TouchGestureDetector(
|
|||
if (duration in 0..TAP_TIMEOUT) {
|
||||
if (timeStart - lastTappedTime < DOUBLE_TAP_TIMEOUT &&
|
||||
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)
|
||||
} else {
|
||||
lastTappedTime = timeEnd
|
||||
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 {
|
||||
// detect swipes
|
||||
|
@ -197,34 +266,38 @@ class TouchGestureDetector(
|
|||
val startEndMax = mainPointerPath.start.max(mainPointerPath.last)
|
||||
when (gesture) {
|
||||
Gesture.SWIPE_DOWN -> {
|
||||
if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
|
||||
if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
|
||||
gesture = Gesture.SWIPE_LARGER
|
||||
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
||||
gesture = Gesture.SWIPE_SMALLER
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_UP -> {
|
||||
if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
|
||||
if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
|
||||
gesture = Gesture.SWIPE_LARGER_REVERSE
|
||||
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
|
||||
gesture = Gesture.SWIPE_SMALLER_REVERSE
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_RIGHT -> {
|
||||
if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
gesture = Gesture.SWIPE_V
|
||||
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
||||
gesture = Gesture.SWIPE_LAMBDA
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_LEFT -> {
|
||||
if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
gesture = Gesture.SWIPE_V_REVERSE
|
||||
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
|
||||
gesture = Gesture.SWIPE_LAMBDA_REVERSE
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (edgeActions) {
|
||||
|
@ -240,7 +313,27 @@ class TouchGestureDetector(
|
|||
gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM)
|
||||
}
|
||||
}
|
||||
|
||||
if (timeStart - lastTappedTime < 2 * DOUBLE_TAP_TIMEOUT) {
|
||||
gesture = gesture?.getTapComboVariant()
|
||||
}
|
||||
gesture?.invoke(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateScreenSize(windowManager: WindowManager) {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
@Suppress("deprecation") // required to support API < 30
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
width = displayMetrics.widthPixels
|
||||
height = displayMetrics.heightPixels
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun setSystemGestureInsets(insets: Insets) {
|
||||
systemGestureInsetTop = insets.top
|
||||
systemGestureInsetBottom = insets.bottom
|
||||
systemGestureInsetLeft = insets.left
|
||||
systemGestureInsetRight = insets.right
|
||||
}
|
||||
}
|
|
@ -3,7 +3,11 @@ package de.jrpie.android.launcher.ui
|
|||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
|
||||
|
@ -11,10 +15,12 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|||
* An interface implemented by every [Activity], Fragment etc. in Launcher.
|
||||
* It handles themes and window flags - a useful abstraction as it is the same everywhere.
|
||||
*/
|
||||
@Suppress("deprecation") // FLAG_FULLSCREEN is required to support API level < 30
|
||||
fun setWindowFlags(window: Window, homeScreen: Boolean) {
|
||||
window.setFlags(0, 0) // clear flags
|
||||
|
||||
// Display notification bar
|
||||
if (LauncherPreferences.display().fullScreen())
|
||||
if (LauncherPreferences.display().hideStatusBar())
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
|
@ -36,17 +42,19 @@ fun setWindowFlags(window: Window, homeScreen: Boolean) {
|
|||
|
||||
}
|
||||
|
||||
|
||||
interface UIObject {
|
||||
fun onCreate() {
|
||||
if (this is Activity) {
|
||||
setWindowFlags(window, isHomeScreen())
|
||||
|
||||
if (!LauncherPreferences.display().rotateScreen()) {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
}
|
||||
if (this !is Activity) {
|
||||
return
|
||||
}
|
||||
setWindowFlags(window, isHomeScreen())
|
||||
|
||||
if (!LauncherPreferences.display().rotateScreen()) {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
}
|
||||
}
|
||||
|
||||
fun onStart() {
|
||||
setOnClicks()
|
||||
adjustLayout()
|
||||
|
@ -70,4 +78,26 @@ interface UIObject {
|
|||
fun isHomeScreen(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun hideNavigationBar() {
|
||||
if (this !is Activity) {
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.insetsController?.apply {
|
||||
hide(WindowInsets.Type.navigationBars())
|
||||
systemBarsBehavior =
|
||||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else {
|
||||
// Try to hide the navigation bar but do not hide the status bar
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,20 @@
|
|||
package de.jrpie.android.launcher.ui.list
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import android.window.OnBackInvokedDispatcher
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import de.jrpie.android.launcher.Application
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_UNINSTALL
|
||||
import de.jrpie.android.launcher.actions.LauncherAction
|
||||
import de.jrpie.android.launcher.apps.AppFilter
|
||||
import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked
|
||||
import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
|
||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSetUp
|
||||
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
||||
|
@ -32,14 +25,6 @@ import de.jrpie.android.launcher.ui.list.apps.ListFragmentApps
|
|||
import de.jrpie.android.launcher.ui.list.other.ListFragmentOther
|
||||
|
||||
|
||||
// TODO: Better solution for this intercommunication functionality (used in list-fragments)
|
||||
var intention = ListActivity.ListActivityIntention.VIEW
|
||||
var favoritesVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||
var privateSpaceVisibility: AppFilter.Companion.AppSetVisibility =
|
||||
AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||
var hiddenVisibility: AppFilter.Companion.AppSetVisibility = AppFilter.Companion.AppSetVisibility.HIDDEN
|
||||
var forGesture: String? = null
|
||||
|
||||
/**
|
||||
* The [ListActivity] is the most general purpose activity in Launcher:
|
||||
* - used to view all apps and edit their settings
|
||||
|
@ -49,9 +34,34 @@ var forGesture: String? = null
|
|||
*/
|
||||
class ListActivity : AppCompatActivity(), UIObject {
|
||||
private lateinit var binding: ListBinding
|
||||
var intention = ListActivityIntention.VIEW
|
||||
var favoritesVisibility: AppFilter.Companion.AppSetVisibility =
|
||||
AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||
var privateSpaceVisibility: AppFilter.Companion.AppSetVisibility =
|
||||
AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||
var hiddenVisibility: AppFilter.Companion.AppSetVisibility =
|
||||
AppFilter.Companion.AppSetVisibility.HIDDEN
|
||||
var forGesture: String? = null
|
||||
|
||||
|
||||
private fun updateLockIcon(locked: Boolean) {
|
||||
if (
|
||||
// only show lock for VIEW intention
|
||||
(intention != ListActivityIntention.VIEW)
|
||||
// hide lock when private space does not exist
|
||||
|| !isPrivateSpaceSetUp(this)
|
||||
// hide lock when private space apps are hidden from the main list and we are not in the private space list
|
||||
|| (LauncherPreferences.apps().hidePrivateSpaceApps()
|
||||
&& privateSpaceVisibility != AppFilter.Companion.AppSetVisibility.EXCLUSIVE)
|
||||
// hide lock when private space is locked and the hidden when locked setting is set
|
||||
|| (locked && hidePrivateSpaceWhenLocked(this))
|
||||
) {
|
||||
binding.listLock.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
|
||||
binding.listLock.visibility = View.VISIBLE
|
||||
|
||||
binding.listLock.setImageDrawable(
|
||||
AppCompatResources.getDrawable(
|
||||
this,
|
||||
|
@ -74,7 +84,6 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
}
|
||||
|
||||
|
||||
|
||||
enum class ListActivityIntention(val titleResource: Int) {
|
||||
VIEW(R.string.list_title_view), /* view list of apps */
|
||||
PICK(R.string.list_title_pick) /* choose app or action to associate to a gesture */
|
||||
|
@ -99,10 +108,13 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
?.let { ListActivityIntention.valueOf(it) }
|
||||
?: ListActivityIntention.VIEW
|
||||
|
||||
@Suppress("deprecation") // required to support API level < 33
|
||||
favoritesVisibility = bundle.getSerializable("favoritesVisibility")
|
||||
as? AppFilter.Companion.AppSetVisibility ?: favoritesVisibility
|
||||
@Suppress("deprecation") // required to support API level < 33
|
||||
privateSpaceVisibility = bundle.getSerializable("privateSpaceVisibility")
|
||||
as? AppFilter.Companion.AppSetVisibility ?: privateSpaceVisibility
|
||||
@Suppress("deprecation") // required to support API level < 33
|
||||
hiddenVisibility = bundle.getSerializable("hiddenVisibility")
|
||||
as? AppFilter.Companion.AppSetVisibility ?: hiddenVisibility
|
||||
|
||||
|
@ -119,20 +131,6 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
LauncherAction.SETTINGS.launch(this@ListActivity)
|
||||
}
|
||||
|
||||
binding.listLock.visibility =
|
||||
if (intention != ListActivityIntention.VIEW) {
|
||||
View.GONE
|
||||
} else if (!isPrivateSpaceSetUp(this)) {
|
||||
View.GONE
|
||||
} else if (LauncherPreferences.apps().hidePrivateSpaceApps()) {
|
||||
if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
|
||||
if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
isPrivateSpaceSetUp(this, showToast = true, launchSettings = true)
|
||||
|
@ -155,7 +153,7 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
binding.listContainer.context.resources.displayMetrics.heightPixels
|
||||
val diff = height - r.bottom
|
||||
if (diff != 0 &&
|
||||
LauncherPreferences.display().fullScreen()
|
||||
LauncherPreferences.display().hideStatusBar()
|
||||
) {
|
||||
if (binding.listContainer.paddingBottom != diff) {
|
||||
binding.listContainer.setPadding(0, 0, 0, diff)
|
||||
|
@ -183,32 +181,19 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
finish()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_UNINSTALL) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Toast.makeText(this, getString(R.string.list_removed), Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
} else if (resultCode == Activity.RESULT_FIRST_USER) {
|
||||
Toast.makeText(this, getString(R.string.list_not_removed), Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateTitle() {
|
||||
var titleResource = intention.titleResource
|
||||
if (intention == ListActivityIntention.VIEW) {
|
||||
titleResource = if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_hidden
|
||||
} else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_private_space
|
||||
} else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_favorite
|
||||
} else {
|
||||
R.string.list_title_view
|
||||
}
|
||||
titleResource =
|
||||
if (hiddenVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_hidden
|
||||
} else if (privateSpaceVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_private_space
|
||||
} else if (favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE) {
|
||||
R.string.list_title_favorite
|
||||
} else {
|
||||
R.string.list_title_view
|
||||
}
|
||||
}
|
||||
|
||||
binding.listHeading.text = getString(titleResource)
|
||||
|
@ -238,11 +223,11 @@ class ListActivity : AppCompatActivity(), UIObject {
|
|||
|
||||
updateTitle()
|
||||
|
||||
val sectionsPagerAdapter = ListSectionsPagerAdapter(this, supportFragmentManager)
|
||||
val viewPager: ViewPager = findViewById(R.id.list_viewpager)
|
||||
viewPager.adapter = sectionsPagerAdapter
|
||||
val tabs: TabLayout = findViewById(R.id.list_tabs)
|
||||
tabs.setupWithViewPager(viewPager)
|
||||
val sectionsPagerAdapter = ListSectionsPagerAdapter(this)
|
||||
binding.listViewpager.let {
|
||||
it.adapter = sectionsPagerAdapter
|
||||
binding.listTabs.setupWithViewPager(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,9 +239,15 @@ private val TAB_TITLES = arrayOf(
|
|||
/**
|
||||
* The [ListSectionsPagerAdapter] returns the fragment,
|
||||
* which corresponds to the selected tab in [ListActivity].
|
||||
*
|
||||
* This should eventually be replaced by a [FragmentStateAdapter]
|
||||
* However this keyboard does not open when using [ViewPager2]
|
||||
* so currently [ViewPager] is used here.
|
||||
* https://github.com/jrpie/launcher/issues/130
|
||||
*/
|
||||
class ListSectionsPagerAdapter(private val context: Context, fm: FragmentManager) :
|
||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
@Suppress("deprecation")
|
||||
class ListSectionsPagerAdapter(private val activity: ListActivity) :
|
||||
FragmentPagerAdapter(activity.supportFragmentManager) {
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
return when (position) {
|
||||
|
@ -267,11 +258,11 @@ class ListSectionsPagerAdapter(private val context: Context, fm: FragmentManager
|
|||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return context.resources.getString(TAB_TITLES[position])
|
||||
return activity.resources.getString(TAB_TITLES[position])
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return when (intention) {
|
||||
return when (activity.intention) {
|
||||
ListActivity.ListActivityIntention.VIEW -> 1
|
||||
else -> 2
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.ui.list.apps
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -15,12 +14,12 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import de.jrpie.android.launcher.Application
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
||||
import de.jrpie.android.launcher.actions.AppAction
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.apps.AbstractDetailedAppInfo
|
||||
import de.jrpie.android.launcher.apps.AppFilter
|
||||
import de.jrpie.android.launcher.apps.AppInfo
|
||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||
import de.jrpie.android.launcher.getUserFromId
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.preferences.ListLayout
|
||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||
|
@ -47,7 +46,8 @@ class AppsRecyclerAdapter(
|
|||
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
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
|
||||
var disableAutoLaunch: Boolean = false
|
||||
|
@ -68,7 +68,7 @@ class AppsRecyclerAdapter(
|
|||
override fun onClick(v: View) {
|
||||
val rect = Rect()
|
||||
img.getGlobalVisibleRect(rect)
|
||||
selectItem(adapterPosition, rect)
|
||||
selectItem(bindingAdapterPosition, rect)
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -80,20 +80,19 @@ class AppsRecyclerAdapter(
|
|||
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
||||
var appLabel = appsListDisplayed[i].getCustomLabel(activity)
|
||||
|
||||
val appIcon = appsListDisplayed[i].getIcon(activity)
|
||||
|
||||
viewHolder.img.transformGrayscale(grayscale)
|
||||
viewHolder.img.setImageDrawable(appIcon.constantState?.newDrawable() ?: appIcon)
|
||||
|
||||
if (layout.useBadgedText) {
|
||||
appLabel = activity.packageManager.getUserBadgedLabel(
|
||||
appLabel,
|
||||
getUserFromId(appsListDisplayed[i].app.user, activity)
|
||||
appsListDisplayed[i].getUser(activity)
|
||||
).toString()
|
||||
}
|
||||
|
||||
val appIcon = appsListDisplayed[i].icon
|
||||
|
||||
viewHolder.textView.text = appLabel
|
||||
viewHolder.img.setImageDrawable(appIcon)
|
||||
|
||||
if (LauncherPreferences.theme().monochromeIcons())
|
||||
viewHolder.img.transformGrayscale()
|
||||
|
||||
// decide when to show the options popup menu about
|
||||
if (intention == ListActivity.ListActivityIntention.VIEW) {
|
||||
|
@ -118,22 +117,26 @@ class AppsRecyclerAdapter(
|
|||
@Suppress("SameReturnValue")
|
||||
private fun showOptionsPopup(
|
||||
viewHolder: ViewHolder,
|
||||
appInfo: DetailedAppInfo
|
||||
appInfo: AbstractDetailedAppInfo
|
||||
): Boolean {
|
||||
//create the popup menu
|
||||
|
||||
val popup = PopupMenu(activity, viewHolder.img)
|
||||
popup.inflate(R.menu.menu_app)
|
||||
|
||||
if (appInfo.isSystemApp) {
|
||||
if (!appInfo.isRemovable()) {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -141,19 +144,19 @@ class AppsRecyclerAdapter(
|
|||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.app_menu_delete -> {
|
||||
appInfo.app.uninstall(activity); true
|
||||
appInfo.getRawInfo().uninstall(activity); true
|
||||
}
|
||||
|
||||
R.id.app_menu_info -> {
|
||||
appInfo.app.openSettings(activity); true
|
||||
(appInfo.getRawInfo() as? AppInfo)?.openSettings(activity); true
|
||||
}
|
||||
|
||||
R.id.app_menu_favorite -> {
|
||||
appInfo.app.toggleFavorite(); true
|
||||
appInfo.getRawInfo().toggleFavorite(); true
|
||||
}
|
||||
|
||||
R.id.app_menu_hidden -> {
|
||||
appInfo.app.toggleHidden(root); true
|
||||
appInfo.getRawInfo().toggleHidden(root); true
|
||||
}
|
||||
|
||||
R.id.app_menu_rename -> {
|
||||
|
@ -188,15 +191,14 @@ class AppsRecyclerAdapter(
|
|||
val appInfo = appsListDisplayed[pos]
|
||||
when (intention) {
|
||||
ListActivity.ListActivityIntention.VIEW -> {
|
||||
AppAction(appInfo.app).invoke(activity, rect)
|
||||
appInfo.getAction().invoke(activity, rect)
|
||||
}
|
||||
|
||||
ListActivity.ListActivityIntention.PICK -> {
|
||||
val returnIntent = Intent()
|
||||
AppAction(appInfo.app).writeToIntent(returnIntent)
|
||||
returnIntent.putExtra("forGesture", forGesture)
|
||||
activity.setResult(REQUEST_CHOOSE_APP, returnIntent)
|
||||
activity.finish()
|
||||
forGesture ?: return
|
||||
val gesture = Gesture.byId(forGesture) ?: return
|
||||
Action.setActionForGesture(gesture, appInfo.getAction())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,8 +213,8 @@ class AppsRecyclerAdapter(
|
|||
&& !disableAutoLaunch
|
||||
&& LauncherPreferences.functionality().searchAutoLaunch()
|
||||
) {
|
||||
val info = appsListDisplayed[0]
|
||||
AppAction(info.app).invoke(activity)
|
||||
val app = appsListDisplayed[0]
|
||||
app.getAction().invoke(activity)
|
||||
|
||||
val inputMethodManager =
|
||||
activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package de.jrpie.android.launcher.ui.list.apps
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.LauncherApps
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
@ -13,11 +13,13 @@ import android.widget.EditText
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_UNINSTALL
|
||||
import de.jrpie.android.launcher.apps.AppInfo
|
||||
import de.jrpie.android.launcher.apps.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.preferences.LauncherPreferences
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private const val LOG_TAG = "AppContextMenu"
|
||||
|
||||
|
@ -32,27 +34,29 @@ fun AppInfo.openSettings(
|
|||
}
|
||||
}
|
||||
|
||||
fun AppInfo.uninstall(activity: android.app.Activity) {
|
||||
val packageName = this.packageName
|
||||
val userId = this.user
|
||||
fun AbstractAppInfo.uninstall(activity: Activity) {
|
||||
if (this is AppInfo) {
|
||||
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)
|
||||
intent.data = Uri.parse("package:$packageName")
|
||||
getUserFromId(userId, activity).let { user ->
|
||||
intent.putExtra(Intent.EXTRA_USER, user)
|
||||
val intent = Intent(Intent.ACTION_DELETE)
|
||||
intent.data = "package:$packageName".toUri()
|
||||
getUserFromId(userId, activity).let { user ->
|
||||
intent.putExtra(Intent.EXTRA_USER, user)
|
||||
}
|
||||
activity.startActivity(intent)
|
||||
|
||||
} else if(this is PinnedShortcutInfo) {
|
||||
val pinned = LauncherPreferences.apps().pinnedShortcuts() ?: mutableSetOf()
|
||||
pinned.remove(this)
|
||||
LauncherPreferences.apps().pinnedShortcuts(pinned)
|
||||
}
|
||||
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||
activity.startActivityForResult(
|
||||
intent,
|
||||
REQUEST_UNINSTALL
|
||||
)
|
||||
}
|
||||
|
||||
fun AppInfo.toggleFavorite() {
|
||||
val favorites: MutableSet<AppInfo> =
|
||||
fun AbstractAppInfo.toggleFavorite() {
|
||||
val favorites: MutableSet<AbstractAppInfo> =
|
||||
LauncherPreferences.apps().favorites() ?: mutableSetOf()
|
||||
|
||||
if (favorites.contains(this)) {
|
||||
|
@ -69,8 +73,8 @@ fun AppInfo.toggleFavorite() {
|
|||
/**
|
||||
* @param view: used to show a snackbar letting the user undo the action
|
||||
*/
|
||||
fun AppInfo.toggleHidden(view: View) {
|
||||
val hidden: MutableSet<AppInfo> =
|
||||
fun AbstractAppInfo.toggleHidden(view: View) {
|
||||
val hidden: MutableSet<AbstractAppInfo> =
|
||||
LauncherPreferences.apps().hidden() ?: mutableSetOf()
|
||||
if (hidden.contains(this)) {
|
||||
hidden.remove(this)
|
||||
|
@ -87,12 +91,12 @@ fun AppInfo.toggleHidden(view: View) {
|
|||
LauncherPreferences.apps().hidden(hidden)
|
||||
}
|
||||
|
||||
fun DetailedAppInfo.showRenameDialog(context: Context) {
|
||||
fun AbstractDetailedAppInfo.showRenameDialog(context: Context) {
|
||||
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)
|
||||
setNegativeButton(R.string.dialog_cancel) { d, _ -> d.cancel() }
|
||||
setPositiveButton(R.string.dialog_rename_ok) { d, _ ->
|
||||
setNegativeButton(android.R.string.cancel) { d, _ -> d.cancel() }
|
||||
setPositiveButton(android.R.string.ok) { d, _ ->
|
||||
setCustomLabel(
|
||||
(d as? AlertDialog)
|
||||
?.findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
||||
|
@ -102,7 +106,7 @@ fun DetailedAppInfo.showRenameDialog(context: Context) {
|
|||
}.create().also { it.show() }.apply {
|
||||
val input = findViewById<EditText>(R.id.dialog_rename_app_edit_text)
|
||||
input?.setText(getCustomLabel(context))
|
||||
input?.hint = label
|
||||
input?.hint = getLabel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,17 +9,14 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.apps.AppFilter
|
||||
import de.jrpie.android.launcher.databinding.ListAppsBinding
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.ui.UIObject
|
||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||
import de.jrpie.android.launcher.ui.list.favoritesVisibility
|
||||
import de.jrpie.android.launcher.ui.list.forGesture
|
||||
import de.jrpie.android.launcher.ui.list.hiddenVisibility
|
||||
import de.jrpie.android.launcher.ui.list.intention
|
||||
import de.jrpie.android.launcher.ui.list.privateSpaceVisibility
|
||||
import de.jrpie.android.launcher.ui.openSoftKeyboard
|
||||
|
||||
|
||||
|
@ -52,7 +49,7 @@ class ListFragmentApps : Fragment(), UIObject {
|
|||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||
|
||||
binding.listAppsCheckBoxFavorites.isChecked =
|
||||
(favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE)
|
||||
((activity as? ListActivity)?.favoritesVisibility == AppFilter.Companion.AppSetVisibility.EXCLUSIVE)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
@ -65,25 +62,33 @@ class ListFragmentApps : Fragment(), UIObject {
|
|||
override fun setOnClicks() {}
|
||||
|
||||
override fun adjustLayout() {
|
||||
val listActivity = activity as? ListActivity ?: return
|
||||
|
||||
appsRecyclerAdapter =
|
||||
AppsRecyclerAdapter(
|
||||
requireActivity(), binding.root, intention, forGesture,
|
||||
listActivity, binding.root, listActivity.intention, listActivity.forGesture,
|
||||
appFilter = AppFilter(
|
||||
requireContext(),
|
||||
"",
|
||||
favoritesVisibility = favoritesVisibility,
|
||||
privateSpaceVisibility = privateSpaceVisibility,
|
||||
hiddenVisibility = hiddenVisibility
|
||||
favoritesVisibility = listActivity.favoritesVisibility,
|
||||
privateSpaceVisibility = listActivity.privateSpaceVisibility,
|
||||
hiddenVisibility = listActivity.hiddenVisibility
|
||||
),
|
||||
layout = LauncherPreferences.list().layout()
|
||||
)
|
||||
|
||||
|
||||
// set up the list / recycler
|
||||
binding.listAppsRview.apply {
|
||||
// improve performance (since content changes don't change the layout size)
|
||||
setHasFixedSize(true)
|
||||
layoutManager = LauncherPreferences.list().layout().layoutManager(context)
|
||||
.also {
|
||||
if (LauncherPreferences.list().reverseLayout()) {
|
||||
(it as? LinearLayoutManager)?.reverseLayout = true
|
||||
(it as? GridLayoutManager)?.reverseLayout = true
|
||||
}
|
||||
}
|
||||
adapter = appsRecyclerAdapter
|
||||
}
|
||||
|
||||
|
@ -115,7 +120,8 @@ class ListFragmentApps : Fragment(), UIObject {
|
|||
|
||||
if (newText == " " &&
|
||||
!appsRecyclerAdapter.disableAutoLaunch &&
|
||||
intention == ListActivity.ListActivityIntention.VIEW &&
|
||||
(activity as? ListActivity)?.intention
|
||||
== ListActivity.ListActivityIntention.VIEW &&
|
||||
LauncherPreferences.functionality().searchAutoLaunch()
|
||||
) {
|
||||
appsRecyclerAdapter.disableAutoLaunch = true
|
||||
|
@ -132,17 +138,17 @@ class ListFragmentApps : Fragment(), UIObject {
|
|||
})
|
||||
|
||||
binding.listAppsCheckBoxFavorites.setOnClickListener {
|
||||
favoritesVisibility =
|
||||
listActivity.favoritesVisibility =
|
||||
if (binding.listAppsCheckBoxFavorites.isChecked) {
|
||||
AppFilter.Companion.AppSetVisibility.EXCLUSIVE
|
||||
} else {
|
||||
AppFilter.Companion.AppSetVisibility.VISIBLE
|
||||
}
|
||||
appsRecyclerAdapter.setFavoritesVisibility(favoritesVisibility)
|
||||
appsRecyclerAdapter.setFavoritesVisibility(listActivity.favoritesVisibility)
|
||||
(activity as? ListActivity)?.updateTitle()
|
||||
}
|
||||
|
||||
if (intention == ListActivity.ListActivityIntention.VIEW
|
||||
if (listActivity.intention == ListActivity.ListActivityIntention.VIEW
|
||||
&& LauncherPreferences.functionality().searchAutoOpenKeyboard()
|
||||
) {
|
||||
binding.listAppsSearchview.openSoftKeyboard(requireContext())
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package de.jrpie.android.launcher.ui.list.other
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -9,9 +8,10 @@ import android.widget.ImageView
|
|||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.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,
|
||||
|
@ -33,10 +33,13 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
|||
|
||||
|
||||
override fun onClick(v: View) {
|
||||
val pos = adapterPosition
|
||||
val pos = bindingAdapterPosition
|
||||
val content = othersList[pos]
|
||||
|
||||
forGesture?.let { returnChoiceIntent(it, content) }
|
||||
activity.finish()
|
||||
val gestureId = (activity as? ListActivity)?.forGesture ?: return
|
||||
val gesture = Gesture.byId(gestureId) ?: return
|
||||
Action.setActionForGesture(gesture, content)
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -61,12 +64,4 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
|||
val view: View = inflater.inflate(R.layout.list_other_row, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
private fun returnChoiceIntent(forGesture: String, action: LauncherAction) {
|
||||
val returnIntent = Intent()
|
||||
returnIntent.putExtra("forGesture", forGesture)
|
||||
action.writeToIntent(returnIntent)
|
||||
activity.setResult(REQUEST_CHOOSE_APP, returnIntent)
|
||||
activity.finish()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package de.jrpie.android.launcher.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
|
@ -8,17 +7,14 @@ import android.os.Bundle
|
|||
import android.provider.Settings
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
||||
import de.jrpie.android.launcher.databinding.SettingsBinding
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.preferences.theme.Background
|
||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||
import de.jrpie.android.launcher.saveListActivityChoice
|
||||
import de.jrpie.android.launcher.ui.UIObject
|
||||
import de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActions
|
||||
import de.jrpie.android.launcher.ui.settings.launcher.SettingsFragmentLauncher
|
||||
|
@ -49,15 +45,15 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
|||
// This ugly workaround causes a jump to the top of the list, but at least
|
||||
// the text stays readable.
|
||||
val i = Intent(this, SettingsActivity::class.java)
|
||||
.also { it.putExtra("tab", 1) }
|
||||
.also { it.putExtra(EXTRA_TAB, 1) }
|
||||
finish()
|
||||
startActivity(i)
|
||||
} else
|
||||
if (prefKey?.startsWith("theme.") == true ||
|
||||
prefKey?.startsWith("display.") == true
|
||||
) {
|
||||
recreate()
|
||||
}
|
||||
if (prefKey?.startsWith("theme.") == true ||
|
||||
prefKey?.startsWith("display.") == true
|
||||
) {
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
private lateinit var binding: SettingsBinding
|
||||
|
||||
|
@ -71,15 +67,14 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
|||
setContentView(binding.root)
|
||||
|
||||
// set up tabs and swiping in settings
|
||||
val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this, supportFragmentManager)
|
||||
val viewPager: ViewPager = findViewById(R.id.settings_viewpager)
|
||||
viewPager.adapter = sectionsPagerAdapter
|
||||
|
||||
val tabs: TabLayout = findViewById(R.id.settings_tabs)
|
||||
tabs.setupWithViewPager(viewPager)
|
||||
if (intent.hasExtra("tab")) {
|
||||
tabs.getTabAt(intent.getIntExtra("tab", 0))?.select()
|
||||
val sectionsPagerAdapter = SettingsSectionsPagerAdapter(this)
|
||||
binding.settingsViewpager.apply {
|
||||
adapter = sectionsPagerAdapter
|
||||
setCurrentItem(intent.getIntExtra(EXTRA_TAB, 0), false)
|
||||
}
|
||||
TabLayoutMediator(binding.settingsTabs, binding.settingsViewpager) { tab, position ->
|
||||
tab.text = sectionsPagerAdapter.getPageTitle(position)
|
||||
}.attach()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -108,24 +103,21 @@ class SettingsActivity : AppCompatActivity(), UIObject {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_CHOOSE_APP -> saveListActivityChoice(data)
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
companion object {
|
||||
private const val EXTRA_TAB = "tab"
|
||||
}
|
||||
}
|
||||
|
||||
private val TAB_TITLES = arrayOf(
|
||||
R.string.settings_tab_app,
|
||||
R.string.settings_tab_actions,
|
||||
R.string.settings_tab_launcher,
|
||||
R.string.settings_tab_meta
|
||||
)
|
||||
|
||||
class SettingsSectionsPagerAdapter(private val context: Context, fm: FragmentManager) :
|
||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
class SettingsSectionsPagerAdapter(private val activity: FragmentActivity) :
|
||||
FragmentStateAdapter(activity) {
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
0 -> SettingsFragmentActions()
|
||||
1 -> SettingsFragmentLauncher()
|
||||
|
@ -134,11 +126,11 @@ class SettingsSectionsPagerAdapter(private val context: Context, fm: FragmentMan
|
|||
}
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return context.resources.getString(TAB_TITLES[position])
|
||||
fun getPageTitle(position: Int): CharSequence {
|
||||
return activity.resources.getString(TAB_TITLES[position])
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
override fun getItemCount(): Int {
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ import android.view.ViewGroup
|
|||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
||||
import de.jrpie.android.launcher.actions.Action
|
||||
import de.jrpie.android.launcher.actions.Gesture
|
||||
import de.jrpie.android.launcher.apps.AppFilter
|
||||
|
@ -94,6 +94,8 @@ class SettingsFragmentActionsRecycler : Fragment(), UIObject {
|
|||
class ActionsRecyclerAdapter(val activity: Activity) :
|
||||
RecyclerView.Adapter<ActionsRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
private val drawableUnknown = AppCompatResources.getDrawable(activity, R.drawable.baseline_question_mark_24)
|
||||
|
||||
private val gesturesList: 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) {
|
||||
val action = Action.forGesture(gesture)
|
||||
val drawable = action?.getIcon(activity)
|
||||
|
||||
if (action == null || drawable == null) {
|
||||
if (action == null) {
|
||||
viewHolder.img.visibility = View.INVISIBLE
|
||||
viewHolder.removeAction.visibility = View.GONE
|
||||
viewHolder.chooseButton.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
// Use the unknown icon if there is an action, but we can't find its icon.
|
||||
// Probably an app was uninstalled.
|
||||
val drawable = action.getIcon(activity) ?: drawableUnknown
|
||||
|
||||
viewHolder.img.visibility = View.VISIBLE
|
||||
viewHolder.removeAction.visibility = View.VISIBLE
|
||||
viewHolder.chooseButton.visibility = View.INVISIBLE
|
||||
|
@ -137,9 +142,7 @@ class ActionsRecyclerAdapter(val activity: Activity) :
|
|||
val description = gesture.getDescription(activity)
|
||||
viewHolder.descriptionTextView.text = description
|
||||
|
||||
|
||||
if (LauncherPreferences.theme().monochromeIcons())
|
||||
viewHolder.img.transformGrayscale()
|
||||
viewHolder.img.transformGrayscale(LauncherPreferences.theme().monochromeIcons())
|
||||
|
||||
updateViewHolder(gesture, viewHolder)
|
||||
viewHolder.img.setOnClickListener { chooseApp(gesture) }
|
||||
|
@ -175,9 +178,6 @@ class ActionsRecyclerAdapter(val activity: Activity) :
|
|||
intent.putExtra("intention", ListActivity.ListActivityIntention.PICK.toString())
|
||||
intent.putExtra("hiddenVisibility", AppFilter.Companion.AppSetVisibility.VISIBLE)
|
||||
intent.putExtra("forGesture", gesture.id) // for which action we choose the app
|
||||
activity.startActivityForResult(
|
||||
intent,
|
||||
REQUEST_CHOOSE_APP
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package de.jrpie.android.launcher.ui.settings.meta
|
|||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -16,10 +15,10 @@ import de.jrpie.android.launcher.copyToClipboard
|
|||
import de.jrpie.android.launcher.databinding.SettingsMetaBinding
|
||||
import de.jrpie.android.launcher.getDeviceInfo
|
||||
import de.jrpie.android.launcher.openInBrowser
|
||||
import de.jrpie.android.launcher.openTutorial
|
||||
import de.jrpie.android.launcher.preferences.resetPreferences
|
||||
import de.jrpie.android.launcher.ui.LegalInfoActivity
|
||||
import de.jrpie.android.launcher.ui.UIObject
|
||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||
|
||||
/**
|
||||
* The [SettingsFragmentMeta] is a used as a tab in the SettingsActivity.
|
||||
|
@ -47,8 +46,17 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
|||
|
||||
override fun setOnClicks() {
|
||||
|
||||
fun bindURL(view: View, urlRes: Int) {
|
||||
view.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(urlRes),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.settingsMetaButtonViewTutorial.setOnClickListener {
|
||||
startActivity(Intent(this.context, TutorialActivity::class.java))
|
||||
openTutorial(requireContext())
|
||||
}
|
||||
|
||||
// prompting for settings-reset confirmation
|
||||
|
@ -69,12 +77,7 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
|||
|
||||
|
||||
// view code
|
||||
binding.settingsMetaButtonViewCode.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_link_github),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github)
|
||||
|
||||
// report a bug
|
||||
binding.settingsMetaButtonReportBug.setOnClickListener {
|
||||
|
@ -110,37 +113,19 @@ class SettingsFragmentMeta : Fragment(), UIObject {
|
|||
}
|
||||
|
||||
// join chat
|
||||
binding.settingsMetaButtonJoinChat.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_chat_url),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
|
||||
bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url)
|
||||
|
||||
// contact developer
|
||||
binding.settingsMetaButtonContact.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_contact_url),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
// bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url)
|
||||
|
||||
// contact fork developer
|
||||
binding.settingsMetaButtonForkContact.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_fork_contact_url),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url)
|
||||
|
||||
// donate
|
||||
bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url)
|
||||
|
||||
// privacy policy
|
||||
binding.settingsMetaButtonPrivacy.setOnClickListener {
|
||||
openInBrowser(
|
||||
getString(R.string.settings_meta_privacy_url),
|
||||
requireContext()
|
||||
)
|
||||
}
|
||||
bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url)
|
||||
|
||||
// legal info
|
||||
binding.settingsMetaButtonLicenses.setOnClickListener {
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
package de.jrpie.android.launcher.ui.tutorial
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.window.OnBackInvokedDispatcher
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import de.jrpie.android.launcher.R
|
||||
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import de.jrpie.android.launcher.databinding.TutorialBinding
|
||||
import 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.tutorial.tabs.TutorialFragmentConcept
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentFinish
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentSetup
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentStart
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragmentUsage
|
||||
import de.jrpie.android.launcher.ui.blink
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment0Start
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment1Concept
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment2Usage
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment3AppList
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment4Setup
|
||||
import de.jrpie.android.launcher.ui.tutorial.tabs.TutorialFragment5Finish
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private lateinit var binding: TutorialBinding
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
||||
super<UIObject>.onCreate()
|
||||
|
||||
// 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
|
||||
val sectionsPagerAdapter = TutorialSectionsPagerAdapter(supportFragmentManager)
|
||||
val viewPager: ViewPager = findViewById(R.id.tutorial_viewpager)
|
||||
viewPager.adapter = sectionsPagerAdapter
|
||||
val tabs: TabLayout = findViewById(R.id.tutorial_tabs)
|
||||
tabs.setupWithViewPager(viewPager)
|
||||
val sectionsPagerAdapter = TutorialSectionsPagerAdapter(this)
|
||||
binding.tutorialViewpager.apply {
|
||||
adapter = sectionsPagerAdapter
|
||||
currentItem = 0
|
||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding.tutorialButtonNext.apply {
|
||||
val lastItem = sectionsPagerAdapter.itemCount - 1
|
||||
visibility = if (position == lastItem) {
|
||||
View.INVISIBLE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
if (position == 0) {
|
||||
blink()
|
||||
} else {
|
||||
clearAnimation()
|
||||
}
|
||||
}
|
||||
binding.tutorialButtonBack.apply {
|
||||
visibility = if (position == 0) {
|
||||
View.INVISIBLE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
TabLayoutMediator(binding.tutorialTabs, binding.tutorialViewpager) { _, _ -> }.attach()
|
||||
binding.tutorialButtonNext.setOnClickListener {
|
||||
binding.tutorialViewpager.apply {
|
||||
setCurrentItem(
|
||||
(currentItem + 1).coerceAtMost(sectionsPagerAdapter.itemCount - 1),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
binding.tutorialButtonBack.setOnClickListener {
|
||||
binding.tutorialViewpager.apply {
|
||||
setCurrentItem((currentItem - 1).coerceAtLeast(0), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTheme(): Resources.Theme {
|
||||
|
@ -53,14 +111,9 @@ class TutorialActivity : AppCompatActivity(), UIObject {
|
|||
super<UIObject>.onStart()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_CHOOSE_APP -> saveListActivityChoice(data)
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Default: prevent going back, allow if viewed again later
|
||||
// prevent going back when the tutorial is shown for the first time
|
||||
@Deprecated("Deprecated in Java", ReplaceWith("use anyway"))
|
||||
@Suppress("deprecation") // support API level < 33
|
||||
override fun onBackPressed() {
|
||||
if (LauncherPreferences.internal().started())
|
||||
super.onBackPressed()
|
||||
|
@ -74,26 +127,22 @@ class TutorialActivity : AppCompatActivity(), UIObject {
|
|||
*
|
||||
* Tabs: (Start | Concept | Usage | Setup | Finish)
|
||||
*/
|
||||
class TutorialSectionsPagerAdapter(fm: FragmentManager) :
|
||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
class TutorialSectionsPagerAdapter(activity: FragmentActivity) :
|
||||
FragmentStateAdapter(activity) {
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
override fun getItemCount(): Int {
|
||||
return 6
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
0 -> TutorialFragmentStart()
|
||||
1 -> TutorialFragmentConcept()
|
||||
2 -> TutorialFragmentUsage()
|
||||
3 -> TutorialFragmentSetup()
|
||||
4 -> TutorialFragmentFinish()
|
||||
0 -> TutorialFragment0Start()
|
||||
1 -> TutorialFragment1Concept()
|
||||
2 -> TutorialFragment2Usage()
|
||||
3 -> TutorialFragment3AppList()
|
||||
4 -> TutorialFragment4Setup()
|
||||
5 -> TutorialFragment5Finish()
|
||||
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.ViewGroup
|
||||
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.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
|
||||
*/
|
||||
class TutorialFragmentStart : Fragment(), UIObject {
|
||||
class TutorialFragment0Start : Fragment(), UIObject {
|
||||
|
||||
private lateinit var binding: TutorialStartBinding
|
||||
private lateinit var binding: Tutorial0StartBinding
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = TutorialStartBinding.inflate(inflater, container, false)
|
||||
binding.tutorialStartIconRight.blink()
|
||||
binding = Tutorial0StartBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
|
@ -6,22 +6,22 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import de.jrpie.android.launcher.BuildConfig
|
||||
import de.jrpie.android.launcher.databinding.TutorialConceptBinding
|
||||
import de.jrpie.android.launcher.databinding.Tutorial1ConceptBinding
|
||||
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 ...)
|
||||
*/
|
||||
class TutorialFragmentConcept : Fragment(), UIObject {
|
||||
private lateinit var binding: TutorialConceptBinding
|
||||
class TutorialFragment1Concept : Fragment(), UIObject {
|
||||
private lateinit var binding: Tutorial1ConceptBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = TutorialConceptBinding.inflate(inflater, container, false)
|
||||
binding = Tutorial1ConceptBinding.inflate(inflater, container, false)
|
||||
binding.tutorialConceptBadgeVersion.text = BuildConfig.VERSION_NAME
|
||||
return binding.root
|
||||
}
|
|
@ -9,17 +9,17 @@ import de.jrpie.android.launcher.R
|
|||
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
|
||||
*/
|
||||
class TutorialFragmentUsage : Fragment(), UIObject {
|
||||
class TutorialFragment2Usage : Fragment(), UIObject {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.tutorial_usage, container, false)
|
||||
return inflater.inflate(R.layout.tutorial_2_usage, container, false)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class TutorialFragmentSetup : Fragment(), UIObject {
|
||||
class TutorialFragment4Setup : Fragment(), UIObject {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.tutorial_setup, container, false)
|
||||
return inflater.inflate(R.layout.tutorial_4_setup, container, false)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
|
@ -6,25 +6,25 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import de.jrpie.android.launcher.BuildConfig.VERSION_CODE
|
||||
import de.jrpie.android.launcher.databinding.TutorialFinishBinding
|
||||
import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding
|
||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||
import de.jrpie.android.launcher.setDefaultHomeScreen
|
||||
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
|
||||
*/
|
||||
class TutorialFragmentFinish : Fragment(), UIObject {
|
||||
class TutorialFragment5Finish : Fragment(), UIObject {
|
||||
|
||||
private lateinit var binding: TutorialFinishBinding
|
||||
private lateinit var binding: Tutorial5FinishBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = TutorialFinishBinding.inflate(inflater, container, false)
|
||||
binding = Tutorial5FinishBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ class HtmlTextView(context: Context, attr: AttributeSet?, int: Int) :
|
|||
constructor(context: Context) : this(context, null, 0)
|
||||
|
||||
init {
|
||||
@Suppress("deprecation") // required to support API level < 24
|
||||
text = Html.fromHtml(text.toString())
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
|
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"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:textColor"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
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:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
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:viewportHeight="960">
|
||||
<path
|
||||
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" />
|
||||
</vector>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
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"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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">
|
||||
|
||||
|
|
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:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="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"
|
||||
custom:layout_constraintBottom_toBottomOf="parent"
|
||||
custom:layout_constraintStart_toStartOf="parent"
|
||||
custom:layout_constraintTop_toTopOf="parent"
|
||||
custom:type="solid" />
|
||||
custom:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_heading"
|
||||
|
@ -70,8 +69,7 @@
|
|||
android:src="@drawable/baseline_close_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
custom:type="solid" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<ImageView
|
||||
android:id="@+id/list_lock"
|
||||
android:visibility="gone"
|
||||
|
@ -85,8 +83,7 @@
|
|||
android:src="@drawable/baseline_lock_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/list_close"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
custom:type="solid" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
|
@ -98,6 +95,11 @@
|
|||
|
||||
</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
|
||||
android:id="@+id/list_viewpager"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
@ -15,18 +15,21 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@mipmap/ic_launcher_round"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_apps_row_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginStart="20sp"
|
||||
android:gravity="start"
|
||||
android:text=""
|
||||
android:textSize="20sp"
|
||||
tools:text="@string/app_name"
|
||||
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" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -15,6 +15,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:src="@mipmap/ic_launcher_round"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
|
@ -25,7 +26,7 @@
|
|||
android:paddingTop="5dp"
|
||||
android:text=""
|
||||
android:textSize="11sp"
|
||||
tools:text="some app"
|
||||
tools:text="@string/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -32,6 +32,6 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="some app" />
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -47,8 +47,7 @@
|
|||
android:src="@drawable/baseline_close_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
custom:type="solid" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/settings_system"
|
||||
|
@ -63,8 +62,7 @@
|
|||
android:src="@drawable/baseline_settings_applications_24"
|
||||
custom:layout_constraintBottom_toBottomOf="parent"
|
||||
custom:layout_constraintStart_toStartOf="parent"
|
||||
custom:layout_constraintTop_toTopOf="parent"
|
||||
custom:type="solid" />
|
||||
custom:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
|
@ -75,7 +73,7 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/settings_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -59,12 +59,12 @@
|
|||
android:text="@string/settings_meta_join_chat"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
<!--<Button
|
||||
android:id="@+id/settings_meta_button_contact"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_meta_contact"
|
||||
android:textAllCaps="false" />
|
||||
android:textAllCaps="false" />-->
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_meta_button_fork_contact"
|
||||
|
@ -73,6 +73,13 @@
|
|||
android:text="@string/settings_meta_fork_contact"
|
||||
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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64sp" />
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
tools:context=".ui.tutorial.TutorialActivity">
|
||||
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/tutorial_viewpager"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
@ -22,15 +22,37 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
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
|
||||
android:id="@+id/tutorial_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
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: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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/tutorial_viewpager" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -8,7 +8,7 @@
|
|||
android:paddingRight="32sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragmentStart">
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragment0Start">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tutorial_start_text"
|
||||
|
@ -21,16 +21,4 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="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>
|
|
@ -8,7 +8,7 @@
|
|||
android:paddingRight="32sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragmentConcept">
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragment1Concept">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tutorial_concept_title"
|
||||
|
@ -28,11 +28,10 @@
|
|||
android:gravity="center"
|
||||
android:text="@string/tutorial_concept_text"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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
|
||||
android:id="@+id/tutorial_concept_text_2"
|
||||
|
@ -60,6 +59,22 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,7 +7,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:paddingLeft="32sp"
|
||||
android:paddingRight="32sp"
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragmentUsage">
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragment2Usage">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tutorial_usage_title"
|
||||
|
@ -39,12 +39,12 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+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" />
|
||||
|
||||
<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:paddingLeft="32sp"
|
||||
android:paddingRight="32sp"
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragmentSetup">
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragment4Setup">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tutorial_setup_title"
|
||||
|
@ -33,7 +33,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tutorial_setup_title" />
|
||||
|
||||
<fragment
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/tutorial_setup_actions_rview_fragment"
|
||||
android:name="de.jrpie.android.launcher.ui.settings.actions.SettingsFragmentActionsRecycler"
|
||||
android:layout_width="0dp"
|
|
@ -7,7 +7,7 @@
|
|||
android:paddingRight="32sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragmentFinish">
|
||||
tools:context=".ui.tutorial.tabs.TutorialFragment5Finish">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tutorial_finish_title"
|
||||
|
@ -24,6 +24,7 @@
|
|||
android:id="@+id/tutorial_finish_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/tutorial_finish_text"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tutorial_finish_button_start"
|
Binary file not shown.
Before Width: | Height: | Size: 612 B |
Binary file not shown.
Before Width: | Height: | Size: 372 B |
Binary file not shown.
Before Width: | Height: | Size: 880 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue