From a4fcdf60c7d3be3d37be15fb3fc8c9b286a7f953 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Sat, 26 Apr 2025 21:52:21 +0200 Subject: [PATCH] add widget panels (see #44) --- app/src/main/AndroidManifest.xml | 10 +- .../jrpie/android/launcher/actions/Action.kt | 10 +- .../launcher/actions/WidgetPanelAction.kt | 83 ++++++++++++++ .../LauncherPreferences$Config.java | 4 +- .../launcher/preferences/Preferences.kt | 24 +++- .../launcher/preferences/legacy/Version1.kt | 9 +- .../launcher/preferences/legacy/Version2.kt | 5 +- .../launcher/preferences/legacy/Version3.kt | 10 +- .../launcher/preferences/legacy/Version4.kt | 27 +++++ .../preferences/legacy/VersionUnknown.kt | 4 +- .../serialization/PreferenceSerializers.kt | 17 +++ .../jrpie/android/launcher/ui/HomeActivity.kt | 1 - .../ui/list/other/OtherRecyclerAdapter.kt | 19 ++-- .../launcher/SettingsFragmentLauncher.kt | 9 ++ .../ui/widgets/WidgetContainerView.kt | 31 ++++-- .../ui/widgets/WidgetPanelActivity.kt | 50 +++++++++ .../manage/ManageWidgetPanelsActivity.kt | 104 ++++++++++++++++++ .../widgets/manage/ManageWidgetsActivity.kt | 24 +++- .../ui/widgets/manage/SelectWidgetActivity.kt | 6 +- .../ui/widgets/manage/WidgetManagerView.kt | 11 +- .../ui/widgets/manage/WidgetOverlayView.kt | 7 +- .../manage/WidgetPanelsRecyclerAdapter.kt | 98 +++++++++++++++++ .../android/launcher/widgets/AppWidget.kt | 12 +- .../android/launcher/widgets/ClockWidget.kt | 1 + .../jrpie/android/launcher/widgets/Widget.kt | 5 + .../android/launcher/widgets/WidgetPanel.kt | 58 ++++++++++ .../jrpie/android/launcher/widgets/Widgets.kt | 16 ++- .../main/res/drawable/baseline_widgets_24.xml | 11 ++ .../layout/activity_manage_widget_panels.xml | 73 ++++++++++++ .../main/res/layout/activity_widget_panel.xml | 15 +++ .../res/layout/dialog_create_widget_panel.xml | 18 +++ .../res/layout/dialog_rename_widget_panel.xml | 18 +++ .../res/layout/dialog_select_widget_panel.xml | 26 +++++ .../res/layout/list_widget_panels_row.xml | 23 ++++ app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 19 ++++ app/src/main/res/xml/preferences.xml | 16 ++- 37 files changed, 807 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetPanelActivity.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt create mode 100644 app/src/main/res/drawable/baseline_widgets_24.xml create mode 100644 app/src/main/res/layout/activity_manage_widget_panels.xml create mode 100644 app/src/main/res/layout/activity_widget_panel.xml create mode 100644 app/src/main/res/layout/dialog_create_widget_panel.xml create mode 100644 app/src/main/res/layout/dialog_rename_widget_panel.xml create mode 100644 app/src/main/res/layout/dialog_select_widget_panel.xml create mode 100644 app/src/main/res/layout/list_widget_panels_row.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 841c9bd..5a1d5a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,9 +21,15 @@ android:theme="@style/launcherBaseTheme" tools:ignore="UnusedAttribute"> + + diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt index 9a2dc62..a883922 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt @@ -6,14 +6,18 @@ import android.content.SharedPreferences.Editor import android.graphics.Rect import android.graphics.drawable.Drawable import android.widget.Toast +import androidx.core.content.edit import de.jrpie.android.launcher.R import de.jrpie.android.launcher.preferences.LauncherPreferences import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import androidx.core.content.edit +/** + * Represents an action that can be bound to a [Gesture]. + * There are four types of actions: [AppAction], [ShortcutAction], [LauncherAction] and [WidgetPanelAction] + */ @Serializable sealed interface Action { fun invoke(context: Context, rect: Rect? = null): Boolean @@ -21,6 +25,10 @@ sealed interface Action { fun getIcon(context: Context): Drawable? fun isAvailable(context: Context): Boolean + fun showConfigurationDialog(context: Context, onSuccess: (Action) -> Unit) { + onSuccess(this) + } + // Can the action be used to reach µLauncher settings? fun canReachSettings(): Boolean diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt new file mode 100644 index 0000000..d7829a6 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt @@ -0,0 +1,83 @@ +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(R.id.dialog_select_widget_panel_info) + alertDialog.findViewById(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 + } +} diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java index 575346a..d509ef2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java @@ -8,6 +8,7 @@ import de.jrpie.android.launcher.actions.lock.LockMethod; import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetWidgetPanelSerializer; import de.jrpie.android.launcher.preferences.serialization.SetWidgetSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; @@ -84,7 +85,8 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @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 = "widgets", type = Set.class, serializer = SetWidgetSerializer.class), + @Preference(name = "custom_panels", type = Set.class, serializer = SetWidgetPanelSerializer.class) }), }) public final class LauncherPreferences$Config {} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt index 332f4df..e8e717e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt @@ -2,18 +2,21 @@ package de.jrpie.android.launcher.preferences import android.content.Context import android.util.Log +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AbstractAppInfo import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER +import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3 +import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion4 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown 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 @@ -21,7 +24,7 @@ import de.jrpie.android.launcher.widgets.deleteAllWidgets * Increase when breaking changes are introduced and write an appropriate case in * `migratePreferencesToNewVersion` */ -const val PREFERENCE_VERSION = 4 +const val PREFERENCE_VERSION = 5 const val UNKNOWN_PREFERENCE_VERSION = -1 private const val TAG = "Launcher - Preferences" @@ -43,18 +46,23 @@ fun migratePreferencesToNewVersion(context: Context) { } 1 -> { - migratePreferencesFromVersion1() + migratePreferencesFromVersion1(context) Log.i(TAG, "migration of preferences complete (1 -> ${PREFERENCE_VERSION}).") } 2 -> { - migratePreferencesFromVersion2() + migratePreferencesFromVersion2(context) Log.i(TAG, "migration of preferences complete (2 -> ${PREFERENCE_VERSION}).") } 3 -> { - migratePreferencesFromVersion3() + migratePreferencesFromVersion3(context) Log.i(TAG, "migration of preferences complete (3 -> ${PREFERENCE_VERSION}).") } + 4 -> { + migratePreferencesFromVersion4(context) + Log.i(TAG, "migration of preferences complete (4 -> ${PREFERENCE_VERSION}).") + } + else -> { Log.w( TAG, @@ -78,7 +86,11 @@ fun resetPreferences(context: Context) { LauncherPreferences.widgets().widgets( setOf( - ClockWidget(-500, WidgetPosition(1,4,10,3)) + ClockWidget( + (context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(), + WidgetPosition(1, 3, 10, 4), + WidgetPanel.HOME.id + ) ) ) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt index a1cb022..6cd9819 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version1.kt @@ -1,11 +1,13 @@ 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.AppAction import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction -import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER +import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION import kotlinx.serialization.Serializable @@ -13,7 +15,6 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.json.JSONException import org.json.JSONObject -import androidx.core.content.edit @Serializable @@ -129,7 +130,7 @@ private fun migrateAction(key: String) { * Migrate preferences from version 1 (used until version j-0.0.18) to the current format * (see [PREFERENCE_VERSION]) */ -fun migratePreferencesFromVersion1() { +fun migratePreferencesFromVersion1(context: Context) { assert(LauncherPreferences.internal().versionCode() == 1) Gesture.entries.forEach { g -> migrateAction(g.id) } migrateAppInfoSet(LauncherPreferences.apps().keys().hidden()) @@ -137,5 +138,5 @@ fun migratePreferencesFromVersion1() { migrateAppInfoStringMap(LauncherPreferences.apps().keys().customNames()) LauncherPreferences.internal().versionCode(2) - migratePreferencesFromVersion2() + migratePreferencesFromVersion2(context) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt index 4e6eae1..9714359 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version2.kt @@ -1,5 +1,6 @@ package de.jrpie.android.launcher.preferences.legacy +import android.content.Context import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction @@ -11,10 +12,10 @@ import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION * Migrate preferences from version 2 (used until version 0.0.21) to the current format * (see [PREFERENCE_VERSION]) */ -fun migratePreferencesFromVersion2() { +fun migratePreferencesFromVersion2(context: Context) { assert(LauncherPreferences.internal().versionCode() == 2) // previously there was no setting for this Action.setActionForGesture(Gesture.BACK, LauncherAction.CHOOSE) LauncherPreferences.internal().versionCode(3) - migratePreferencesFromVersion3() + migratePreferencesFromVersion3(context) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt index 4a9241f..e0a8447 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version3.kt @@ -1,17 +1,17 @@ package de.jrpie.android.launcher.preferences.legacy +import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences.Editor -import de.jrpie.android.launcher.apps.AppInfo +import androidx.core.content.edit import de.jrpie.android.launcher.apps.AbstractAppInfo +import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import java.util.HashSet -import androidx.core.content.edit /** * Migrate preferences from version 3 (used until version 0.0.23) to the current format @@ -70,8 +70,7 @@ private fun migrateMapAppInfoString(key: String, preferences: SharedPreferences, } } -fun migratePreferencesFromVersion3() { - assert(PREFERENCE_VERSION == 4) +fun migratePreferencesFromVersion3(context: Context) { assert(LauncherPreferences.internal().versionCode() == 3) val preferences = LauncherPreferences.getSharedPreferences() @@ -82,4 +81,5 @@ fun migratePreferencesFromVersion3() { } LauncherPreferences.internal().versionCode(4) + migratePreferencesFromVersion4(context) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt new file mode 100644 index 0000000..d4c7441 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt @@ -0,0 +1,27 @@ +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) +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt index 2d1152d..f954b31 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/VersionUnknown.kt @@ -3,10 +3,10 @@ package de.jrpie.android.launcher.preferences.legacy import android.content.Context import android.content.SharedPreferences import android.util.Log +import androidx.core.content.edit import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.theme.Background import de.jrpie.android.launcher.preferences.theme.ColorTheme -import androidx.core.content.edit private fun migrateStringPreference( @@ -392,5 +392,5 @@ fun migratePreferencesFromVersionUnknown(context: Context) { LauncherPreferences.internal().versionCode(1) Log.i(TAG, "migrated preferences to version 1.") - migratePreferencesFromVersion1() + migratePreferencesFromVersion1(context) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt index a2749ae..7b5d794 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt @@ -5,6 +5,7 @@ package de.jrpie.android.launcher.preferences.serialization import de.jrpie.android.launcher.apps.AbstractAppInfo import de.jrpie.android.launcher.apps.PinnedShortcutInfo import de.jrpie.android.launcher.widgets.Widget +import de.jrpie.android.launcher.widgets.WidgetPanel import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import kotlinx.serialization.Serializable @@ -46,6 +47,22 @@ class SetWidgetSerializer : } } +@Suppress("UNCHECKED_CAST") +class SetWidgetPanelSerializer : + PreferenceSerializer?, java.util.Set?> { + @Throws(PreferenceSerializationException::class) + override fun serialize(value: java.util.Set?): java.util.Set? { + return value?.map(WidgetPanel::serialize) + ?.toHashSet() as? java.util.Set + } + + @Throws(PreferenceSerializationException::class) + override fun deserialize(value: java.util.Set?): java.util.Set? { + return value?.map(java.lang.String::toString)?.map(WidgetPanel::deserialize) + ?.toHashSet() as? java.util.Set + } +} + @Suppress("UNCHECKED_CAST") class SetPinnedShortcutInfoPreferenceSerializer : diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 3f1e497..192a8e9 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -207,7 +207,6 @@ class HomeActivity : UIObject, Activity() { } override fun onTouchEvent(event: MotionEvent): Boolean { - android.util.Log.e("Launcher", "on touch") touchGestureDetector?.onTouchEvent(event) return true } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt index f176469..06be78a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt @@ -11,6 +11,7 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction +import de.jrpie.android.launcher.actions.WidgetPanelAction import de.jrpie.android.launcher.ui.list.ListActivity /** @@ -23,8 +24,10 @@ import de.jrpie.android.launcher.ui.list.ListActivity class OtherRecyclerAdapter(val activity: Activity) : RecyclerView.Adapter() { - private val othersList: Array = - LauncherAction.entries.filter { it.isAvailable(activity) }.toTypedArray() + private val othersList: Array = + LauncherAction.entries.filter { it.isAvailable(activity) } + .plus(WidgetPanelAction(-1)) + .toTypedArray() inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { @@ -36,10 +39,12 @@ class OtherRecyclerAdapter(val activity: Activity) : val pos = bindingAdapterPosition val content = othersList[pos] - activity.finish() val gestureId = (activity as? ListActivity)?.forGesture ?: return val gesture = Gesture.byId(gestureId) ?: return - Action.setActionForGesture(gesture, content) + content.showConfigurationDialog(activity) { configuredAction -> + Action.setActionForGesture(gesture, configuredAction) + activity.finish() + } } init { @@ -48,11 +53,11 @@ class OtherRecyclerAdapter(val activity: Activity) : } override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { - val otherLabel = activity.getString(othersList[i].label) - val icon = othersList[i].icon + val otherLabel = othersList[i].label(activity) + val icon = othersList[i].getIcon(activity) viewHolder.textView.text = otherLabel - viewHolder.iconView.setImageResource(icon) + viewHolder.iconView.setImageDrawable(icon) } override fun getItemCount(): Int { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt index 8907f04..bb9df74 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt @@ -11,6 +11,7 @@ import de.jrpie.android.launcher.actions.openAppsList import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.theme.ColorTheme import de.jrpie.android.launcher.setDefaultHomeScreen +import de.jrpie.android.launcher.ui.widgets.manage.ManageWidgetPanelsActivity import de.jrpie.android.launcher.ui.widgets.manage.ManageWidgetsActivity @@ -90,6 +91,14 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() { true } + val manageWidgetPanels = findPreference( + LauncherPreferences.widgets().keys().customPanels() + ) + manageWidgetPanels?.setOnPreferenceClickListener { + startActivity(Intent(requireActivity(), ManageWidgetPanelsActivity::class.java)) + true + } + val hiddenApps = findPreference( LauncherPreferences.apps().keys().hidden() ) diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt index 04668ca..d071771 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt @@ -13,6 +13,7 @@ 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 @@ -20,22 +21,29 @@ import kotlin.math.max /** * This only works in an Activity, not AppCompatActivity */ -open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : ViewGroup(context, attrs) { +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() - open fun updateWidgets(activity: Activity, widgets: Set?) { - if (widgets == null) { - return - } - Log.i("WidgetContainer", "updating ${activity.localClassName}") - widgetViewById.clear() - (0.. + open fun updateWidgets(activity: Activity, widgets: Collection?) { + 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, WidgetContainerView.Companion.LayoutParams(widget.position)) + addView(it, LayoutParams(widget.position)) widgetViewById.put(widget.id, it) } + } } } @@ -67,7 +75,6 @@ open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : (0...onCreate(savedInstanceState) + super.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.onStart() + super.onStart() + } + + override fun isHomeScreen(): Boolean { + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt new file mode 100644 index 0000000..b18852f --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt @@ -0,0 +1,104 @@ +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.onCreate(savedInstanceState) + super.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(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(R.id.dialog_create_widget_panel_edit_text) + ?.setText( + getString( + R.string.widget_panel_default_name, + WidgetPanel.allocateId() + ) + ) + } + true + } + } + + override fun onStart() { + super.onStart() + super.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() } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt index 3172401..d191b70 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt @@ -2,7 +2,6 @@ package de.jrpie.android.launcher.ui.widgets.manage import android.app.Activity import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.SharedPreferences import android.content.res.Resources @@ -18,6 +17,7 @@ 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 @@ -27,9 +27,13 @@ import kotlin.math.min 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()) { @@ -44,6 +48,9 @@ class ManageWidgetsActivity : Activity(), UIObject { super.onCreate(savedInstanceState) super.onCreate() setContentView(R.layout.activity_manage_widgets) + + panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id + findViewById(R.id.manage_widgets_button_add).setOnClickListener { selectWidget() } @@ -54,9 +61,10 @@ class ManageWidgetsActivity : Activity(), UIObject { insets } - findViewById(R.id.manage_widgets_container).updateWidgets(this, - (application as Application).widgets.value - ) + findViewById(R.id.manage_widgets_container).let { + it.widgetPanelId = panelId + it.updateWidgets(this, (application as Application).widgets.value) + } } override fun onStart() { @@ -101,6 +109,10 @@ class ManageWidgetsActivity : Activity(), UIObject { AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetHost.allocateAppWidgetId() ) + it.putExtra( + EXTRA_PANEL_ID, + panelId + ) }, REQUEST_PICK_APPWIDGET ) } @@ -124,7 +136,7 @@ class ManageWidgetsActivity : Activity(), UIObject { display.height ) - val widget = AppWidget(appWidgetId, provider, position) + val widget = AppWidget(appWidgetId, position, panelId, provider) LauncherPreferences.widgets().widgets( (LauncherPreferences.widgets().widgets() ?: HashSet()).also { it.add(widget) @@ -135,7 +147,7 @@ class ManageWidgetsActivity : Activity(), UIObject { private fun configureWidget(data: Intent) { val extras = data.extras val appWidgetId = extras!!.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) - val widget = AppWidget(appWidgetId) + val widget = AppWidget(appWidgetId, panelId = panelId) if (widget.isConfigurable(this)) { widget.configure(this, REQUEST_CREATE_APPWIDGET) } else { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt index c414db6..a1bd3b5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt @@ -21,6 +21,7 @@ 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 @@ -38,6 +39,7 @@ private const val REQUEST_WIDGET_PERMISSION = 29 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) { @@ -53,13 +55,14 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { 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))) + updateWidget(ClockWidget(widgetId, WidgetPosition(0, 4, 12, 3), widgetPanelId)) finish() } } @@ -79,6 +82,7 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id) if (widgetId == -1) { widgetId = getAppWidgetHost().allocateAppWidgetId() } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt index 2d41e13..7a355f7 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt @@ -20,6 +20,7 @@ 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 @@ -28,8 +29,9 @@ import kotlin.math.min /** * A variant of the [WidgetContainerView] which allows to manage widgets. */ -class WidgetManagerView(context: Context, attrs: AttributeSet? = null) : - WidgetContainerView(context, attrs) { +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 @@ -155,13 +157,14 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) : selectedWidgetOverlayView?.mode = null } - override fun updateWidgets(activity: Activity, widgets: Set?) { + override fun updateWidgets(activity: Activity, widgets: Collection?) { super.updateWidgets(activity, widgets) if (widgets == null) { return } + children.mapNotNull { it as? WidgetOverlayView }.forEach { removeView(it) } - widgets.forEach { widget -> + widgets.filter { it.panelId == widgetPanelId }.forEach { widget -> WidgetOverlayView(activity).let { addView(it) it.widgetId = widget.id diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt index 0ce789f..1b8a2d2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt @@ -13,12 +13,13 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.widgets.Widget import de.jrpie.android.launcher.widgets.updateWidget -/** - * An overlay to show configuration options for a widget. - */ 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 { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt new file mode 100644 index 0000000..40c2c2f --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt @@ -0,0 +1,98 @@ +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() { + + 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(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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt index 3e9a2eb..22a63eb 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt @@ -1,4 +1,4 @@ -package de.jrpie.android.launcher.widgets; +package de.jrpie.android.launcher.widgets import android.app.Activity import android.appwidget.AppWidgetHostView @@ -11,7 +11,6 @@ import android.util.DisplayMetrics import android.util.SizeF import android.view.View import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.ui.HomeActivity import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -20,6 +19,7 @@ import kotlinx.serialization.Serializable 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 @@ -31,10 +31,16 @@ class AppWidget( ): Widget() { - constructor(id: Int, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) : + constructor( + id: Int, + position: WidgetPosition, + panelId: Int, + widgetProviderInfo: AppWidgetProviderInfo + ) : this( id, position, + panelId, false, widgetProviderInfo.provider.packageName, widgetProviderInfo.provider.className, diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt index d819538..d0d1c0e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.Serializable class ClockWidget( override val id: Int, override var position: WidgetPosition, + override val panelId: Int, override var allowInteraction: Boolean = true ) : Widget() { diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt index d3610dd..dbe667b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.json.Json sealed class Widget { abstract val id: Int abstract var position: WidgetPosition + abstract val panelId: Int abstract var allowInteraction: Boolean /** @@ -36,6 +37,10 @@ sealed class Widget { ) } + fun getPanel(): WidgetPanel? { + return WidgetPanel.byId(panelId) + } + override fun hashCode(): Int { return id } diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt new file mode 100644 index 0000000..93e588d --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt @@ -0,0 +1,58 @@ +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) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt index cd4ef29..b7f140b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt @@ -74,9 +74,19 @@ fun getAppWidgetProviders( context: Context ): List { fun updateWidget(widget: Widget) { - var widgets = LauncherPreferences.widgets().widgets() ?: setOf() - widgets = widgets.minus(widget).plus(widget) - LauncherPreferences.widgets().widgets(widgets) + 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 { diff --git a/app/src/main/res/drawable/baseline_widgets_24.xml b/app/src/main/res/drawable/baseline_widgets_24.xml new file mode 100644 index 0000000..fd0f571 --- /dev/null +++ b/app/src/main/res/drawable/baseline_widgets_24.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/layout/activity_manage_widget_panels.xml b/app/src/main/res/layout/activity_manage_widget_panels.xml new file mode 100644 index 0000000..60413a3 --- /dev/null +++ b/app/src/main/res/layout/activity_manage_widget_panels.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_widget_panel.xml b/app/src/main/res/layout/activity_widget_panel.xml new file mode 100644 index 0000000..6ef6b20 --- /dev/null +++ b/app/src/main/res/layout/activity_widget_panel.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_create_widget_panel.xml b/app/src/main/res/layout/dialog_create_widget_panel.xml new file mode 100644 index 0000000..900823d --- /dev/null +++ b/app/src/main/res/layout/dialog_create_widget_panel.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_rename_widget_panel.xml b/app/src/main/res/layout/dialog_rename_widget_panel.xml new file mode 100644 index 0000000..effb783 --- /dev/null +++ b/app/src/main/res/layout/dialog_rename_widget_panel.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_select_widget_panel.xml b/app/src/main/res/layout/dialog_select_widget_panel.xml new file mode 100644 index 0000000..5f83d51 --- /dev/null +++ b/app/src/main/res/layout/dialog_select_widget_panel.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_widget_panels_row.xml b/app/src/main/res/layout/list_widget_panels_row.xml new file mode 100644 index 0000000..53f7449 --- /dev/null +++ b/app/src/main/res/layout/list_widget_panels_row.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 30e4cda..f783d2a 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -10,6 +10,7 @@ internal.first_startup internal.version_code widgets.widgets + widgets.custom_panels apps.favorites apps.hidden apps.pinned_shortcuts diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8813f8..7d29128 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ Click on time Manage widgets + Manage widget panels Choose App @@ -400,5 +401,23 @@ Clock The default clock of μLauncher + Delete + Rename + + Widget Panel #%1$d + + Contains %d widget. + Contains %d widgets. + + + + Ok + Widget Panels + Select a Widget Panel + Create new widget panel + Launcher > Manage Widget Panels.]]> + Open Widget Panel + This widget panel no longer exists. + Widgets diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index b4bc5f0..0ee7c17 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -3,14 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + - - - + + + +