mirror of
https://github.com/jrpie/Launcher.git
synced 2025-05-09 12:04:18 +02:00
Compare commits
No commits in common. "master" and "0.1.4" have entirely different histories.
57 changed files with 154 additions and 2521 deletions
|
@ -106,7 +106,6 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||||
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
|
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
|
||||||
implementation 'androidx.activity:activity:1.10.1'
|
|
||||||
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
|
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
|
||||||
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
|
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
|
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
|
||||||
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
||||||
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
|
@ -20,19 +19,6 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/launcherBaseTheme"
|
android:theme="@style/launcherBaseTheme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
<activity
|
|
||||||
android:name=".ui.widgets.manage.ManageWidgetPanelsActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
<activity
|
|
||||||
android:name=".ui.widgets.WidgetPanelActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
<activity
|
|
||||||
android:name=".ui.widgets.manage.ManageWidgetsActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:theme="@style/launcherHomeTheme" />
|
|
||||||
<activity
|
|
||||||
android:name=".ui.widgets.manage.SelectWidgetActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PinShortcutActivity"
|
android:name=".ui.PinShortcutActivity"
|
||||||
android:autoRemoveFromRecents="true"
|
android:autoRemoveFromRecents="true"
|
||||||
|
@ -111,4 +97,4 @@
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -12,8 +12,6 @@ import android.os.Build.VERSION_CODES
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import android.appwidget.AppWidgetHost
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import de.jrpie.android.launcher.actions.TorchManager
|
import de.jrpie.android.launcher.actions.TorchManager
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
|
@ -22,22 +20,13 @@ import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
|
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
|
||||||
import de.jrpie.android.launcher.preferences.resetPreferences
|
import de.jrpie.android.launcher.preferences.resetPreferences
|
||||||
import de.jrpie.android.launcher.widgets.LauncherWidgetProvider
|
|
||||||
import de.jrpie.android.launcher.widgets.Widget
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
const val APP_WIDGET_HOST_ID = 42;
|
|
||||||
|
|
||||||
|
|
||||||
class Application : android.app.Application() {
|
class Application : android.app.Application() {
|
||||||
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
||||||
val widgets = MutableLiveData<Set<Widget>>()
|
|
||||||
val privateSpaceLocked = MutableLiveData<Boolean>()
|
val privateSpaceLocked = MutableLiveData<Boolean>()
|
||||||
lateinit var appWidgetHost: AppWidgetHost
|
|
||||||
lateinit var appWidgetManager: AppWidgetManager
|
|
||||||
|
|
||||||
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
@ -101,8 +90,6 @@ class Application : android.app.Application() {
|
||||||
customAppNames = LauncherPreferences.apps().customNames()
|
customAppNames = LauncherPreferences.apps().customNames()
|
||||||
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
|
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
|
||||||
loadApps()
|
loadApps()
|
||||||
} else if (pref == LauncherPreferences.widgets().keys().widgets()) {
|
|
||||||
widgets.postValue(LauncherPreferences.widgets().widgets() ?: setOf())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,15 +103,10 @@ class Application : android.app.Application() {
|
||||||
torchManager = TorchManager(this)
|
torchManager = TorchManager(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID)
|
|
||||||
appWidgetManager = AppWidgetManager.getInstance(this.applicationContext)
|
|
||||||
|
|
||||||
appWidgetHost.startListening()
|
|
||||||
|
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
LauncherPreferences.init(preferences, this.resources)
|
LauncherPreferences.init(preferences, this.resources)
|
||||||
|
|
||||||
|
|
||||||
// Try to restore old preferences
|
// Try to restore old preferences
|
||||||
migratePreferencesToNewVersion(this)
|
migratePreferencesToNewVersion(this)
|
||||||
|
|
||||||
|
@ -175,10 +157,4 @@ class Application : android.app.Application() {
|
||||||
apps.postValue(getApps(packageManager, applicationContext))
|
apps.postValue(getApps(packageManager, applicationContext))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
|
||||||
appWidgetHost.stopListening()
|
|
||||||
super.onTerminate()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,6 @@ import android.app.role.RoleManager
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.appwidget.AppWidgetProvider
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
|
@ -226,4 +223,4 @@ fun copyToClipboard(context: Context, text: String) {
|
||||||
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
val clipData = ClipData.newPlainText("Debug Info", text)
|
val clipData = ClipData.newPlainText("Debug Info", text)
|
||||||
clipboardManager.setPrimaryClip(clipData)
|
clipboardManager.setPrimaryClip(clipData)
|
||||||
}
|
}
|
|
@ -6,18 +6,14 @@ import android.content.SharedPreferences.Editor
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an action that can be bound to a [Gesture].
|
|
||||||
* There are four types of actions: [AppAction], [ShortcutAction], [LauncherAction] and [WidgetPanelAction]
|
|
||||||
*/
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface Action {
|
sealed interface Action {
|
||||||
fun invoke(context: Context, rect: Rect? = null): Boolean
|
fun invoke(context: Context, rect: Rect? = null): Boolean
|
||||||
|
@ -25,10 +21,6 @@ sealed interface Action {
|
||||||
fun getIcon(context: Context): Drawable?
|
fun getIcon(context: Context): Drawable?
|
||||||
fun isAvailable(context: Context): Boolean
|
fun isAvailable(context: Context): Boolean
|
||||||
|
|
||||||
fun showConfigurationDialog(context: Context, onSuccess: (Action) -> Unit) {
|
|
||||||
onSuccess(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can the action be used to reach µLauncher settings?
|
// Can the action be used to reach µLauncher settings?
|
||||||
fun canReachSettings(): Boolean
|
fun canReachSettings(): Boolean
|
||||||
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
package de.jrpie.android.launcher.actions
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.WidgetPanelActivity
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.manage.EXTRA_PANEL_ID
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.manage.WidgetPanelsRecyclerAdapter
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("action:panel")
|
|
||||||
class WidgetPanelAction(val widgetPanelId: Int) : Action {
|
|
||||||
|
|
||||||
override fun invoke(context: Context, rect: Rect?): Boolean {
|
|
||||||
|
|
||||||
if (WidgetPanel.byId(widgetPanelId) == null) {
|
|
||||||
Toast.makeText(context, R.string.alert_widget_panel_not_found, Toast.LENGTH_LONG).show()
|
|
||||||
} else {
|
|
||||||
context.startActivity(Intent(context, WidgetPanelActivity::class.java).also {
|
|
||||||
it.putExtra(EXTRA_PANEL_ID, widgetPanelId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun label(context: Context): String {
|
|
||||||
return WidgetPanel.byId(widgetPanelId)?.label
|
|
||||||
?: context.getString(R.string.list_other_open_widget_panel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isAvailable(context: Context): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun canReachSettings(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIcon(context: Context): Drawable? {
|
|
||||||
return ResourcesCompat.getDrawable(
|
|
||||||
context.resources,
|
|
||||||
R.drawable.baseline_widgets_24,
|
|
||||||
context.theme
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showConfigurationDialog(context: Context, onSuccess: (Action) -> Unit) {
|
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
|
||||||
setTitle(R.string.dialog_select_widget_panel_title)
|
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
|
|
||||||
setView(R.layout.dialog_select_widget_panel)
|
|
||||||
}.create().also { it.show() }.also { alertDialog ->
|
|
||||||
val infoTextView =
|
|
||||||
alertDialog.findViewById<TextView>(R.id.dialog_select_widget_panel_info)
|
|
||||||
alertDialog.findViewById<RecyclerView>(R.id.dialog_select_widget_panel_recycler)
|
|
||||||
?.apply {
|
|
||||||
setHasFixedSize(true)
|
|
||||||
layoutManager = LinearLayoutManager(alertDialog.context)
|
|
||||||
adapter =
|
|
||||||
WidgetPanelsRecyclerAdapter(alertDialog.context, false) { widgetPanel ->
|
|
||||||
onSuccess(WidgetPanelAction(widgetPanel.id))
|
|
||||||
alertDialog.dismiss()
|
|
||||||
}
|
|
||||||
if (adapter?.itemCount == 0) {
|
|
||||||
infoTextView?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,8 +8,6 @@ import de.jrpie.android.launcher.actions.lock.LockMethod;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetWidgetPanelSerializer;
|
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetWidgetSerializer;
|
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background;
|
import de.jrpie.android.launcher.preferences.theme.Background;
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Font;
|
import de.jrpie.android.launcher.preferences.theme.Font;
|
||||||
|
@ -74,7 +72,6 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
@Preference(name = "search_auto_launch", type = boolean.class, defaultValue = "true"),
|
@Preference(name = "search_auto_launch", type = boolean.class, defaultValue = "true"),
|
||||||
@Preference(name = "search_web", type = boolean.class, description = "false"),
|
@Preference(name = "search_web", type = boolean.class, description = "false"),
|
||||||
@Preference(name = "search_auto_open_keyboard", type = boolean.class, defaultValue = "true"),
|
@Preference(name = "search_auto_open_keyboard", type = boolean.class, defaultValue = "true"),
|
||||||
@Preference(name = "search_auto_close_keyboard", type = boolean.class, defaultValue = "false"),
|
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "enabled_gestures", prefix = "settings_enabled_gestures_", suffix = "_key", value = {
|
@PreferenceGroup(name = "enabled_gestures", prefix = "settings_enabled_gestures_", suffix = "_key", value = {
|
||||||
@Preference(name = "double_swipe", type = boolean.class, defaultValue = "true"),
|
@Preference(name = "double_swipe", type = boolean.class, defaultValue = "true"),
|
||||||
|
@ -84,9 +81,5 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
@PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = {
|
@PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = {
|
||||||
@Preference(name = "lock_method", type = LockMethod.class, defaultValue = "DEVICE_ADMIN"),
|
@Preference(name = "lock_method", type = LockMethod.class, defaultValue = "DEVICE_ADMIN"),
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "widgets", prefix = "settings_widgets_", suffix= "_key", value = {
|
|
||||||
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetSerializer.class),
|
|
||||||
@Preference(name = "custom_panels", type = Set.class, serializer = SetWidgetPanelSerializer.class)
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
public final class LauncherPreferences$Config {}
|
public final class LauncherPreferences$Config {}
|
|
@ -2,29 +2,23 @@ package de.jrpie.android.launcher.preferences
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import de.jrpie.android.launcher.BuildConfig
|
import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
|
||||||
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
import de.jrpie.android.launcher.apps.DetailedAppInfo
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion4
|
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||||
import de.jrpie.android.launcher.ui.HomeActivity
|
import de.jrpie.android.launcher.ui.HomeActivity
|
||||||
import de.jrpie.android.launcher.widgets.ClockWidget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import de.jrpie.android.launcher.widgets.deleteAllWidgets
|
|
||||||
|
|
||||||
/* Current version of the structure of preferences.
|
/* Current version of the structure of preferences.
|
||||||
* Increase when breaking changes are introduced and write an appropriate case in
|
* Increase when breaking changes are introduced and write an appropriate case in
|
||||||
* `migratePreferencesToNewVersion`
|
* `migratePreferencesToNewVersion`
|
||||||
*/
|
*/
|
||||||
const val PREFERENCE_VERSION = 5
|
const val PREFERENCE_VERSION = 4
|
||||||
const val UNKNOWN_PREFERENCE_VERSION = -1
|
const val UNKNOWN_PREFERENCE_VERSION = -1
|
||||||
private const val TAG = "Launcher - Preferences"
|
private const val TAG = "Launcher - Preferences"
|
||||||
|
|
||||||
|
@ -46,23 +40,18 @@ fun migratePreferencesToNewVersion(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
migratePreferencesFromVersion1(context)
|
migratePreferencesFromVersion1()
|
||||||
Log.i(TAG, "migration of preferences complete (1 -> ${PREFERENCE_VERSION}).")
|
Log.i(TAG, "migration of preferences complete (1 -> ${PREFERENCE_VERSION}).")
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
migratePreferencesFromVersion2(context)
|
migratePreferencesFromVersion2()
|
||||||
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
|
Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).")
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
migratePreferencesFromVersion3(context)
|
migratePreferencesFromVersion3()
|
||||||
Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).")
|
Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).")
|
||||||
}
|
}
|
||||||
|
|
||||||
4 -> {
|
|
||||||
migratePreferencesFromVersion4(context)
|
|
||||||
Log.i(TAG, "migration of preferences complete (4 -> ${PREFERENCE_VERSION}).")
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
|
@ -82,17 +71,6 @@ fun resetPreferences(context: Context) {
|
||||||
Log.i(TAG, "Resetting preferences")
|
Log.i(TAG, "Resetting preferences")
|
||||||
LauncherPreferences.clear()
|
LauncherPreferences.clear()
|
||||||
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
||||||
deleteAllWidgets(context)
|
|
||||||
|
|
||||||
LauncherPreferences.widgets().widgets(
|
|
||||||
setOf(
|
|
||||||
ClockWidget(
|
|
||||||
(context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(),
|
|
||||||
WidgetPosition(1, 3, 10, 4),
|
|
||||||
WidgetPanel.HOME.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package de.jrpie.android.launcher.preferences.legacy
|
package de.jrpie.android.launcher.preferences.legacy
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.content.edit
|
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.AppAction
|
import de.jrpie.android.launcher.actions.AppAction
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -15,6 +13,7 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -130,7 +129,7 @@ private fun migrateAction(key: String) {
|
||||||
* Migrate preferences from version 1 (used until version j-0.0.18) to the current format
|
* Migrate preferences from version 1 (used until version j-0.0.18) to the current format
|
||||||
* (see [PREFERENCE_VERSION])
|
* (see [PREFERENCE_VERSION])
|
||||||
*/
|
*/
|
||||||
fun migratePreferencesFromVersion1(context: Context) {
|
fun migratePreferencesFromVersion1() {
|
||||||
assert(LauncherPreferences.internal().versionCode() == 1)
|
assert(LauncherPreferences.internal().versionCode() == 1)
|
||||||
Gesture.entries.forEach { g -> migrateAction(g.id) }
|
Gesture.entries.forEach { g -> migrateAction(g.id) }
|
||||||
migrateAppInfoSet(LauncherPreferences.apps().keys().hidden())
|
migrateAppInfoSet(LauncherPreferences.apps().keys().hidden())
|
||||||
|
@ -138,5 +137,5 @@ fun migratePreferencesFromVersion1(context: Context) {
|
||||||
migrateAppInfoStringMap(LauncherPreferences.apps().keys().customNames())
|
migrateAppInfoStringMap(LauncherPreferences.apps().keys().customNames())
|
||||||
LauncherPreferences.internal().versionCode(2)
|
LauncherPreferences.internal().versionCode(2)
|
||||||
|
|
||||||
migratePreferencesFromVersion2(context)
|
migratePreferencesFromVersion2()
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package de.jrpie.android.launcher.preferences.legacy
|
package de.jrpie.android.launcher.preferences.legacy
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
|
@ -12,10 +11,10 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
||||||
* Migrate preferences from version 2 (used until version 0.0.21) to the current format
|
* Migrate preferences from version 2 (used until version 0.0.21) to the current format
|
||||||
* (see [PREFERENCE_VERSION])
|
* (see [PREFERENCE_VERSION])
|
||||||
*/
|
*/
|
||||||
fun migratePreferencesFromVersion2(context: Context) {
|
fun migratePreferencesFromVersion2() {
|
||||||
assert(LauncherPreferences.internal().versionCode() == 2)
|
assert(LauncherPreferences.internal().versionCode() == 2)
|
||||||
// previously there was no setting for this
|
// previously there was no setting for this
|
||||||
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
|
Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE)
|
||||||
LauncherPreferences.internal().versionCode(3)
|
LauncherPreferences.internal().versionCode(3)
|
||||||
migratePreferencesFromVersion3(context)
|
migratePreferencesFromVersion3()
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
package de.jrpie.android.launcher.preferences.legacy
|
package de.jrpie.android.launcher.preferences.legacy
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.Editor
|
import android.content.SharedPreferences.Editor
|
||||||
import androidx.core.content.edit
|
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
|
||||||
import de.jrpie.android.launcher.apps.AppInfo
|
import de.jrpie.android.launcher.apps.AppInfo
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
||||||
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer
|
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer
|
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
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
|
* Migrate preferences from version 3 (used until version 0.0.23) to the current format
|
||||||
|
@ -70,7 +70,8 @@ private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun migratePreferencesFromVersion3(context: Context) {
|
fun migratePreferencesFromVersion3() {
|
||||||
|
assert(PREFERENCE_VERSION == 4)
|
||||||
assert(LauncherPreferences.internal().versionCode() == 3)
|
assert(LauncherPreferences.internal().versionCode() == 3)
|
||||||
|
|
||||||
val preferences = LauncherPreferences.getSharedPreferences()
|
val preferences = LauncherPreferences.getSharedPreferences()
|
||||||
|
@ -81,5 +82,4 @@ fun migratePreferencesFromVersion3(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
LauncherPreferences.internal().versionCode(4)
|
LauncherPreferences.internal().versionCode(4)
|
||||||
migratePreferencesFromVersion4(context)
|
|
||||||
}
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package de.jrpie.android.launcher.preferences.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION
|
|
||||||
import de.jrpie.android.launcher.widgets.ClockWidget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
|
|
||||||
fun migratePreferencesFromVersion4(context: Context) {
|
|
||||||
assert(PREFERENCE_VERSION == 5)
|
|
||||||
assert(LauncherPreferences.internal().versionCode() == 4)
|
|
||||||
|
|
||||||
LauncherPreferences.widgets().widgets(
|
|
||||||
setOf(
|
|
||||||
ClockWidget(
|
|
||||||
(context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(),
|
|
||||||
WidgetPosition(1, 3, 10, 4),
|
|
||||||
WidgetPanel.HOME.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LauncherPreferences.internal().versionCode(5)
|
|
||||||
}
|
|
|
@ -3,10 +3,10 @@ package de.jrpie.android.launcher.preferences.legacy
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.edit
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background
|
import de.jrpie.android.launcher.preferences.theme.Background
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
private fun migrateStringPreference(
|
private fun migrateStringPreference(
|
||||||
|
@ -392,5 +392,5 @@ fun migratePreferencesFromVersionUnknown(context: Context) {
|
||||||
LauncherPreferences.internal().versionCode(1)
|
LauncherPreferences.internal().versionCode(1)
|
||||||
Log.i(TAG, "migrated preferences to version 1.")
|
Log.i(TAG, "migrated preferences to version 1.")
|
||||||
|
|
||||||
migratePreferencesFromVersion1(context)
|
migratePreferencesFromVersion1()
|
||||||
}
|
}
|
|
@ -4,8 +4,6 @@ package de.jrpie.android.launcher.preferences.serialization
|
||||||
|
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||||
import de.jrpie.android.launcher.widgets.Widget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -30,40 +28,6 @@ class SetAbstractAppInfoPreferenceSerializer :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
class SetWidgetSerializer :
|
|
||||||
PreferenceSerializer<java.util.Set<Widget>?, java.util.Set<java.lang.String>?> {
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
|
||||||
override fun serialize(value: java.util.Set<Widget>?): java.util.Set<java.lang.String>? {
|
|
||||||
return value?.map(Widget::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<Widget>? {
|
|
||||||
return value?.map(java.lang.String::toString)?.map(Widget::deserialize)
|
|
||||||
?.toHashSet() as? java.util.Set<Widget>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
class SetWidgetPanelSerializer :
|
|
||||||
PreferenceSerializer<java.util.Set<WidgetPanel>?, java.util.Set<java.lang.String>?> {
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
|
||||||
override fun serialize(value: java.util.Set<WidgetPanel>?): java.util.Set<java.lang.String>? {
|
|
||||||
return value?.map(WidgetPanel::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<WidgetPanel>? {
|
|
||||||
return value?.map(java.lang.String::toString)?.map(WidgetPanel::deserialize)
|
|
||||||
?.toHashSet() as? java.util.Set<WidgetPanel>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class SetPinnedShortcutInfoPreferenceSerializer :
|
class SetPinnedShortcutInfoPreferenceSerializer :
|
||||||
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package de.jrpie.android.launcher.ui
|
package de.jrpie.android.launcher.ui
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.ColorMatrix
|
import android.graphics.ColorMatrix
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.ColorMatrixColorFilter
|
||||||
|
@ -39,17 +38,10 @@ fun ImageView.transformGrayscale(grayscale: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Taken from https://stackoverflow.com/a/50743764
|
// Taken from https://stackoverflow.com/a/50743764/12787264
|
||||||
fun View.openSoftKeyboard(context: Context) {
|
fun View.openSoftKeyboard(context: Context) {
|
||||||
this.requestFocus()
|
this.requestFocus()
|
||||||
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
// open the soft keyboard
|
||||||
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/17789187
|
|
||||||
fun closeSoftKeyboard(activity: Activity) {
|
|
||||||
activity.currentFocus?.let { focus ->
|
|
||||||
(activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
|
||||||
.hideSoftInputFromWindow( focus.windowToken, 0 )
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package de.jrpie.android.launcher.ui
|
package de.jrpie.android.launcher.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
@ -11,7 +10,8 @@ import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.window.OnBackInvokedDispatcher
|
import android.window.OnBackInvokedDispatcher
|
||||||
import de.jrpie.android.launcher.Application
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
@ -20,6 +20,7 @@ import de.jrpie.android.launcher.databinding.HomeBinding
|
||||||
import de.jrpie.android.launcher.openTutorial
|
import de.jrpie.android.launcher.openTutorial
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [HomeActivity] is the actual application Launcher,
|
* [HomeActivity] is the actual application Launcher,
|
||||||
|
@ -33,10 +34,10 @@ import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
* - Setting global variables (preferences etc.)
|
* - Setting global variables (preferences etc.)
|
||||||
* - Opening the [TutorialActivity] on new installations
|
* - Opening the [TutorialActivity] on new installations
|
||||||
*/
|
*/
|
||||||
class HomeActivity : UIObject, Activity() {
|
class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: HomeBinding
|
private lateinit var binding: HomeBinding
|
||||||
private var touchGestureDetector: TouchGestureDetector? = null
|
private lateinit var touchGestureDetector: TouchGestureDetector
|
||||||
|
|
||||||
private var sharedPreferencesListener =
|
private var sharedPreferencesListener =
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
||||||
|
@ -44,26 +45,40 @@ class HomeActivity : UIObject, Activity() {
|
||||||
prefKey?.startsWith("display.") == true
|
prefKey?.startsWith("display.") == true
|
||||||
) {
|
) {
|
||||||
recreate()
|
recreate()
|
||||||
} else if (prefKey?.startsWith("action.") == true) {
|
|
||||||
updateSettingsFallbackButtonVisibility()
|
|
||||||
} else if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
|
|
||||||
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
|
|
||||||
LauncherPreferences.widgets().widgets()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefKey?.startsWith("action.") == true) {
|
||||||
|
updateSettingsFallbackButtonVisibility()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super<Activity>.onCreate(savedInstanceState)
|
super<AppCompatActivity>.onCreate(savedInstanceState)
|
||||||
super<UIObject>.onCreate()
|
super<UIObject>.onCreate()
|
||||||
|
|
||||||
|
touchGestureDetector = TouchGestureDetector(
|
||||||
|
this, 0, 0,
|
||||||
|
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
|
||||||
|
)
|
||||||
|
touchGestureDetector.updateScreenSize(windowManager)
|
||||||
|
|
||||||
// Initialise layout
|
// Initialise layout
|
||||||
binding = HomeBinding.inflate(layoutInflater)
|
binding = HomeBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
binding.root.setOnApplyWindowInsetsListener { _, windowInsets ->
|
||||||
|
@Suppress("deprecation") // required to support API 29
|
||||||
|
val insets = windowInsets.systemGestureInsets
|
||||||
|
touchGestureDetector.setSystemGestureInsets(insets)
|
||||||
|
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle back key / gesture on Android 13+, cf. onKeyDown()
|
// Handle back key / gesture on Android 13+, cf. onKeyDown()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
onBackInvokedDispatcher.registerOnBackInvokedCallback(
|
onBackInvokedDispatcher.registerOnBackInvokedCallback(
|
||||||
|
@ -79,11 +94,12 @@ class HomeActivity : UIObject, Activity() {
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
touchGestureDetector?.updateScreenSize(windowManager)
|
touchGestureDetector.updateScreenSize(windowManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super<Activity>.onStart()
|
super<AppCompatActivity>.onStart()
|
||||||
|
|
||||||
super<UIObject>.onStart()
|
super<UIObject>.onStart()
|
||||||
|
|
||||||
// If the tutorial was not finished, start it
|
// If the tutorial was not finished, start it
|
||||||
|
@ -94,15 +110,6 @@ class HomeActivity : UIObject, Activity() {
|
||||||
LauncherPreferences.getSharedPreferences()
|
LauncherPreferences.getSharedPreferences()
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
|
||||||
(application as Application).appWidgetHost.startListening()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
(application as Application).appWidgetHost.stopListening()
|
|
||||||
super.onStop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
@ -128,6 +135,44 @@ class HomeActivity : UIObject, Activity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initClock() {
|
||||||
|
val locale = Locale.getDefault()
|
||||||
|
val dateVisible = LauncherPreferences.clock().dateVisible()
|
||||||
|
val timeVisible = LauncherPreferences.clock().timeVisible()
|
||||||
|
|
||||||
|
var dateFMT = "yyyy-MM-dd"
|
||||||
|
var timeFMT = "HH:mm"
|
||||||
|
if (LauncherPreferences.clock().showSeconds()) {
|
||||||
|
timeFMT += ":ss"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LauncherPreferences.clock().localized()) {
|
||||||
|
dateFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, dateFMT)
|
||||||
|
timeFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, timeFMT)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upperFormat = dateFMT
|
||||||
|
var lowerFormat = timeFMT
|
||||||
|
var upperVisible = dateVisible
|
||||||
|
var lowerVisible = timeVisible
|
||||||
|
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
upperFormat = lowerFormat.also { lowerFormat = upperFormat }
|
||||||
|
upperVisible = lowerVisible.also { lowerVisible = upperVisible }
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.homeUpperView.isVisible = upperVisible
|
||||||
|
binding.homeLowerView.isVisible = lowerVisible
|
||||||
|
|
||||||
|
binding.homeUpperView.setTextColor(LauncherPreferences.clock().color())
|
||||||
|
binding.homeLowerView.setTextColor(LauncherPreferences.clock().color())
|
||||||
|
|
||||||
|
binding.homeLowerView.format24Hour = lowerFormat
|
||||||
|
binding.homeUpperView.format24Hour = upperFormat
|
||||||
|
binding.homeLowerView.format12Hour = lowerFormat
|
||||||
|
binding.homeUpperView.format12Hour = upperFormat
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
override fun getTheme(): Resources.Theme {
|
||||||
val mTheme = modifyTheme(super.getTheme())
|
val mTheme = modifyTheme(super.getTheme())
|
||||||
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
||||||
|
@ -143,33 +188,11 @@ class HomeActivity : UIObject, Activity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
/* This should be initialized in onCreate()
|
touchGestureDetector.edgeWidth =
|
||||||
However on some devices there seems to be a bug where the touchGestureDetector
|
|
||||||
is not working properly after resuming the app.
|
|
||||||
Reinitializing the touchGestureDetector every time the app is resumed might help to fix that.
|
|
||||||
(see issue #138)
|
|
||||||
*/
|
|
||||||
touchGestureDetector = TouchGestureDetector(
|
|
||||||
this, 0, 0,
|
|
||||||
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
|
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
|
||||||
).also {
|
|
||||||
it.updateScreenSize(windowManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
initClock()
|
||||||
binding.root.setOnApplyWindowInsetsListener { _, windowInsets ->
|
|
||||||
@Suppress("deprecation") // required to support API 29
|
|
||||||
val insets = windowInsets.systemGestureInsets
|
|
||||||
touchGestureDetector?.setSystemGestureInsets(insets)
|
|
||||||
|
|
||||||
windowInsets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateSettingsFallbackButtonVisibility()
|
updateSettingsFallbackButtonVisibility()
|
||||||
|
|
||||||
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
|
|
||||||
LauncherPreferences.widgets().widgets()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -207,10 +230,30 @@ class HomeActivity : UIObject, Activity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
touchGestureDetector?.onTouchEvent(event)
|
touchGestureDetector.onTouchEvent(event)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setOnClicks() {
|
||||||
|
|
||||||
|
binding.homeUpperView.setOnClickListener {
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
Gesture.TIME(this)
|
||||||
|
} else {
|
||||||
|
Gesture.DATE(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.homeLowerView.setOnClickListener {
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
Gesture.DATE(this)
|
||||||
|
} else {
|
||||||
|
Gesture.TIME(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleBack() {
|
private fun handleBack() {
|
||||||
Gesture.BACK(this)
|
Gesture.BACK(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,21 +49,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
|
||||||
|
|
||||||
val request = launcherApps.getPinItemRequest(intent)
|
val request = launcherApps.getPinItemRequest(intent)
|
||||||
this.request = request
|
this.request = request
|
||||||
if (request == null) {
|
if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
request.getAppWidgetProviderInfo(this)
|
|
||||||
// startActivity()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,13 @@ import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
import de.jrpie.android.launcher.apps.AppFilter
|
||||||
import de.jrpie.android.launcher.databinding.ListAppsBinding
|
import de.jrpie.android.launcher.databinding.ListAppsBinding
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
import de.jrpie.android.launcher.ui.closeSoftKeyboard
|
|
||||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
import de.jrpie.android.launcher.ui.openSoftKeyboard
|
import de.jrpie.android.launcher.ui.openSoftKeyboard
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,20 +90,6 @@ class ListFragmentApps : Fragment(), UIObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter = appsRecyclerAdapter
|
adapter = appsRecyclerAdapter
|
||||||
if (LauncherPreferences.functionality().searchAutoCloseKeyboard()) {
|
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
var totalDy: Int = 0
|
|
||||||
var threshold = (resources.displayMetrics.density * 100).toInt()
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
totalDy += dy
|
|
||||||
|
|
||||||
if (totalDy.absoluteValue > 100) {
|
|
||||||
totalDy = 0
|
|
||||||
closeSoftKeyboard(requireActivity())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.listAppsSearchview.setOnQueryTextListener(object :
|
binding.listAppsSearchview.setOnQueryTextListener(object :
|
||||||
|
|
|
@ -11,7 +11,6 @@ import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
import de.jrpie.android.launcher.actions.LauncherAction
|
import de.jrpie.android.launcher.actions.LauncherAction
|
||||||
import de.jrpie.android.launcher.actions.WidgetPanelAction
|
|
||||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,10 +23,8 @@ import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
class OtherRecyclerAdapter(val activity: Activity) :
|
class OtherRecyclerAdapter(val activity: Activity) :
|
||||||
RecyclerView.Adapter<OtherRecyclerAdapter.ViewHolder>() {
|
RecyclerView.Adapter<OtherRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val othersList: Array<Action> =
|
private val othersList: Array<LauncherAction> =
|
||||||
LauncherAction.entries.filter { it.isAvailable(activity) }
|
LauncherAction.entries.filter { it.isAvailable(activity) }.toTypedArray()
|
||||||
.plus(WidgetPanelAction(-1))
|
|
||||||
.toTypedArray()
|
|
||||||
|
|
||||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
@ -39,12 +36,10 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
||||||
val pos = bindingAdapterPosition
|
val pos = bindingAdapterPosition
|
||||||
val content = othersList[pos]
|
val content = othersList[pos]
|
||||||
|
|
||||||
|
activity.finish()
|
||||||
val gestureId = (activity as? ListActivity)?.forGesture ?: return
|
val gestureId = (activity as? ListActivity)?.forGesture ?: return
|
||||||
val gesture = Gesture.byId(gestureId) ?: return
|
val gesture = Gesture.byId(gestureId) ?: return
|
||||||
content.showConfigurationDialog(activity) { configuredAction ->
|
Action.setActionForGesture(gesture, content)
|
||||||
Action.setActionForGesture(gesture, configuredAction)
|
|
||||||
activity.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -53,11 +48,11 @@ class OtherRecyclerAdapter(val activity: Activity) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
||||||
val otherLabel = othersList[i].label(activity)
|
val otherLabel = activity.getString(othersList[i].label)
|
||||||
val icon = othersList[i].getIcon(activity)
|
val icon = othersList[i].icon
|
||||||
|
|
||||||
viewHolder.textView.text = otherLabel
|
viewHolder.textView.text = otherLabel
|
||||||
viewHolder.iconView.setImageDrawable(icon)
|
viewHolder.iconView.setImageResource(icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
|
|
|
@ -11,8 +11,6 @@ import de.jrpie.android.launcher.actions.openAppsList
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme
|
||||||
import de.jrpie.android.launcher.setDefaultHomeScreen
|
import de.jrpie.android.launcher.setDefaultHomeScreen
|
||||||
import de.jrpie.android.launcher.ui.widgets.manage.ManageWidgetPanelsActivity
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.manage.ManageWidgetsActivity
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,22 +81,6 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val manageWidgets = findPreference<androidx.preference.Preference>(
|
|
||||||
LauncherPreferences.widgets().keys().widgets()
|
|
||||||
)
|
|
||||||
manageWidgets?.setOnPreferenceClickListener {
|
|
||||||
startActivity(Intent(requireActivity(), ManageWidgetsActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val manageWidgetPanels = findPreference<androidx.preference.Preference>(
|
|
||||||
LauncherPreferences.widgets().keys().customPanels()
|
|
||||||
)
|
|
||||||
manageWidgetPanels?.setOnPreferenceClickListener {
|
|
||||||
startActivity(Intent(requireActivity(), ManageWidgetPanelsActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val hiddenApps = findPreference<androidx.preference.Preference>(
|
val hiddenApps = findPreference<androidx.preference.Preference>(
|
||||||
LauncherPreferences.apps().keys().hidden()
|
LauncherPreferences.apps().keys().hidden()
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
|
||||||
import de.jrpie.android.launcher.databinding.ClockBinding
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) {
|
|
||||||
|
|
||||||
val binding: ClockBinding = ClockBinding.inflate(LayoutInflater.from(context), this, true)
|
|
||||||
init {
|
|
||||||
initClock()
|
|
||||||
setOnClicks()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun initClock() {
|
|
||||||
val locale = Locale.getDefault()
|
|
||||||
val dateVisible = LauncherPreferences.clock().dateVisible()
|
|
||||||
val timeVisible = LauncherPreferences.clock().timeVisible()
|
|
||||||
|
|
||||||
var dateFMT = "yyyy-MM-dd"
|
|
||||||
var timeFMT = "HH:mm"
|
|
||||||
if (LauncherPreferences.clock().showSeconds()) {
|
|
||||||
timeFMT += ":ss"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LauncherPreferences.clock().localized()) {
|
|
||||||
dateFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, dateFMT)
|
|
||||||
timeFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, timeFMT)
|
|
||||||
}
|
|
||||||
|
|
||||||
var upperFormat = dateFMT
|
|
||||||
var lowerFormat = timeFMT
|
|
||||||
var upperVisible = dateVisible
|
|
||||||
var lowerVisible = timeVisible
|
|
||||||
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
upperFormat = lowerFormat.also { lowerFormat = upperFormat }
|
|
||||||
upperVisible = lowerVisible.also { lowerVisible = upperVisible }
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.clockUpperView.isVisible = upperVisible
|
|
||||||
binding.clockLowerView.isVisible = lowerVisible
|
|
||||||
|
|
||||||
binding.clockUpperView.setTextColor(LauncherPreferences.clock().color())
|
|
||||||
binding.clockLowerView.setTextColor(LauncherPreferences.clock().color())
|
|
||||||
|
|
||||||
binding.clockLowerView.format24Hour = lowerFormat
|
|
||||||
binding.clockUpperView.format24Hour = upperFormat
|
|
||||||
binding.clockLowerView.format12Hour = lowerFormat
|
|
||||||
binding.clockUpperView.format12Hour = upperFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnClicks() {
|
|
||||||
binding.clockUpperView.setOnClickListener {
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
Gesture.TIME(context)
|
|
||||||
} else {
|
|
||||||
Gesture.DATE(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.clockLowerView.setOnClickListener {
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
Gesture.DATE(context)
|
|
||||||
} else {
|
|
||||||
Gesture.TIME(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.PointF
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.View.MeasureSpec.makeMeasureSpec
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.graphics.contains
|
|
||||||
import androidx.core.view.size
|
|
||||||
import de.jrpie.android.launcher.widgets.Widget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This only works in an Activity, not AppCompatActivity
|
|
||||||
*/
|
|
||||||
open class WidgetContainerView(
|
|
||||||
var widgetPanelId: Int,
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null
|
|
||||||
) : ViewGroup(context, attrs) {
|
|
||||||
constructor(context: Context, attrs: AttributeSet) : this(WidgetPanel.HOME.id, context, attrs)
|
|
||||||
|
|
||||||
var widgetViewById = HashMap<Int, View>()
|
|
||||||
|
|
||||||
open fun updateWidgets(activity: Activity, widgets: Collection<Widget>?) {
|
|
||||||
synchronized(widgetViewById) {
|
|
||||||
if (widgets == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.i("WidgetContainer", "updating ${activity.localClassName}")
|
|
||||||
widgetViewById.forEach { removeView(it.value) }
|
|
||||||
widgetViewById.clear()
|
|
||||||
widgets.filter { it.panelId == widgetPanelId }.forEach { widget ->
|
|
||||||
widget.createView(activity)?.let {
|
|
||||||
addView(it, LayoutParams(widget.position))
|
|
||||||
widgetViewById.put(widget.id, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
|
||||||
if (ev == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val position = PointF(ev.x, ev.y)
|
|
||||||
|
|
||||||
return widgetViewById.filter {
|
|
||||||
RectF(
|
|
||||||
it.value.x,
|
|
||||||
it.value.y,
|
|
||||||
it.value.x + it.value.width,
|
|
||||||
it.value.y + it.value.height
|
|
||||||
).contains(position) == true
|
|
||||||
}.any {
|
|
||||||
Widget.byId(context, it.key)?.allowInteraction == false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
||||||
|
|
||||||
var maxHeight = suggestedMinimumHeight
|
|
||||||
var maxWidth = suggestedMinimumWidth
|
|
||||||
|
|
||||||
val mWidth = MeasureSpec.getSize(widthMeasureSpec)
|
|
||||||
val mHeight = MeasureSpec.getSize(heightMeasureSpec)
|
|
||||||
|
|
||||||
(0..<size).map { getChildAt(it) }.forEach {
|
|
||||||
val position = (it.layoutParams as LayoutParams).position.getAbsoluteRect(mWidth, mHeight)
|
|
||||||
it.measure(makeMeasureSpec(position.width(), MeasureSpec.EXACTLY), makeMeasureSpec(position.height(), MeasureSpec.EXACTLY))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find rightmost and bottom-most child
|
|
||||||
(0..<size).map { getChildAt(it) }.filter { it.visibility != GONE }.forEach {
|
|
||||||
val position = (it.layoutParams as LayoutParams).position.getAbsoluteRect(mWidth, mHeight)
|
|
||||||
maxWidth = max(maxWidth, position.left + it.measuredWidth)
|
|
||||||
maxHeight = max(maxHeight, position.top + it.measuredHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
setMeasuredDimension(
|
|
||||||
resolveSizeAndState(maxWidth.toInt(), widthMeasureSpec, 0),
|
|
||||||
resolveSizeAndState(maxHeight.toInt(), heightMeasureSpec, 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of layout parameters with a width of
|
|
||||||
* [ViewGroup.LayoutParams.WRAP_CONTENT],
|
|
||||||
* a height of [ViewGroup.LayoutParams.WRAP_CONTENT]
|
|
||||||
* and with the coordinates (0, 0).
|
|
||||||
*/
|
|
||||||
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
|
|
||||||
return LayoutParams(WidgetPosition(0,0,1,1))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
||||||
for (i in 0..<size) {
|
|
||||||
val child = getChildAt(i)
|
|
||||||
val lp = child.layoutParams as LayoutParams
|
|
||||||
val position = lp.position.getAbsoluteRect(r - l, b - t)
|
|
||||||
child.layout(position.left, position.top, position.right, position.bottom)
|
|
||||||
child.layoutParams.width = position.width()
|
|
||||||
child.layoutParams.height = position.height()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
|
|
||||||
return LayoutParams(context, attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override to allow type-checking of LayoutParams.
|
|
||||||
override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
|
|
||||||
return p is LayoutParams
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
|
|
||||||
return LayoutParams(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldDelayChildPressedState(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
class LayoutParams : ViewGroup.LayoutParams {
|
|
||||||
var position = WidgetPosition(0,0,4,4)
|
|
||||||
|
|
||||||
|
|
||||||
constructor(position: WidgetPosition) : super(WRAP_CONTENT, WRAP_CONTENT) {
|
|
||||||
this.position = position
|
|
||||||
}
|
|
||||||
constructor(c: Context, attrs: AttributeSet?) : super(c, attrs)
|
|
||||||
constructor(source: ViewGroup.LayoutParams?) : super(source)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.os.Bundle
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.databinding.ActivityWidgetPanelBinding
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.manage.EXTRA_PANEL_ID
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
|
|
||||||
class WidgetPanelActivity : Activity(), UIObject {
|
|
||||||
lateinit var binding: ActivityWidgetPanelBinding
|
|
||||||
var widgetPanelId: Int = WidgetPanel.Companion.HOME.id
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super<Activity>.onCreate(savedInstanceState)
|
|
||||||
super<UIObject>.onCreate()
|
|
||||||
widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.Companion.HOME.id)
|
|
||||||
val binding = ActivityWidgetPanelBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
binding.widgetPanelWidgetContainer.widgetPanelId = widgetPanelId
|
|
||||||
binding.widgetPanelWidgetContainer.updateWidgets(
|
|
||||||
this,
|
|
||||||
LauncherPreferences.widgets().widgets()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
|
||||||
val mTheme = modifyTheme(super.getTheme())
|
|
||||||
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
|
||||||
LauncherPreferences.clock().font().applyToTheme(mTheme)
|
|
||||||
LauncherPreferences.theme().colorTheme().applyToTheme(
|
|
||||||
mTheme,
|
|
||||||
LauncherPreferences.theme().textShadow()
|
|
||||||
)
|
|
||||||
|
|
||||||
return mTheme
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super<Activity>.onStart()
|
|
||||||
super<UIObject>.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isHomeScreen(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.databinding.ActivityManageWidgetPanelsBinding
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidgetPanel
|
|
||||||
|
|
||||||
class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject {
|
|
||||||
|
|
||||||
private val sharedPreferencesListener =
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
|
||||||
if (prefKey == LauncherPreferences.widgets().keys().customPanels()) {
|
|
||||||
viewAdapter.widgetPanels =
|
|
||||||
(LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray()
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
viewAdapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private lateinit var binding: ActivityManageWidgetPanelsBinding
|
|
||||||
private lateinit var viewAdapter: WidgetPanelsRecyclerAdapter
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
|
||||||
super<UIObject>.onCreate()
|
|
||||||
|
|
||||||
binding = ActivityManageWidgetPanelsBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.main)
|
|
||||||
|
|
||||||
val viewManager = LinearLayoutManager(this)
|
|
||||||
viewAdapter = WidgetPanelsRecyclerAdapter(this, true) { widgetPanel ->
|
|
||||||
startActivity(
|
|
||||||
Intent(
|
|
||||||
this@ManageWidgetPanelsActivity,
|
|
||||||
ManageWidgetsActivity::class.java
|
|
||||||
).also {
|
|
||||||
it.putExtra(EXTRA_PANEL_ID, widgetPanel.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
binding.manageWidgetPanelsRecycler.apply {
|
|
||||||
// improve performance (since content changes don't change the layout size)
|
|
||||||
setHasFixedSize(true)
|
|
||||||
layoutManager = viewManager
|
|
||||||
adapter = viewAdapter
|
|
||||||
}
|
|
||||||
binding.manageWidgetPanelsClose.setOnClickListener { finish() }
|
|
||||||
binding.manageWidgetPanelsAddPanel.setOnClickListener {
|
|
||||||
AlertDialog.Builder(this@ManageWidgetPanelsActivity, R.style.AlertDialogCustom).apply {
|
|
||||||
setTitle(R.string.dialog_create_widget_panel_title)
|
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
|
|
||||||
setPositiveButton(R.string.dialog_ok) { dialogInterface, _ ->
|
|
||||||
val panelId = WidgetPanel.allocateId()
|
|
||||||
val label = (dialogInterface as? AlertDialog)
|
|
||||||
?.findViewById<EditText>(R.id.dialog_create_widget_panel_edit_text)?.text?.toString()
|
|
||||||
?: (getString(R.string.widget_panel_default_name, panelId))
|
|
||||||
|
|
||||||
updateWidgetPanel(WidgetPanel(panelId, label))
|
|
||||||
}
|
|
||||||
setView(R.layout.dialog_create_widget_panel)
|
|
||||||
}.create().also { it.show() }.apply {
|
|
||||||
findViewById<EditText>(R.id.dialog_create_widget_panel_edit_text)
|
|
||||||
?.setText(
|
|
||||||
getString(
|
|
||||||
R.string.widget_panel_default_name,
|
|
||||||
WidgetPanel.allocateId()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super<AppCompatActivity>.onStart()
|
|
||||||
super<UIObject>.onStart()
|
|
||||||
LauncherPreferences.getSharedPreferences()
|
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
LauncherPreferences.getSharedPreferences()
|
|
||||||
.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
|
||||||
return modifyTheme(super.getTheme())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setOnClicks() {
|
|
||||||
binding.manageWidgetPanelsClose.setOnClickListener { finish() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
|
||||||
import de.jrpie.android.launcher.widgets.AppWidget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
|
|
||||||
// http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html
|
|
||||||
|
|
||||||
const val REQUEST_CREATE_APPWIDGET = 1
|
|
||||||
const val REQUEST_PICK_APPWIDGET = 2
|
|
||||||
|
|
||||||
const val EXTRA_PANEL_ID = "widgetPanelId"
|
|
||||||
|
|
||||||
// We can't use AppCompatActivity, since some AppWidgets don't work there.
|
|
||||||
class ManageWidgetsActivity : Activity(), UIObject {
|
|
||||||
|
|
||||||
var panelId: Int = WidgetPanel.HOME.id
|
|
||||||
|
|
||||||
private var sharedPreferencesListener =
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
|
||||||
if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
|
|
||||||
// We can't observe the livedata because this is not an AppCompatActivity
|
|
||||||
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this,
|
|
||||||
LauncherPreferences.widgets().widgets()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super<Activity>.onCreate(savedInstanceState)
|
|
||||||
super<UIObject>.onCreate()
|
|
||||||
setContentView(R.layout.activity_manage_widgets)
|
|
||||||
|
|
||||||
panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id
|
|
||||||
|
|
||||||
findViewById<FloatingActionButton>(R.id.manage_widgets_button_add).setOnClickListener {
|
|
||||||
selectWidget()
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
|
||||||
insets
|
|
||||||
}
|
|
||||||
|
|
||||||
findViewById<WidgetContainerView>(R.id.manage_widgets_container).let {
|
|
||||||
it.widgetPanelId = panelId
|
|
||||||
it.updateWidgets(this, (application as Application).widgets.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super<Activity>.onStart()
|
|
||||||
super<UIObject>.onStart()
|
|
||||||
|
|
||||||
LauncherPreferences.getSharedPreferences()
|
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this,
|
|
||||||
LauncherPreferences.widgets().widgets()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
override fun getTheme(): Resources.Theme {
|
|
||||||
val mTheme = modifyTheme(super.getTheme())
|
|
||||||
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
|
||||||
LauncherPreferences.clock().font().applyToTheme(mTheme)
|
|
||||||
LauncherPreferences.theme().colorTheme().applyToTheme(
|
|
||||||
mTheme,
|
|
||||||
LauncherPreferences.theme().textShadow()
|
|
||||||
)
|
|
||||||
return mTheme
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
LauncherPreferences.getSharedPreferences()
|
|
||||||
.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun selectWidget() {
|
|
||||||
val appWidgetHost = (application as Application).appWidgetHost
|
|
||||||
startActivityForResult(
|
|
||||||
Intent(this, SelectWidgetActivity::class.java).also {
|
|
||||||
it.putExtra(
|
|
||||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
|
||||||
appWidgetHost.allocateAppWidgetId()
|
|
||||||
)
|
|
||||||
it.putExtra(
|
|
||||||
EXTRA_PANEL_ID,
|
|
||||||
panelId
|
|
||||||
)
|
|
||||||
}, REQUEST_PICK_APPWIDGET
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun createWidget(data: Intent) {
|
|
||||||
Log.i("Launcher", "creating widget")
|
|
||||||
val appWidgetManager = (application as Application).appWidgetManager
|
|
||||||
val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
|
|
||||||
|
|
||||||
val provider = appWidgetManager.getAppWidgetInfo(appWidgetId)
|
|
||||||
|
|
||||||
val display = windowManager.defaultDisplay
|
|
||||||
|
|
||||||
val position = WidgetPosition.fromAbsoluteRect(
|
|
||||||
Rect(0,0,
|
|
||||||
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth),
|
|
||||||
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight)
|
|
||||||
),
|
|
||||||
display.width,
|
|
||||||
display.height
|
|
||||||
)
|
|
||||||
|
|
||||||
val widget = AppWidget(appWidgetId, position, panelId, provider)
|
|
||||||
LauncherPreferences.widgets().widgets(
|
|
||||||
(LauncherPreferences.widgets().widgets() ?: HashSet()).also {
|
|
||||||
it.add(widget)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun configureWidget(data: Intent) {
|
|
||||||
val extras = data.extras
|
|
||||||
val appWidgetId = extras!!.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
|
||||||
val widget = AppWidget(appWidgetId, panelId = panelId)
|
|
||||||
if (widget.isConfigurable(this)) {
|
|
||||||
widget.configure(this, REQUEST_CREATE_APPWIDGET)
|
|
||||||
} else {
|
|
||||||
createWidget(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(
|
|
||||||
requestCode: Int, resultCode: Int,
|
|
||||||
data: Intent?
|
|
||||||
) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
if (requestCode == REQUEST_PICK_APPWIDGET) {
|
|
||||||
configureWidget(data!!)
|
|
||||||
} else if (requestCode == REQUEST_CREATE_APPWIDGET) {
|
|
||||||
createWidget(data!!)
|
|
||||||
}
|
|
||||||
} else if (resultCode == RESULT_CANCELED && data != null) {
|
|
||||||
val appWidgetId =
|
|
||||||
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
|
||||||
if (appWidgetId != -1) {
|
|
||||||
AppWidget(appWidgetId).delete(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For a better preview, [ManageWidgetsActivity] should behave exactly like [HomeActivity]
|
|
||||||
*/
|
|
||||||
override fun isHomeScreen(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Intent
|
|
||||||
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.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.databinding.ActivitySelectWidgetBinding
|
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
|
||||||
import de.jrpie.android.launcher.widgets.ClockWidget
|
|
||||||
import de.jrpie.android.launcher.widgets.LauncherAppWidgetProvider
|
|
||||||
import de.jrpie.android.launcher.widgets.LauncherClockWidgetProvider
|
|
||||||
import de.jrpie.android.launcher.widgets.LauncherWidgetProvider
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import de.jrpie.android.launcher.widgets.bindAppWidgetOrRequestPermission
|
|
||||||
import de.jrpie.android.launcher.widgets.getAppWidgetHost
|
|
||||||
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidget
|
|
||||||
|
|
||||||
|
|
||||||
private const val REQUEST_WIDGET_PERMISSION = 29
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This activity lets the user pick an app widget to add.
|
|
||||||
* It provides an interface similar to [android.appwidget.AppWidgetManager.ACTION_APPWIDGET_PICK],
|
|
||||||
* but shows more information and also shows widgets from other user profiles.
|
|
||||||
*/
|
|
||||||
class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
|
||||||
lateinit var binding: ActivitySelectWidgetBinding
|
|
||||||
var widgetId: Int = -1
|
|
||||||
var widgetPanelId: Int = WidgetPanel.HOME.id
|
|
||||||
|
|
||||||
private fun tryBindWidget(info: LauncherWidgetProvider) {
|
|
||||||
when (info) {
|
|
||||||
is LauncherAppWidgetProvider -> {
|
|
||||||
if (bindAppWidgetOrRequestPermission(
|
|
||||||
this,
|
|
||||||
info.info,
|
|
||||||
widgetId,
|
|
||||||
REQUEST_WIDGET_PERMISSION
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setResult(
|
|
||||||
RESULT_OK,
|
|
||||||
Intent().also {
|
|
||||||
it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
|
||||||
it.putExtra(EXTRA_PANEL_ID, widgetPanelId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is LauncherClockWidgetProvider -> {
|
|
||||||
updateWidget(ClockWidget(widgetId, WidgetPosition(0, 4, 12, 3), widgetPanelId))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super<AppCompatActivity>.onStart()
|
|
||||||
super<UIObject>.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
|
||||||
super<UIObject>.onCreate()
|
|
||||||
|
|
||||||
binding = ActivitySelectWidgetBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
|
|
||||||
|
|
||||||
widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
|
||||||
widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id)
|
|
||||||
if (widgetId == -1) {
|
|
||||||
widgetId = getAppWidgetHost().allocateAppWidgetId()
|
|
||||||
}
|
|
||||||
|
|
||||||
val viewManager = LinearLayoutManager(this)
|
|
||||||
val viewAdapter = SelectWidgetRecyclerAdapter()
|
|
||||||
|
|
||||||
binding.selectWidgetRecycler.apply {
|
|
||||||
setHasFixedSize(false)
|
|
||||||
layoutManager = viewManager
|
|
||||||
adapter = viewAdapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
|
||||||
return modifyTheme(super.getTheme())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
|
|
||||||
if (requestCode == REQUEST_WIDGET_PERMISSION && resultCode == RESULT_OK) {
|
|
||||||
data ?: return
|
|
||||||
val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
|
|
||||||
tryBindWidget(LauncherAppWidgetProvider(provider))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class SelectWidgetRecyclerAdapter() :
|
|
||||||
RecyclerView.Adapter<SelectWidgetRecyclerAdapter.ViewHolder>() {
|
|
||||||
|
|
||||||
private val widgets = getAppWidgetProviders(this@SelectWidgetActivity).toTypedArray()
|
|
||||||
|
|
||||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
|
||||||
View.OnClickListener {
|
|
||||||
var textView: TextView = itemView.findViewById(R.id.list_widgets_row_name)
|
|
||||||
var descriptionView: TextView = itemView.findViewById(R.id.list_widgets_row_description)
|
|
||||||
var iconView: ImageView = itemView.findViewById(R.id.list_widgets_row_icon)
|
|
||||||
var previewView: ImageView = itemView.findViewById(R.id.list_widgets_row_preview)
|
|
||||||
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
tryBindWidget(widgets[bindingAdapterPosition])
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
itemView.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
|
||||||
val label = widgets[i].loadLabel(this@SelectWidgetActivity)
|
|
||||||
val description = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
widgets[i].loadDescription(this@SelectWidgetActivity)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
val preview =
|
|
||||||
widgets[i].loadPreviewImage(this@SelectWidgetActivity)
|
|
||||||
val icon =
|
|
||||||
widgets[i].loadIcon(this@SelectWidgetActivity)
|
|
||||||
|
|
||||||
viewHolder.textView.text = label
|
|
||||||
viewHolder.descriptionView.text = description
|
|
||||||
viewHolder.descriptionView.visibility =
|
|
||||||
if (description?.isEmpty() == false) { View.VISIBLE } else { View.GONE }
|
|
||||||
viewHolder.iconView.setImageDrawable(icon)
|
|
||||||
|
|
||||||
viewHolder.previewView.setImageDrawable(preview)
|
|
||||||
viewHolder.previewView.visibility =
|
|
||||||
if (preview != null) { View.VISIBLE } else { View.GONE }
|
|
||||||
|
|
||||||
viewHolder.previewView.requestLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return widgets.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
|
||||||
val view: View = inflater.inflate(R.layout.list_widgets_row, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewConfiguration
|
|
||||||
import androidx.core.graphics.contains
|
|
||||||
import androidx.core.graphics.minus
|
|
||||||
import androidx.core.graphics.toRect
|
|
||||||
import androidx.core.view.children
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
|
||||||
import de.jrpie.android.launcher.widgets.Widget
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidget
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of the [WidgetContainerView] which allows to manage widgets.
|
|
||||||
*/
|
|
||||||
class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSet? = null) :
|
|
||||||
WidgetContainerView(widgetPanelId, context, attrs) {
|
|
||||||
constructor(context: Context, attrs: AttributeSet?) : this(WidgetPanel.HOME.id, context, attrs)
|
|
||||||
|
|
||||||
val TOUCH_SLOP: Int
|
|
||||||
val TOUCH_SLOP_SQUARE: Int
|
|
||||||
val LONG_PRESS_TIMEOUT: Long
|
|
||||||
|
|
||||||
init {
|
|
||||||
val configuration = ViewConfiguration.get(context)
|
|
||||||
TOUCH_SLOP = configuration.scaledTouchSlop
|
|
||||||
TOUCH_SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP
|
|
||||||
|
|
||||||
LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum class EditMode(val resize: (dx: Int, dy: Int, rect: Rect) -> Rect) {
|
|
||||||
MOVE({ dx, dy, rect ->
|
|
||||||
Rect(rect.left + dx, rect.top + dy, rect.right + dx, rect.bottom + dy)
|
|
||||||
}),
|
|
||||||
TOP({ dx, dy, rect ->
|
|
||||||
Rect(rect.left, min(rect.top + dy, rect.bottom - 200), rect.right, rect.bottom)
|
|
||||||
}),
|
|
||||||
BOTTOM({ dx, dy, rect ->
|
|
||||||
Rect(rect.left, rect.top, rect.right, max(rect.top + 200, rect.bottom + dy))
|
|
||||||
}),
|
|
||||||
LEFT({ dx, dy, rect ->
|
|
||||||
Rect(min(rect.left + dx, rect.right - 200), rect.top, rect.right, rect.bottom)
|
|
||||||
}),
|
|
||||||
RIGHT({ dx, dy, rect ->
|
|
||||||
Rect(rect.left, rect.top, max(rect.left + 200, rect.right + dx), rect.bottom)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedWidgetOverlayView: WidgetOverlayView? = null
|
|
||||||
var selectedWidgetView: View? = null
|
|
||||||
var currentGestureStart: Point? = null
|
|
||||||
var startWidgetPosition: Rect? = null
|
|
||||||
var lastPosition = Rect()
|
|
||||||
|
|
||||||
private val longPressHandler = Handler(Looper.getMainLooper())
|
|
||||||
|
|
||||||
|
|
||||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
||||||
if (event == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
synchronized(this) {
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
||||||
val start = Point(event.x.toInt(), event.y.toInt())
|
|
||||||
currentGestureStart = start
|
|
||||||
val view = children.mapNotNull { it as? WidgetOverlayView }.firstOrNull {
|
|
||||||
RectF(it.x, it.y, it.x + it.width, it.y + it.height).toRect().contains(start) == true
|
|
||||||
} ?: return false
|
|
||||||
|
|
||||||
val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
|
|
||||||
selectedWidgetOverlayView = view
|
|
||||||
selectedWidgetView = widgetViewById.get(view.widgetId) ?: return true
|
|
||||||
startWidgetPosition = position
|
|
||||||
|
|
||||||
val positionInView = start.minus(Point(position.left, position.top))
|
|
||||||
view.mode = view.getHandles().firstOrNull { it.position.contains(positionInView) }?.mode ?: EditMode.MOVE
|
|
||||||
|
|
||||||
longPressHandler.postDelayed({
|
|
||||||
synchronized(this@WidgetManagerView) {
|
|
||||||
view.showPopupMenu()
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
|
||||||
endInteraction()
|
|
||||||
}
|
|
||||||
}, LONG_PRESS_TIMEOUT)
|
|
||||||
}
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_MOVE ||
|
|
||||||
event.actionMasked == MotionEvent.ACTION_UP
|
|
||||||
) {
|
|
||||||
val distanceX = event.x - (currentGestureStart?.x ?: return true)
|
|
||||||
val distanceY = event.y - (currentGestureStart?.y ?: return true)
|
|
||||||
if (distanceX * distanceX + distanceY * distanceY > TOUCH_SLOP_SQUARE) {
|
|
||||||
longPressHandler.removeCallbacksAndMessages(null)
|
|
||||||
}
|
|
||||||
val view = selectedWidgetOverlayView ?: return true
|
|
||||||
val start = startWidgetPosition ?: return true
|
|
||||||
val absoluteNewPosition = view.mode?.resize(
|
|
||||||
distanceX.toInt(),
|
|
||||||
distanceY.toInt(),
|
|
||||||
start
|
|
||||||
) ?: return true
|
|
||||||
val newPosition = WidgetPosition.fromAbsoluteRect(
|
|
||||||
absoluteNewPosition, width, height
|
|
||||||
)
|
|
||||||
if (newPosition != lastPosition) {
|
|
||||||
lastPosition = absoluteNewPosition
|
|
||||||
(view.layoutParams as Companion.LayoutParams).position = newPosition
|
|
||||||
(selectedWidgetView?.layoutParams as? Companion.LayoutParams)?.position = newPosition
|
|
||||||
requestLayout()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
|
||||||
longPressHandler.removeCallbacksAndMessages(null)
|
|
||||||
val id = selectedWidgetOverlayView?.widgetId ?: return true
|
|
||||||
val widget = Widget.byId(context, id) ?: return true
|
|
||||||
widget.position = newPosition
|
|
||||||
endInteraction()
|
|
||||||
updateWidget(widget)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
private fun endInteraction() {
|
|
||||||
startWidgetPosition = null
|
|
||||||
selectedWidgetOverlayView?.mode = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateWidgets(activity: Activity, widgets: Collection<Widget>?) {
|
|
||||||
super.updateWidgets(activity, widgets)
|
|
||||||
if (widgets == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
children.mapNotNull { it as? WidgetOverlayView }.forEach { removeView(it) }
|
|
||||||
|
|
||||||
widgets.filter { it.panelId == widgetPanelId }.forEach { widget ->
|
|
||||||
WidgetOverlayView(activity).let {
|
|
||||||
addView(it)
|
|
||||||
it.widgetId = widget.id
|
|
||||||
(it.layoutParams as Companion.LayoutParams).position = widget.position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.PopupMenu
|
|
||||||
import androidx.core.graphics.toRectF
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.widgets.Widget
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidget
|
|
||||||
|
|
||||||
|
|
||||||
private const val HANDLE_SIZE = 100
|
|
||||||
private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An overlay to show configuration options for a widget in [WidgetManagerView]
|
|
||||||
*/
|
|
||||||
class WidgetOverlayView : View {
|
|
||||||
|
|
||||||
|
|
||||||
val paint = Paint()
|
|
||||||
val handlePaint = Paint()
|
|
||||||
val selectedHandlePaint = Paint()
|
|
||||||
var mode: WidgetManagerView.EditMode? = null
|
|
||||||
class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
|
|
||||||
init {
|
|
||||||
handlePaint.style = Paint.Style.STROKE
|
|
||||||
handlePaint.setARGB(255, 255, 255, 255)
|
|
||||||
|
|
||||||
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
|
|
||||||
selectedHandlePaint.setARGB(100, 255, 255, 255)
|
|
||||||
|
|
||||||
paint.style = Paint.Style.STROKE
|
|
||||||
paint.setARGB(255, 255, 255, 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var preview: Drawable? = null
|
|
||||||
var widgetId: Int = -1
|
|
||||||
set(newId) {
|
|
||||||
field = newId
|
|
||||||
preview = Widget.byId(context, widgetId)?.getPreview(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
|
||||||
init(null, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
|
||||||
init(attrs, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
|
|
||||||
context,
|
|
||||||
attrs,
|
|
||||||
defStyle
|
|
||||||
) {
|
|
||||||
init(attrs, defStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun init(attrs: AttributeSet?, defStyle: Int) { }
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
|
|
||||||
getHandles().forEach {
|
|
||||||
if (it.mode == mode) {
|
|
||||||
canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, selectedHandlePaint)
|
|
||||||
} else {
|
|
||||||
canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, handlePaint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val bounds = getBounds()
|
|
||||||
canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint)
|
|
||||||
|
|
||||||
if (mode == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//preview?.bounds = bounds
|
|
||||||
//preview?.draw(canvas)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showPopupMenu() {
|
|
||||||
val widget = Widget.byId(context, widgetId)?: return
|
|
||||||
val menu = PopupMenu(context, this)
|
|
||||||
menu.menu.let {
|
|
||||||
it.add(
|
|
||||||
context.getString(R.string.widget_menu_remove)
|
|
||||||
).setOnMenuItemClickListener { _ ->
|
|
||||||
Widget.byId(context, widgetId)?.delete(context)
|
|
||||||
return@setOnMenuItemClickListener true
|
|
||||||
}
|
|
||||||
it.add(
|
|
||||||
if (widget.allowInteraction) {
|
|
||||||
context.getString(R.string.widget_menu_disable_interaction)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.widget_menu_enable_interaction)
|
|
||||||
}
|
|
||||||
).setOnMenuItemClickListener { _ ->
|
|
||||||
widget.allowInteraction = !widget.allowInteraction
|
|
||||||
updateWidget(widget)
|
|
||||||
return@setOnMenuItemClickListener true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getHandles(): List<Handle> {
|
|
||||||
return listOf<Handle>(
|
|
||||||
Handle(WidgetManagerView.EditMode.TOP,
|
|
||||||
Rect(HANDLE_EDGE_SIZE, 0, width - HANDLE_EDGE_SIZE, HANDLE_SIZE)),
|
|
||||||
Handle(WidgetManagerView.EditMode.BOTTOM,
|
|
||||||
Rect(HANDLE_EDGE_SIZE, height - HANDLE_SIZE, width - HANDLE_EDGE_SIZE, height)),
|
|
||||||
Handle(WidgetManagerView.EditMode.LEFT,
|
|
||||||
Rect(0, HANDLE_EDGE_SIZE, HANDLE_SIZE, height - HANDLE_EDGE_SIZE)),
|
|
||||||
Handle(WidgetManagerView.EditMode.RIGHT,
|
|
||||||
Rect(width - HANDLE_SIZE, HANDLE_EDGE_SIZE, width, height - HANDLE_EDGE_SIZE))
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBounds(): Rect {
|
|
||||||
return Rect(0,0, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets.manage
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.PopupMenu
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPanel
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidgetPanel
|
|
||||||
|
|
||||||
|
|
||||||
class WidgetPanelsRecyclerAdapter(
|
|
||||||
val context: Context,
|
|
||||||
val showMenu: Boolean = false,
|
|
||||||
val onSelectWidgetPanel: (WidgetPanel) -> Unit
|
|
||||||
) :
|
|
||||||
RecyclerView.Adapter<WidgetPanelsRecyclerAdapter.ViewHolder>() {
|
|
||||||
|
|
||||||
var widgetPanels = (LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray()
|
|
||||||
|
|
||||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
||||||
var labelView: TextView = itemView.findViewById(R.id.list_widget_panels_label)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
|
||||||
viewHolder.labelView.text = widgetPanels[i].label
|
|
||||||
|
|
||||||
viewHolder.itemView.setOnClickListener {
|
|
||||||
onSelectWidgetPanel(widgetPanels[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showMenu) {
|
|
||||||
viewHolder.itemView.setOnLongClickListener {
|
|
||||||
showOptionsPopup(
|
|
||||||
viewHolder,
|
|
||||||
widgetPanels[i]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("SameReturnValue")
|
|
||||||
private fun showOptionsPopup(
|
|
||||||
viewHolder: ViewHolder,
|
|
||||||
widgetPanel: WidgetPanel
|
|
||||||
): Boolean {
|
|
||||||
//create the popup menu
|
|
||||||
|
|
||||||
val popup = PopupMenu(context, viewHolder.labelView)
|
|
||||||
popup.menu.add(R.string.manage_widget_panels_delete).setOnMenuItemClickListener { _ ->
|
|
||||||
widgetPanel.delete(context)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
popup.menu.add(R.string.manage_widget_panels_rename).setOnMenuItemClickListener { _ ->
|
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
|
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
|
|
||||||
setPositiveButton(R.string.dialog_ok) { dialogInterface, _ ->
|
|
||||||
var newLabel = (dialogInterface as? AlertDialog)
|
|
||||||
?.findViewById<EditText>(R.id.dialog_rename_widget_panel_edit_text)
|
|
||||||
?.text?.toString()
|
|
||||||
if (newLabel == null || newLabel.isEmpty()) {
|
|
||||||
newLabel =
|
|
||||||
(context.getString(R.string.widget_panel_default_name, widgetPanel.id))
|
|
||||||
}
|
|
||||||
widgetPanel.label = newLabel
|
|
||||||
updateWidgetPanel(widgetPanel)
|
|
||||||
}
|
|
||||||
setView(R.layout.dialog_rename_widget_panel)
|
|
||||||
}.create().also { it.show() }.apply {
|
|
||||||
findViewById<EditText>(R.id.dialog_rename_widget_panel_edit_text)?.let {
|
|
||||||
it.setText(widgetPanel.label)
|
|
||||||
it.hint = context.getString(R.string.widget_panel_default_name, widgetPanel.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.show()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return widgetPanels.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val view: View =
|
|
||||||
LayoutInflater.from(context).inflate(R.layout.list_widget_panels_row, parent, false)
|
|
||||||
val viewHolder = ViewHolder(view)
|
|
||||||
return viewHolder
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.appwidget.AppWidgetHostView
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.SizeF
|
|
||||||
import android.view.View
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("widget:app")
|
|
||||||
class AppWidget(
|
|
||||||
override val id: Int,
|
|
||||||
override var position: WidgetPosition = WidgetPosition(0,0,1,1),
|
|
||||||
override var panelId: Int = WidgetPanel.HOME.id,
|
|
||||||
override var allowInteraction: Boolean = false,
|
|
||||||
|
|
||||||
// We keep track of packageName, className and user to make it possible to restore the widget
|
|
||||||
// on a new device when restoring settings (currently not implemented)
|
|
||||||
// In normal operation only id and position are used.
|
|
||||||
val packageName: String? = null,
|
|
||||||
val className: String? = null,
|
|
||||||
val user: Int? = null
|
|
||||||
): Widget() {
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
id: Int,
|
|
||||||
position: WidgetPosition,
|
|
||||||
panelId: Int,
|
|
||||||
widgetProviderInfo: AppWidgetProviderInfo
|
|
||||||
) :
|
|
||||||
this(
|
|
||||||
id,
|
|
||||||
position,
|
|
||||||
panelId,
|
|
||||||
false,
|
|
||||||
widgetProviderInfo.provider.packageName,
|
|
||||||
widgetProviderInfo.provider.className,
|
|
||||||
widgetProviderInfo.profile.hashCode()
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the [AppWidgetProviderInfo] by [id].
|
|
||||||
* If the widget is not installed, use [restoreAppWidgetProviderInfo] instead.
|
|
||||||
*/
|
|
||||||
fun getAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
|
||||||
if (id < 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (context.applicationContext as Application).appWidgetManager
|
|
||||||
.getAppWidgetInfo(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the AppWidgetProviderInfo from [user], [packageName] and [className].
|
|
||||||
* Only use this when the widget is not installed,
|
|
||||||
* in normal operation use [getAppWidgetProviderInfo] instead.
|
|
||||||
*/
|
|
||||||
/*fun restoreAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
|
||||||
return getAppWidgetProviders(context).firstOrNull {
|
|
||||||
it.profile.hashCode() == user
|
|
||||||
&& it.provider.packageName == packageName
|
|
||||||
&& it.provider.className == className
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "WidgetInfo(id=$id, position=$position, packageName=$packageName, className=$className, user=$user)"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createView(activity: Activity): AppWidgetHostView? {
|
|
||||||
val providerInfo = activity.getAppWidgetManager().getAppWidgetInfo(id) ?: return null
|
|
||||||
val view = activity.getAppWidgetHost()
|
|
||||||
.createView(activity, this.id, providerInfo)
|
|
||||||
|
|
||||||
val dp = activity.resources.displayMetrics.density
|
|
||||||
val screenWidth = activity.resources.displayMetrics.widthPixels
|
|
||||||
val screenHeight = activity.resources.displayMetrics.heightPixels
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val absolutePosition = position.getAbsoluteRect(screenWidth, screenHeight)
|
|
||||||
view.updateAppWidgetSize(Bundle.EMPTY,
|
|
||||||
listOf(SizeF(
|
|
||||||
absolutePosition.width() / dp,
|
|
||||||
absolutePosition.height() / dp
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
view.setPadding(0,0,0,0)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findView(views: Sequence<View>): AppWidgetHostView? {
|
|
||||||
return views.mapNotNull { it as? AppWidgetHostView }.firstOrNull { it.appWidgetId == id }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIcon(context: Context): Drawable? {
|
|
||||||
return context.getAppWidgetManager().getAppWidgetInfo(id)?.loadIcon(context, DisplayMetrics.DENSITY_HIGH)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPreview(context: Context): Drawable? {
|
|
||||||
return context.getAppWidgetManager().getAppWidgetInfo(id)?.loadPreviewImage(context, DisplayMetrics.DENSITY_HIGH)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isConfigurable(context: Context): Boolean {
|
|
||||||
return context.getAppWidgetManager().getAppWidgetInfo(id)?.configure != null
|
|
||||||
}
|
|
||||||
override fun configure(activity: Activity, requestCode: Int) {
|
|
||||||
if (!isConfigurable(activity)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
activity.getAppWidgetHost().startAppWidgetConfigureActivityForResult(
|
|
||||||
activity,
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
requestCode,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
import de.jrpie.android.launcher.ui.widgets.ClockView
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("widget:clock")
|
|
||||||
class ClockWidget(
|
|
||||||
override val id: Int,
|
|
||||||
override var position: WidgetPosition,
|
|
||||||
override val panelId: Int,
|
|
||||||
override var allowInteraction: Boolean = true
|
|
||||||
) : Widget() {
|
|
||||||
|
|
||||||
override fun createView(activity: Activity): View? {
|
|
||||||
return ClockView(activity, null, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findView(views: Sequence<View>): ClockView? {
|
|
||||||
return views.mapNotNull { it as? ClockView }.firstOrNull { it.appWidgetId == id }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPreview(context: Context): Drawable? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIcon(context: Context): Drawable? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isConfigurable(context: Context): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configure(activity: Activity, requestCode: Int) { }
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import de.jrpie.android.launcher.R
|
|
||||||
|
|
||||||
sealed class LauncherWidgetProvider {
|
|
||||||
abstract fun loadLabel(context: Context): CharSequence?
|
|
||||||
abstract fun loadPreviewImage(context: Context): Drawable?
|
|
||||||
abstract fun loadIcon(context: Context): Drawable?
|
|
||||||
abstract fun loadDescription(context: Context): CharSequence?
|
|
||||||
}
|
|
||||||
|
|
||||||
class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidgetProvider() {
|
|
||||||
|
|
||||||
override fun loadLabel(context: Context): CharSequence? {
|
|
||||||
return info.loadLabel(context.packageManager)
|
|
||||||
}
|
|
||||||
override fun loadPreviewImage(context: Context): Drawable? {
|
|
||||||
return info.loadPreviewImage(context, DisplayMetrics.DENSITY_DEFAULT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadIcon(context: Context): Drawable? {
|
|
||||||
return info.loadIcon(context, DisplayMetrics.DENSITY_DEFAULT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadDescription(context: Context): CharSequence? {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
info.loadDescription(context)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
class LauncherClockWidgetProvider : LauncherWidgetProvider() {
|
|
||||||
|
|
||||||
override fun loadLabel(context: Context): CharSequence? {
|
|
||||||
return context.getString(R.string.widget_clock_label)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadDescription(context: Context): CharSequence? {
|
|
||||||
return context.getString(R.string.widget_clock_description)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadPreviewImage(context: Context): Drawable? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadIcon(context: Context): Drawable? {
|
|
||||||
return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
sealed class Widget {
|
|
||||||
abstract val id: Int
|
|
||||||
abstract var position: WidgetPosition
|
|
||||||
abstract val panelId: Int
|
|
||||||
abstract var allowInteraction: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param activity The activity where the view will be used. Must not be an AppCompatActivity.
|
|
||||||
*/
|
|
||||||
abstract fun createView(activity: Activity): View?
|
|
||||||
abstract fun findView(views: Sequence<View>): View?
|
|
||||||
abstract fun getPreview(context: Context): Drawable?
|
|
||||||
abstract fun getIcon(context: Context): Drawable?
|
|
||||||
abstract fun isConfigurable(context: Context): Boolean
|
|
||||||
abstract fun configure(activity: Activity, requestCode: Int)
|
|
||||||
|
|
||||||
fun delete(context: Context) {
|
|
||||||
context.getAppWidgetHost().deleteAppWidgetId(id)
|
|
||||||
|
|
||||||
LauncherPreferences.widgets().widgets(
|
|
||||||
LauncherPreferences.widgets().widgets()?.also {
|
|
||||||
it.remove(this)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPanel(): WidgetPanel? {
|
|
||||||
return WidgetPanel.byId(panelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return (other as? Widget)?.id == id
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(): String {
|
|
||||||
return Json.encodeToString(serializer(), this)
|
|
||||||
}
|
|
||||||
companion object {
|
|
||||||
fun deserialize(serialized: String): Widget {
|
|
||||||
return Json.decodeFromString(serialized)
|
|
||||||
}
|
|
||||||
fun byId(context: Context, id: Int): Widget? {
|
|
||||||
return (context.applicationContext as Application).widgets.value?.firstOrNull {
|
|
||||||
it.id == id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("panel")
|
|
||||||
class WidgetPanel(val id: Int, var label: String) {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return (other as? WidgetPanel)?.id == id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(): String {
|
|
||||||
return Json.encodeToString(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun delete(context: Context) {
|
|
||||||
LauncherPreferences.widgets().customPanels(
|
|
||||||
(LauncherPreferences.widgets().customPanels() ?: setOf()).minus(this)
|
|
||||||
)
|
|
||||||
(LauncherPreferences.widgets().widgets() ?: return)
|
|
||||||
.filter { it.panelId == this.id }.forEach { it.delete(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val HOME = WidgetPanel(0, "home")
|
|
||||||
fun byId(id: Int): WidgetPanel? {
|
|
||||||
if (id == 0) {
|
|
||||||
return HOME
|
|
||||||
}
|
|
||||||
return LauncherPreferences.widgets().customPanels()?.firstOrNull { it.id == id }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allocateId(): Int {
|
|
||||||
return (
|
|
||||||
(LauncherPreferences.widgets().customPanels() ?: setOf())
|
|
||||||
.plus(HOME)
|
|
||||||
.maxOfOrNull { it.id } ?: 0
|
|
||||||
) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deserialize(serialized: String): WidgetPanel {
|
|
||||||
return Json.decodeFromString(serialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.graphics.Rect
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
const val GRID_SIZE: Short = 12
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) {
|
|
||||||
|
|
||||||
fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect {
|
|
||||||
val gridWidth = screenWidth / GRID_SIZE.toFloat()
|
|
||||||
val gridHeight= screenHeight / GRID_SIZE.toFloat()
|
|
||||||
|
|
||||||
return Rect(
|
|
||||||
(x * gridWidth).toInt(),
|
|
||||||
(y * gridHeight).toInt(),
|
|
||||||
((x + width) * gridWidth).toInt(),
|
|
||||||
((y + height) * gridHeight).toInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition {
|
|
||||||
val gridWidth = screenWidth / GRID_SIZE.toFloat()
|
|
||||||
val gridHeight= screenHeight / GRID_SIZE.toFloat()
|
|
||||||
|
|
||||||
val x = (absolute.left / gridWidth).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort())
|
|
||||||
val y = (absolute.top / gridHeight).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort())
|
|
||||||
|
|
||||||
|
|
||||||
val w = max(2, ((absolute.right - absolute.left) / gridWidth).roundToInt()).toShort()
|
|
||||||
val h = max(2, ((absolute.bottom - absolute.top) / gridHeight).roundToInt()).toShort()
|
|
||||||
|
|
||||||
return WidgetPosition(x,y,w,h)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition {
|
|
||||||
val gridWidth = screenWidth / GRID_SIZE.toFloat()
|
|
||||||
val gridHeight= screenHeight / GRID_SIZE.toFloat()
|
|
||||||
|
|
||||||
val cellsWidth = ceil(minWidth / gridWidth).toInt().toShort()
|
|
||||||
val cellsHeight = ceil(minHeight / gridHeight).toInt().toShort()
|
|
||||||
|
|
||||||
return WidgetPosition(
|
|
||||||
((GRID_SIZE - cellsWidth) / 2).toShort(),
|
|
||||||
((GRID_SIZE - cellsHeight) / 2).toShort(),
|
|
||||||
cellsWidth,
|
|
||||||
cellsHeight
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Service
|
|
||||||
import android.appwidget.AppWidgetHost
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.LauncherApps
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.UserManager
|
|
||||||
import android.util.Log
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
|
||||||
|
|
||||||
fun deleteAllWidgets(context: Context) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
context.getAppWidgetHost().appWidgetIds.forEach { AppWidget(it).delete(context) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to bind [providerInfo] to the id [id].
|
|
||||||
* @param providerInfo The widget to be bound.
|
|
||||||
* @param id The id to bind the widget to. If -1 is provided, a new id is allocated.
|
|
||||||
* @param
|
|
||||||
* @param requestCode Used to start an activity to request permission to bind the widget.
|
|
||||||
*
|
|
||||||
* @return true iff the app widget was bound successfully.
|
|
||||||
*/
|
|
||||||
fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, id: Int, requestCode: Int? = null): Boolean {
|
|
||||||
val appWidgetId = if(id == -1) {
|
|
||||||
activity.getAppWidgetHost().allocateAppWidgetId()
|
|
||||||
} else { id }
|
|
||||||
|
|
||||||
Log.i("Launcher", "Binding new widget ${appWidgetId}")
|
|
||||||
if (!activity.getAppWidgetManager().bindAppWidgetIdIfAllowed(
|
|
||||||
appWidgetId,
|
|
||||||
providerInfo.provider
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Log.i("Widgets", "requesting permission for widget")
|
|
||||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId)
|
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, providerInfo.provider)
|
|
||||||
}
|
|
||||||
activity.startActivityForResult(intent, requestCode ?: 0)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> {
|
|
||||||
val list = mutableListOf<LauncherWidgetProvider>(LauncherClockWidgetProvider())
|
|
||||||
val appWidgetManager = context.getAppWidgetManager()
|
|
||||||
val profiles =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
(context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps).profiles
|
|
||||||
} else {
|
|
||||||
(context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles
|
|
||||||
}
|
|
||||||
list.addAll(
|
|
||||||
profiles.map {
|
|
||||||
appWidgetManager.getInstalledProvidersForProfile(it)
|
|
||||||
.map { LauncherAppWidgetProvider(it) }
|
|
||||||
}.flatten()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun updateWidget(widget: Widget) {
|
|
||||||
LauncherPreferences.widgets().widgets(
|
|
||||||
(LauncherPreferences.widgets().widgets() ?: setOf())
|
|
||||||
.minus(widget)
|
|
||||||
.plus(widget)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateWidgetPanel(widgetPanel: WidgetPanel) {
|
|
||||||
LauncherPreferences.widgets().customPanels(
|
|
||||||
(LauncherPreferences.widgets().customPanels() ?: setOf())
|
|
||||||
.minus(widgetPanel)
|
|
||||||
.plus(widgetPanel)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.getAppWidgetHost(): AppWidgetHost {
|
|
||||||
return (this.applicationContext as Application).appWidgetHost
|
|
||||||
}
|
|
||||||
fun Context.getAppWidgetManager(): AppWidgetManager {
|
|
||||||
return (this.applicationContext as Application).appWidgetManager
|
|
||||||
}
|
|
|
@ -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="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
||||||
|
|
||||||
</vector>
|
|
|
@ -1,15 +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="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="?android:textColor"
|
|
||||||
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
|
|
||||||
|
|
||||||
</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="M13,13v8h8v-8h-8zM3,21h8v-8L3,13v8zM3,3v8h8L11,3L3,3zM16.66,1.69L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z" />
|
|
||||||
|
|
||||||
</vector>
|
|
|
@ -1,73 +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/main"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".ui.widgets.manage.ManageWidgetPanelsActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/manage_widget_panels_appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@null"
|
|
||||||
app:elevation="0dp"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/manage_widget_panels_heading"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minHeight="?actionBarSize"
|
|
||||||
android:padding="@dimen/appbar_padding"
|
|
||||||
android:text="@string/widget_panels_title"
|
|
||||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
|
|
||||||
android:textSize="30sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/manage_widget_panels_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>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/manage_widget_panels_recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/manage_widget_panels_appbar" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/manage_widget_panels_add_panel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:src="@drawable/baseline_add_24"
|
|
||||||
app:layout_anchor="@+id/manage_widget_panels_recycler"
|
|
||||||
app:layout_anchorGravity="end|bottom"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,25 +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/main"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".ui.widgets.manage.ManageWidgetsActivity">
|
|
||||||
<de.jrpie.android.launcher.ui.widgets.manage.WidgetManagerView
|
|
||||||
android:id="@+id/manage_widgets_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/manage_widgets_button_add"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:src="@drawable/baseline_add_24"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,72 +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/select_widget_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".ui.widgets.manage.SelectWidgetActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/select_widget_appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
|
|
||||||
android:background="@null"
|
|
||||||
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="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/select_widget_heading"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minHeight="?actionBarSize"
|
|
||||||
android:padding="@dimen/appbar_padding"
|
|
||||||
android:text="@string/select_widget_title"
|
|
||||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
|
|
||||||
android:textSize="30sp"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/select_widget_close"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/select_widget_close"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginStart="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>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/select_widget_recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/select_widget_appbar" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/home_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:longClickable="false"
|
|
||||||
tools:context=".ui.widgets.WidgetPanelActivity">
|
|
||||||
|
|
||||||
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
|
||||||
android:id="@+id/widget_panel_widget_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,35 +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:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:longClickable="false"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".ui.widgets.ClockView">
|
|
||||||
|
|
||||||
<TextClock
|
|
||||||
android:id="@+id/clock_upper_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:textSize="30sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="2024-12-24" />
|
|
||||||
|
|
||||||
<TextClock
|
|
||||||
android:id="@+id/clock_lower_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="18:00:00"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/clock_upper_view"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
style="@style/AlertDialogCustom"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_create_widget_panel_edit_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="text"
|
|
||||||
tools:ignore="LabelFor" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
style="@style/AlertDialogCustom"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_rename_widget_panel_edit_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="text"
|
|
||||||
tools:ignore="LabelFor" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="10dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/dialog_select_widget_panel_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/dialog_select_widget_panel_info_no_panels"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/dialog_select_widget_panel_recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -10,10 +10,29 @@
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".ui.HomeActivity">
|
tools:context=".ui.HomeActivity">
|
||||||
|
|
||||||
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
<TextClock
|
||||||
android:id="@+id/home_widget_container"
|
android:id="@+id/home_upper_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.45"
|
||||||
|
tools:text="2024-12-24" />
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/home_lower_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="18:00:00"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/home_upper_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<!-- only shown when µLauncher settings can't be reached by a gesture -->
|
<!-- only shown when µLauncher settings can't be reached by a gesture -->
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
@ -27,8 +27,6 @@
|
||||||
android:text=""
|
android:text=""
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
tools:text="@string/app_name"
|
tools:text="@string/app_name"
|
||||||
android:ellipsize="end"
|
|
||||||
android:lines="1"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon"
|
app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -1,23 +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"
|
|
||||||
android:id="@+id/list_widget_panels_row_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="15sp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/list_widget_panels_label"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="60sp"
|
|
||||||
android:layout_marginEnd="5dp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text=""
|
|
||||||
android:textSize="20sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,35 +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_apps_row_container"
|
|
||||||
android:background="@color/cardview_dark_background"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="15sp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/list_widgets_header_icon"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:src="@mipmap/ic_launcher_round"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/list_widgets_header_app_name"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20sp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text=""
|
|
||||||
android:textSize="20sp"
|
|
||||||
tools:text="some widget"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/list_widgets_header_icon"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,60 +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_apps_row_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="15sp">
|
|
||||||
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/list_widgets_row_icon"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:src="@mipmap/ic_launcher_round"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/list_widgets_row_name"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="10sp"
|
|
||||||
android:layout_marginEnd="10sp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text=""
|
|
||||||
android:textSize="20sp"
|
|
||||||
tools:text="some widget"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/list_widgets_row_icon"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/list_widgets_row_description"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="10sp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text=""
|
|
||||||
android:textSize="12sp"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/list_widgets_row_name"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/list_widgets_row_name"
|
|
||||||
tools:text="a longer description of the widget" />
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/list_widgets_row_preview"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:maxHeight="100dp"
|
|
||||||
android:layout_marginStart="0dp"
|
|
||||||
android:layout_marginEnd="0dp"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/list_widgets_row_description"
|
|
||||||
tools:src="@mipmap/ic_launcher_round"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -11,9 +11,5 @@
|
||||||
<color name="lightTheme_background_color">#fff</color>
|
<color name="lightTheme_background_color">#fff</color>
|
||||||
<color name="lightTheme_accent_color">#9999ff</color>
|
<color name="lightTheme_accent_color">#9999ff</color>
|
||||||
<color name="lightTheme_text_color">#000</color>
|
<color name="lightTheme_text_color">#000</color>
|
||||||
<color name="light_blue_400">#FF29B6F6</color>
|
|
||||||
<color name="light_blue_600">#FF039BE5</color>
|
|
||||||
<color name="gray_400">#FFBDBDBD</color>
|
|
||||||
<color name="gray_600">#FF757575</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
|
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
|
||||||
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
|
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
|
||||||
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
|
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
|
||||||
<string name="settings_widgets_widgets_key" translatable="false">widgets.widgets</string>
|
|
||||||
<string name="settings_widgets_custom_panels_key" translatable="false">widgets.custom_panels</string>
|
|
||||||
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
|
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
|
||||||
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
|
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
|
||||||
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>
|
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>
|
||||||
|
@ -149,7 +147,6 @@
|
||||||
<string name="settings_functionality_search_auto_launch_key" translatable="false">functionality.search_auto_launch</string>
|
<string name="settings_functionality_search_auto_launch_key" translatable="false">functionality.search_auto_launch</string>
|
||||||
<string name="settings_functionality_search_web_key" translatable="false">functionality.search_web</string>
|
<string name="settings_functionality_search_web_key" translatable="false">functionality.search_web</string>
|
||||||
<string name="settings_functionality_search_auto_open_keyboard_key" translatable="false">functionality.search_auto_keyboard</string>
|
<string name="settings_functionality_search_auto_open_keyboard_key" translatable="false">functionality.search_auto_keyboard</string>
|
||||||
<string name="settings_functionality_search_auto_close_keyboard_key" translatable="false">functionality.search_auto_close_keyboard</string>
|
|
||||||
|
|
||||||
|
|
||||||
<string name="settings_actions_lock_method_key" translatable="false">settings_action_lock_method</string>
|
<string name="settings_actions_lock_method_key" translatable="false">settings_action_lock_method</string>
|
||||||
|
|
|
@ -98,9 +98,6 @@
|
||||||
<string name="settings_gesture_time">Time</string>
|
<string name="settings_gesture_time">Time</string>
|
||||||
<string name="settings_gesture_description_time">Click on time</string>
|
<string name="settings_gesture_description_time">Click on time</string>
|
||||||
|
|
||||||
<string name="settings_widgets_widgets">Manage widgets</string>
|
|
||||||
<string name="settings_widgets_custom_panels">Manage widget panels</string>
|
|
||||||
|
|
||||||
|
|
||||||
<string name="settings_apps_choose">Choose App</string>
|
<string name="settings_apps_choose">Choose App</string>
|
||||||
|
|
||||||
|
@ -170,7 +167,6 @@
|
||||||
<string name="settings_functionality_search_web">Search the web</string>
|
<string name="settings_functionality_search_web">Search the web</string>
|
||||||
<string name="settings_functionality_search_web_summary">Press return while searching the app list to launch a web search.</string>
|
<string name="settings_functionality_search_web_summary">Press return while searching the app list to launch a web search.</string>
|
||||||
<string name="settings_functionality_auto_keyboard">Start keyboard for search</string>
|
<string name="settings_functionality_auto_keyboard">Start keyboard for search</string>
|
||||||
<string name="settings_functionality_auto_close_keyboard">Close keyboard when scrolling</string>
|
|
||||||
|
|
||||||
<string name="settings_launcher_sensitivity">Sensitivity</string>
|
<string name="settings_launcher_sensitivity">Sensitivity</string>
|
||||||
|
|
||||||
|
@ -255,12 +251,12 @@
|
||||||
<string name="list_other_track_next">Music: Next</string>
|
<string name="list_other_track_next">Music: Next</string>
|
||||||
<string name="list_other_track_previous">Music: Previous</string>
|
<string name="list_other_track_previous">Music: Previous</string>
|
||||||
<string name="list_other_track_play_pause">Music: Play / Pause</string>
|
<string name="list_other_track_play_pause">Music: Play / Pause</string>
|
||||||
<string name="list_other_expand_notifications_panel">Expand Notifications Panel</string>
|
<string name="list_other_expand_notifications_panel">Expand notifications panel</string>
|
||||||
<string name="list_other_recent_apps">Recent Apps</string>
|
<string name="list_other_recent_apps">Recent Apps</string>
|
||||||
<string name="list_other_nop">Do Nothing</string>
|
<string name="list_other_nop">Do nothing</string>
|
||||||
<string name="list_other_lock_screen">Lock Screen</string>
|
<string name="list_other_lock_screen">Lock Screen</string>
|
||||||
<string name="list_other_torch">Toggle Torch</string>
|
<string name="list_other_torch">Toggle Torch</string>
|
||||||
<string name="list_other_launch_other_launcher">Launch Other Home Screen</string>
|
<string name="list_other_launch_other_launcher">Launch other Home Screen</string>
|
||||||
|
|
||||||
<!-- Pin shortcuts -->
|
<!-- Pin shortcuts -->
|
||||||
<string name="pin_shortcut_title">Add Shortcut</string>
|
<string name="pin_shortcut_title">Add Shortcut</string>
|
||||||
|
@ -391,33 +387,5 @@
|
||||||
<string name="legal_info_title">Open Source Licenses</string>
|
<string name="legal_info_title">Open Source Licenses</string>
|
||||||
<string name="toast_activity_not_found_search_web">No app found to handle search.</string>
|
<string name="toast_activity_not_found_search_web">No app found to handle search.</string>
|
||||||
<string name="toast_activity_not_found_browser">Can\'t open URL: no browser found.</string>
|
<string name="toast_activity_not_found_browser">Can\'t open URL: no browser found.</string>
|
||||||
<string name="select_widget_title">Choose Widget</string>
|
|
||||||
|
|
||||||
<string name="widget_menu_remove">Remove</string>
|
|
||||||
<string name="widget_menu_configure">Configure</string>
|
|
||||||
<string name="widget_menu_enable_interaction">Enable Interaction</string>
|
|
||||||
<string name="widget_menu_disable_interaction">Disable Interaction</string>
|
|
||||||
|
|
||||||
|
|
||||||
<string name="widget_clock_label">Clock</string>
|
|
||||||
<string name="widget_clock_description">The default clock of μLauncher</string>
|
|
||||||
<string name="manage_widget_panels_delete">Delete</string>
|
|
||||||
<string name="manage_widget_panels_rename">Rename</string>
|
|
||||||
|
|
||||||
<string name="widget_panel_default_name">Widget Panel #%1$d</string>
|
|
||||||
<plurals name="widget_panel_number_of_widgets">
|
|
||||||
<item quantity="one">Contains %d widget.</item>
|
|
||||||
<item quantity="other">Contains %d widgets.</item>
|
|
||||||
</plurals>
|
|
||||||
|
|
||||||
|
|
||||||
<string name="dialog_ok">Ok</string>
|
|
||||||
<string name="widget_panels_title">Widget Panels</string>
|
|
||||||
<string name="dialog_select_widget_panel_title">Select a Widget Panel</string>
|
|
||||||
<string name="dialog_create_widget_panel_title">Create new widget panel</string>
|
|
||||||
<string name="dialog_select_widget_panel_info_no_panels"><![CDATA[No widget panels found. You can create widget panels in Settings > Launcher > Manage Widget Panels.]]></string>
|
|
||||||
<string name="list_other_open_widget_panel">Open Widget Panel</string>
|
|
||||||
<string name="alert_widget_panel_not_found">This widget panel no longer exists.</string>
|
|
||||||
<string name="settings_launcher_section_widgets">Widgets</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -66,12 +66,12 @@
|
||||||
<item name="android:shadowDy">0</item>
|
<item name="android:shadowDy">0</item>
|
||||||
<item name="android:shadowRadius">2</item>
|
<item name="android:shadowRadius">2</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="textShadowLight" parent="textShadow">
|
<style name="textShadowLight" parent="textShadow">
|
||||||
<item name="android:shadowColor">#aaa</item>
|
<item name="android:shadowColor">#aaa</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style name="backgroundWallpaper">
|
<style name="backgroundWallpaper">
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
@ -81,30 +81,26 @@
|
||||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="backgroundSolid"></style>
|
<style name="backgroundSolid">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="fontSystemDefault">
|
<style name="fontSystemDefault">
|
||||||
<!--<item name="android:textSize">18sp</item>-->
|
<!--<item name="android:textSize">18sp</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontHack">
|
<style name="fontHack">
|
||||||
<item name="android:fontFamily">@font/hack</item>
|
<item name="android:fontFamily">@font/hack</item>
|
||||||
<!--<item name="android:textSize">18sp</item>-->
|
<!--<item name="android:textSize">18sp</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontMonospace">
|
<style name="fontMonospace">
|
||||||
<item name="android:fontFamily">monospace</item>
|
<item name="android:fontFamily">monospace</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSerifMonospace">
|
<style name="fontSerifMonospace">
|
||||||
<item name="android:fontFamily">serif-monospace</item>
|
<item name="android:fontFamily">serif-monospace</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSansSerif">
|
<style name="fontSansSerif">
|
||||||
<item name="android:fontFamily">sans-serif</item>
|
<item name="android:fontFamily">sans-serif</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSerif" tools:keep="@style/fontSerif">
|
<style name="fontSerif" tools:keep="@style/fontSerif">
|
||||||
<item name="android:fontFamily">serif</item>
|
<item name="android:fontFamily">serif</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:allowDividerAbove="false" > <!-- general -->
|
app:allowDividerAbove="false" > <!-- general -->
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/settings_general_choose_home_screen_key"
|
android:key="@string/settings_general_choose_home_screen_key"
|
||||||
android:title="@string/settings_general_choose_home_screen"/>
|
android:title="@string/settings_general_choose_home_screen"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:allowDividerAbove="false"
|
app:allowDividerAbove="false"
|
||||||
|
@ -104,10 +104,6 @@
|
||||||
android:key="@string/settings_functionality_search_auto_open_keyboard_key"
|
android:key="@string/settings_functionality_search_auto_open_keyboard_key"
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:title="@string/settings_functionality_auto_keyboard" />
|
android:title="@string/settings_functionality_auto_keyboard" />
|
||||||
<SwitchPreference
|
|
||||||
android:key="@string/settings_functionality_search_auto_close_keyboard_key"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_functionality_auto_close_keyboard" />
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:key="@string/settings_enabled_gestures_double_swipe_key"
|
android:key="@string/settings_enabled_gestures_double_swipe_key"
|
||||||
android:summary="@string/settings_enabled_gestures_double_swipe_summary"
|
android:summary="@string/settings_enabled_gestures_double_swipe_summary"
|
||||||
|
@ -174,16 +170,6 @@
|
||||||
android:defaultValue="false"/>
|
android:defaultValue="false"/>
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
<PreferenceCategory
|
|
||||||
android:title="@string/settings_launcher_section_widgets"
|
|
||||||
app:allowDividerAbove="false">
|
|
||||||
<Preference
|
|
||||||
android:key="@string/settings_widgets_widgets_key"
|
|
||||||
android:title="@string/settings_widgets_widgets" />
|
|
||||||
<Preference
|
|
||||||
android:key="@string/settings_widgets_custom_panels_key"
|
|
||||||
android:title="@string/settings_widgets_custom_panels" />
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/settings_launcher_section_display"
|
android:title="@string/settings_launcher_section_display"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue