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