From 4f795289d5176fb6c9130cd8aa136061eb6d0793 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Tue, 15 Apr 2025 18:55:13 +0200 Subject: [PATCH 01/34] improve English translation --- app/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8bc9b3..21f25f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -251,12 +251,12 @@ Music: Next Music: Previous Music: Play / Pause - Expand notifications panel + Expand Notifications Panel Recent Apps - Do nothing + Do Nothing Lock Screen Toggle Torch - Launch other Home Screen + Launch Other Home Screen Add Shortcut From 22633bdac3d057b552258d8bd84868c9cc7473b8 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Tue, 15 Apr 2025 19:24:23 +0200 Subject: [PATCH 02/34] try to fix #138 --- .../jrpie/android/launcher/ui/HomeActivity.kt | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) 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 53a0876..2ab5d9f 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 @@ -37,7 +37,7 @@ import java.util.Locale class HomeActivity : UIObject, AppCompatActivity() { private lateinit var binding: HomeBinding - private lateinit var touchGestureDetector: TouchGestureDetector + private var touchGestureDetector: TouchGestureDetector? = null private var sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> @@ -56,29 +56,12 @@ class HomeActivity : UIObject, AppCompatActivity() { super.onCreate(savedInstanceState) super.onCreate() - touchGestureDetector = TouchGestureDetector( - this, 0, 0, - LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f - ) - touchGestureDetector.updateScreenSize(windowManager) // Initialise layout binding = HomeBinding.inflate(layoutInflater) setContentView(binding.root) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - binding.root.setOnApplyWindowInsetsListener { _, windowInsets -> - @Suppress("deprecation") // required to support API 29 - val insets = windowInsets.systemGestureInsets - touchGestureDetector.setSystemGestureInsets(insets) - - windowInsets - } - } - - - // Handle back key / gesture on Android 13+, cf. onKeyDown() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { onBackInvokedDispatcher.registerOnBackInvokedCallback( @@ -94,7 +77,7 @@ class HomeActivity : UIObject, AppCompatActivity() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - touchGestureDetector.updateScreenSize(windowManager) + touchGestureDetector?.updateScreenSize(windowManager) } override fun onStart() { @@ -188,8 +171,28 @@ class HomeActivity : UIObject, AppCompatActivity() { override fun onResume() { super.onResume() - touchGestureDetector.edgeWidth = + /* This should be initialized in onCreate() + However on some devices there seems to be a bug where the touchGestureDetector + is not working properly after resuming the app. + Reinitializing the touchGestureDetector every time the app is resumed might help to fix that. + (see issue #138) + */ + touchGestureDetector = TouchGestureDetector( + this, 0, 0, LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f + ).also { + it.updateScreenSize(windowManager) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + binding.root.setOnApplyWindowInsetsListener { _, windowInsets -> + @Suppress("deprecation") // required to support API 29 + val insets = windowInsets.systemGestureInsets + touchGestureDetector?.setSystemGestureInsets(insets) + + windowInsets + } + } initClock() updateSettingsFallbackButtonVisibility() @@ -230,7 +233,7 @@ class HomeActivity : UIObject, AppCompatActivity() { } override fun onTouchEvent(event: MotionEvent): Boolean { - touchGestureDetector.onTouchEvent(event) + touchGestureDetector?.onTouchEvent(event) return true } From 077bd1ce448808f969254e704681924143873da7 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Wed, 23 Apr 2025 01:51:01 +0200 Subject: [PATCH 03/34] add option to hide keyboard when scrolling (cf. #142) --- .../preferences/LauncherPreferences$Config.java | 1 + .../java/de/jrpie/android/launcher/ui/Helper.kt | 16 ++++++++++++---- .../launcher/ui/list/apps/ListFragmentApps.kt | 17 +++++++++++++++++ app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/preferences.xml | 4 ++++ 6 files changed, 36 insertions(+), 4 deletions(-) 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 85979fe..4653910 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 @@ -72,6 +72,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @Preference(name = "search_auto_launch", type = boolean.class, defaultValue = "true"), @Preference(name = "search_web", type = boolean.class, description = "false"), @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 = { @Preference(name = "double_swipe", type = boolean.class, defaultValue = "true"), diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt index 1ca4d2b..a863c67 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt @@ -1,5 +1,6 @@ package de.jrpie.android.launcher.ui +import android.app.Activity import android.content.Context import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter @@ -38,10 +39,17 @@ fun ImageView.transformGrayscale(grayscale: Boolean) { } -// Taken from https://stackoverflow.com/a/50743764/12787264 +// Taken from https://stackoverflow.com/a/50743764 fun View.openSoftKeyboard(context: Context) { this.requestFocus() - // open the soft keyboard - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .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 ) + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt index 1a55bbb..a8e59ba 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt @@ -11,13 +11,16 @@ import android.widget.Toast import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import de.jrpie.android.launcher.R import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.databinding.ListAppsBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.UIObject +import de.jrpie.android.launcher.ui.closeSoftKeyboard import de.jrpie.android.launcher.ui.list.ListActivity import de.jrpie.android.launcher.ui.openSoftKeyboard +import kotlin.math.absoluteValue /** @@ -90,6 +93,20 @@ class ListFragmentApps : Fragment(), UIObject { } } 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 : diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 89ec086..69c7f6a 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -147,6 +147,7 @@ functionality.search_auto_launch functionality.search_web functionality.search_auto_keyboard + functionality.search_auto_close_keyboard settings_action_lock_method diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21f25f5..ed2bb66 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -167,6 +167,7 @@ Search the web Press return while searching the app list to launch a web search. Start keyboard for search + Close keyboard when scrolling Sensitivity diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6ef5d07..7d906ff 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -104,6 +104,10 @@ android:key="@string/settings_functionality_search_auto_open_keyboard_key" android:defaultValue="true" android:title="@string/settings_functionality_auto_keyboard" /> + Date: Thu, 24 Apr 2025 14:37:05 +0200 Subject: [PATCH 04/34] add support for app widgets (see #44) --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 10 +- .../de/jrpie/android/launcher/Application.kt | 26 ++- .../de/jrpie/android/launcher/Functions.kt | 5 +- .../LauncherPreferences$Config.java | 4 + .../launcher/preferences/Preferences.kt | 10 + .../serialization/PreferenceSerializers.kt | 19 ++ .../jrpie/android/launcher/ui/HomeActivity.kt | 95 +++------- .../launcher/ui/PinShortcutActivity.kt | 16 +- .../launcher/SettingsFragmentLauncher.kt | 9 + .../android/launcher/ui/widgets/ClockView.kt | 80 ++++++++ .../ui/widgets/WidgetContainerView.kt | 137 ++++++++++++++ .../widgets/manage/ManageWidgetsActivity.kt | 173 ++++++++++++++++++ .../ui/widgets/manage/SelectWidgetActivity.kt | 168 +++++++++++++++++ .../ui/widgets/manage/WidgetManagerView.kt | 172 +++++++++++++++++ .../ui/widgets/manage/WidgetOverlayView.kt | 131 +++++++++++++ .../android/launcher/widgets/AppWidget.kt | 120 ++++++++++++ .../android/launcher/widgets/ClockWidget.kt | 41 +++++ .../widgets/LauncherWidgetProvider.kt | 58 ++++++ .../jrpie/android/launcher/widgets/Widget.kt | 60 ++++++ .../launcher/widgets/WidgetPosition.kt | 58 ++++++ .../jrpie/android/launcher/widgets/Widgets.kt | 87 +++++++++ app/src/main/res/drawable/baseline_add_24.xml | 11 ++ .../main/res/drawable/baseline_clock_24.xml | 15 ++ .../res/layout/activity_manage_widgets.xml | 25 +++ .../res/layout/activity_select_widget.xml | 72 ++++++++ app/src/main/res/layout/clock.xml | 35 ++++ app/src/main/res/layout/home.xml | 27 +-- .../main/res/layout/list_widgets_header.xml | 35 ++++ app/src/main/res/layout/list_widgets_row.xml | 60 ++++++ app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 12 ++ app/src/main/res/values/styles.xml | 10 +- app/src/main/res/xml/preferences.xml | 4 + 35 files changed, 1691 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt create mode 100644 app/src/main/res/drawable/baseline_add_24.xml create mode 100644 app/src/main/res/drawable/baseline_clock_24.xml create mode 100644 app/src/main/res/layout/activity_manage_widgets.xml create mode 100644 app/src/main/res/layout/activity_select_widget.xml create mode 100644 app/src/main/res/layout/clock.xml create mode 100644 app/src/main/res/layout/list_widgets_header.xml create mode 100644 app/src/main/res/layout/list_widgets_row.xml diff --git a/app/build.gradle b/app/build.gradle index 1a0a6fb..eaf97f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -106,6 +106,7 @@ dependencies { implementation 'com.google.android.material:material:1.12.0' implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") 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 "com.android.databinding:compiler:$android_plugin_version" testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5f8831..841c9bd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ tools:ignore="QueryAllPackagesPermission" /> + + + - + \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt index e6cce23..775621c 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -12,6 +12,8 @@ import android.os.Build.VERSION_CODES import android.os.UserHandle import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetManager import androidx.preference.PreferenceManager import de.jrpie.android.launcher.actions.TorchManager import de.jrpie.android.launcher.apps.AbstractAppInfo @@ -20,13 +22,22 @@ import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion import de.jrpie.android.launcher.preferences.resetPreferences +import de.jrpie.android.launcher.widgets.LauncherWidgetProvider +import de.jrpie.android.launcher.widgets.Widget import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch + +const val APP_WIDGET_HOST_ID = 42; + + class Application : android.app.Application() { val apps = MutableLiveData>() + val widgets = MutableLiveData>() val privateSpaceLocked = MutableLiveData() + lateinit var appWidgetHost: AppWidgetHost + lateinit var appWidgetManager: AppWidgetManager private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -90,6 +101,8 @@ class Application : android.app.Application() { customAppNames = LauncherPreferences.apps().customNames() } else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) { loadApps() + } else if (pref == LauncherPreferences.widgets().keys().widgets()) { + widgets.postValue(LauncherPreferences.widgets().widgets() ?: setOf()) } } @@ -103,10 +116,15 @@ class Application : android.app.Application() { torchManager = TorchManager(this) } + appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID) + appWidgetManager = AppWidgetManager.getInstance(this.applicationContext) + + appWidgetHost.startListening() + + val preferences = PreferenceManager.getDefaultSharedPreferences(this) LauncherPreferences.init(preferences, this.resources) - // Try to restore old preferences migratePreferencesToNewVersion(this) @@ -157,4 +175,10 @@ class Application : android.app.Application() { apps.postValue(getApps(packageManager, applicationContext)) } } + + override fun onTerminate() { + appWidgetHost.stopListening() + super.onTerminate() + + } } diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt index afc2c31..9679ae5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -6,6 +6,9 @@ import android.app.role.RoleManager import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.content.Intent import android.content.pm.LauncherApps @@ -223,4 +226,4 @@ fun copyToClipboard(context: Context, text: String) { val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("Debug Info", text) clipboardManager.setPrimaryClip(clipData) -} \ No newline at end of file +} 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 4653910..575346a 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.SetWidgetSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.Font; @@ -82,5 +83,8 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = { @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) + }), }) 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 59ecc7a..332f4df 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 @@ -13,6 +13,9 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3 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.WidgetPosition +import de.jrpie.android.launcher.widgets.deleteAllWidgets /* Current version of the structure of preferences. * Increase when breaking changes are introduced and write an appropriate case in @@ -71,6 +74,13 @@ fun resetPreferences(context: Context) { Log.i(TAG, "Resetting preferences") LauncherPreferences.clear() LauncherPreferences.internal().versionCode(PREFERENCE_VERSION) + deleteAllWidgets(context) + + LauncherPreferences.widgets().widgets( + setOf( + ClockWidget(-500, WidgetPosition(1,4,10,3)) + ) + ) val hidden: MutableSet = mutableSetOf() 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 3e19daf..a2749ae 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 @@ -4,6 +4,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 eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import kotlinx.serialization.Serializable @@ -28,6 +29,24 @@ class SetAbstractAppInfoPreferenceSerializer : } } + +@Suppress("UNCHECKED_CAST") +class SetWidgetSerializer : + PreferenceSerializer?, java.util.Set?> { + @Throws(PreferenceSerializationException::class) + override fun serialize(value: java.util.Set?): java.util.Set? { + return value?.map(Widget::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(Widget::deserialize) + ?.toHashSet() as? java.util.Set + } +} + + @Suppress("UNCHECKED_CAST") class SetPinnedShortcutInfoPreferenceSerializer : PreferenceSerializer?, java.util.Set?> { 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 2ab5d9f..3f1e497 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 @@ -1,6 +1,7 @@ package de.jrpie.android.launcher.ui import android.annotation.SuppressLint +import android.app.Activity import android.content.SharedPreferences import android.content.res.Configuration import android.content.res.Resources @@ -10,8 +11,7 @@ import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.window.OnBackInvokedDispatcher -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.isVisible +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture @@ -20,7 +20,6 @@ import de.jrpie.android.launcher.databinding.HomeBinding import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity -import java.util.Locale /** * [HomeActivity] is the actual application Launcher, @@ -34,7 +33,7 @@ import java.util.Locale * - Setting global variables (preferences etc.) * - Opening the [TutorialActivity] on new installations */ -class HomeActivity : UIObject, AppCompatActivity() { +class HomeActivity : UIObject, Activity() { private lateinit var binding: HomeBinding private var touchGestureDetector: TouchGestureDetector? = null @@ -45,15 +44,18 @@ class HomeActivity : UIObject, AppCompatActivity() { prefKey?.startsWith("display.") == true ) { 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?) { - super.onCreate(savedInstanceState) + super.onCreate(savedInstanceState) super.onCreate() @@ -81,8 +83,7 @@ class HomeActivity : UIObject, AppCompatActivity() { } override fun onStart() { - super.onStart() - + super.onStart() super.onStart() // If the tutorial was not finished, start it @@ -93,6 +94,15 @@ class HomeActivity : UIObject, AppCompatActivity() { LauncherPreferences.getSharedPreferences() .registerOnSharedPreferenceChangeListener(sharedPreferencesListener) + (application as Application).appWidgetHost.startListening() + + } + + + + override fun onStop() { + (application as Application).appWidgetHost.stopListening() + super.onStop() } override fun onWindowFocusChanged(hasFocus: Boolean) { @@ -118,44 +128,6 @@ class HomeActivity : UIObject, AppCompatActivity() { } } - 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 { val mTheme = modifyTheme(super.getTheme()) mTheme.applyStyle(R.style.backgroundWallpaper, true) @@ -193,9 +165,11 @@ class HomeActivity : UIObject, AppCompatActivity() { windowInsets } } - - initClock() updateSettingsFallbackButtonVisibility() + + binding.homeWidgetContainer.updateWidgets(this@HomeActivity, + LauncherPreferences.widgets().widgets() + ) } override fun onDestroy() { @@ -233,30 +207,11 @@ class HomeActivity : UIObject, AppCompatActivity() { } override fun onTouchEvent(event: MotionEvent): Boolean { + android.util.Log.e("Launcher", "on touch") touchGestureDetector?.onTouchEvent(event) 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() { Gesture.BACK(this) } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt index 71908ba..3dbdda8 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt @@ -49,7 +49,21 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { val request = launcherApps.getPinItemRequest(intent) this.request = request - if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) { + if (request == null) { + finish() + return + } + + if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) { + + // TODO + request.getAppWidgetProviderInfo(this) + // startActivity() + finish() + return + } + + if (request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) { finish() return } 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 a8efb43..8907f04 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.ManageWidgetsActivity /** @@ -81,6 +82,14 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() { true } + val manageWidgets = findPreference( + LauncherPreferences.widgets().keys().widgets() + ) + manageWidgets?.setOnPreferenceClickListener { + startActivity(Intent(requireActivity(), ManageWidgetsActivity::class.java)) + true + } + val hiddenApps = findPreference( LauncherPreferences.apps().keys().hidden() ) diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt new file mode 100644 index 0000000..33c4888 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt @@ -0,0 +1,80 @@ +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) + } + } + } + + + +} \ No newline at end of file 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 new file mode 100644 index 0000000..04668ca --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt @@ -0,0 +1,137 @@ +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.WidgetPosition +import kotlin.math.max + + +/** + * This only works in an Activity, not AppCompatActivity + */ +open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : ViewGroup(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.. + widget.createView(activity)?.let { + addView(it, WidgetContainerView.Companion.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.. + if (prefKey == LauncherPreferences.widgets().keys().widgets()) { + // We can't observe the livedata because this is not an AppCompatActivity + findViewById(R.id.manage_widgets_container).updateWidgets(this, + LauncherPreferences.widgets().widgets() + ) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + super.onCreate() + setContentView(R.layout.activity_manage_widgets) + findViewById(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(R.id.manage_widgets_container).updateWidgets(this, + (application as Application).widgets.value + ) + } + + override fun onStart() { + super.onStart() + super.onStart() + + LauncherPreferences.getSharedPreferences() + .registerOnSharedPreferenceChangeListener(sharedPreferencesListener) + + } + + override fun onResume() { + super.onResume() + findViewById(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() + ) + }, 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, provider, position) + 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) + 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 + } +} 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 new file mode 100644 index 0000000..c414db6 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt @@ -0,0 +1,168 @@ +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.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 + + 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) + } + ) + finish() + } + } + is LauncherClockWidgetProvider -> { + updateWidget(ClockWidget(widgetId, WidgetPosition(0,4,12,3))) + finish() + } + } + } + + override fun onStart() { + super.onStart() + super.onStart() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + super.onCreate() + + binding = ActivitySelectWidgetBinding.inflate(layoutInflater) + setContentView(binding.root) + + + widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + 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() { + + 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) + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..2d41e13 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt @@ -0,0 +1,172 @@ +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.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(context: Context, attrs: AttributeSet? = null) : + WidgetContainerView(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: Set?) { + super.updateWidgets(activity, widgets) + if (widgets == null) { + return + } + + widgets.forEach { widget -> + WidgetOverlayView(activity).let { + addView(it) + it.widgetId = widget.id + (it.layoutParams as Companion.LayoutParams).position = widget.position + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..0ce789f --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt @@ -0,0 +1,131 @@ +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 + +/** + * 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() +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 { + return listOf( + 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) + } +} \ 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 new file mode 100644 index 0000000..3e9a2eb --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt @@ -0,0 +1,120 @@ +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 de.jrpie.android.launcher.ui.HomeActivity +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 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, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) : + this( + id, + position, + 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): 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 + ) + } +} 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 new file mode 100644 index 0000000..d819538 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt @@ -0,0 +1,41 @@ +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 var allowInteraction: Boolean = true +) : Widget() { + + override fun createView(activity: Activity): View? { + return ClockView(activity, null, id) + } + + override fun findView(views: Sequence): 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) { } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt new file mode 100644 index 0000000..018b29b --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt @@ -0,0 +1,58 @@ +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) + } +} + 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 new file mode 100644 index 0000000..d3610dd --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt @@ -0,0 +1,60 @@ +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 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? + 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) + } + ) + } + + 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 + } + } + } +} diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt new file mode 100644 index 0000000..b575665 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt @@ -0,0 +1,58 @@ +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 + ) + + } + } +} \ 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 new file mode 100644 index 0000000..cd4ef29 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt @@ -0,0 +1,87 @@ +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 { + val list = mutableListOf(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) { + var widgets = LauncherPreferences.widgets().widgets() ?: setOf() + widgets = widgets.minus(widget).plus(widget) + LauncherPreferences.widgets().widgets(widgets) +} + +fun Context.getAppWidgetHost(): AppWidgetHost { + return (this.applicationContext as Application).appWidgetHost +} +fun Context.getAppWidgetManager(): AppWidgetManager { + return (this.applicationContext as Application).appWidgetManager +} diff --git a/app/src/main/res/drawable/baseline_add_24.xml b/app/src/main/res/drawable/baseline_add_24.xml new file mode 100644 index 0000000..13267ce --- /dev/null +++ b/app/src/main/res/drawable/baseline_add_24.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_clock_24.xml b/app/src/main/res/drawable/baseline_clock_24.xml new file mode 100644 index 0000000..7968998 --- /dev/null +++ b/app/src/main/res/drawable/baseline_clock_24.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_manage_widgets.xml b/app/src/main/res/layout/activity_manage_widgets.xml new file mode 100644 index 0000000..c77f0e3 --- /dev/null +++ b/app/src/main/res/layout/activity_manage_widgets.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_select_widget.xml b/app/src/main/res/layout/activity_select_widget.xml new file mode 100644 index 0000000..82db94d --- /dev/null +++ b/app/src/main/res/layout/activity_select_widget.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/clock.xml b/app/src/main/res/layout/clock.xml new file mode 100644 index 0000000..d81fc5f --- /dev/null +++ b/app/src/main/res/layout/clock.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/home.xml index ecefdea..717151f 100644 --- a/app/src/main/res/layout/home.xml +++ b/app/src/main/res/layout/home.xml @@ -10,29 +10,10 @@ android:fitsSystemWindows="true" tools:context=".ui.HomeActivity"> - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_widgets_row.xml b/app/src/main/res/layout/list_widgets_row.xml new file mode 100644 index 0000000..878aaad --- /dev/null +++ b/app/src/main/res/layout/list_widgets_row.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1e4d12b..2712036 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,5 +11,9 @@ #fff #9999ff #000 + #FF29B6F6 + #FF039BE5 + #FFBDBDBD + #FF757575 diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 69c7f6a..30e4cda 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -9,6 +9,7 @@ internal.started_before internal.first_startup internal.version_code + widgets.widgets 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 ed2bb66..a8813f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,6 +98,8 @@ Time Click on time + Manage widgets + Choose App @@ -388,5 +390,15 @@ Open Source Licenses No app found to handle search. Can\'t open URL: no browser found. + Choose Widget + + Remove + Configure + Enable Interaction + Disable Interaction + + + Clock + The default clock of μLauncher diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 20ccb67..c5b7252 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -66,12 +66,12 @@ 0 2 + - - + + + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7d906ff..b4bc5f0 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -6,6 +6,10 @@ + + Date: Fri, 25 Apr 2025 11:25:00 +0200 Subject: [PATCH 05/34] fix #145 --- app/src/main/res/layout/list_apps_row_variant_grid.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/layout/list_apps_row_variant_grid.xml b/app/src/main/res/layout/list_apps_row_variant_grid.xml index ee57c45..f63d724 100644 --- a/app/src/main/res/layout/list_apps_row_variant_grid.xml +++ b/app/src/main/res/layout/list_apps_row_variant_grid.xml @@ -27,6 +27,8 @@ android:text="" android:textSize="11sp" tools:text="@string/app_name" + android:ellipsize="end" + android:lines="1" app:layout_constraintTop_toBottomOf="@id/list_apps_row_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" From a4fcdf60c7d3be3d37be15fb3fc8c9b286a7f953 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Sat, 26 Apr 2025 21:52:21 +0200 Subject: [PATCH 06/34] 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"> + - - - + + + + Date: Tue, 29 Apr 2025 22:19:08 -0500 Subject: [PATCH 07/34] add wiki --- .gitmodules | 3 +++ launcher.wiki | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 launcher.wiki diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f1aceb4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "launcher.wiki"] + path = launcher.wiki + url = https://github.com/wassupluke/Launcher.wiki.git diff --git a/launcher.wiki b/launcher.wiki new file mode 160000 index 0000000..d40df78 --- /dev/null +++ b/launcher.wiki @@ -0,0 +1 @@ +Subproject commit d40df78a56336aa0ae26ad1308e4aac48456dff8 From f6155102560d140f72e9e5c77937166a0f157aba Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 30 Apr 2025 20:26:05 -0500 Subject: [PATCH 08/34] migrate wiki to docs directory --- docs/{build.md => Building-from-Source.md} | 21 +- ...cher.md => Changes-from-Finns-Launcher.md} | 0 docs/Contributing.md | 10 + docs/Gestures-and-Bindings.md | 47 ++++ docs/Home.md | 37 +++ docs/Launcher-Settings.md | 222 ++++++++++++++++++ docs/Work-Profile.md | 3 + launcher.wiki | 1 - 8 files changed, 331 insertions(+), 10 deletions(-) rename docs/{build.md => Building-from-Source.md} (76%) rename docs/{launcher.md => Changes-from-Finns-Launcher.md} (100%) create mode 100644 docs/Contributing.md create mode 100644 docs/Gestures-and-Bindings.md create mode 100644 docs/Home.md create mode 100644 docs/Launcher-Settings.md create mode 100644 docs/Work-Profile.md delete mode 160000 launcher.wiki diff --git a/docs/build.md b/docs/Building-from-Source.md similarity index 76% rename from docs/build.md rename to docs/Building-from-Source.md index 75921f9..3c2ef1f 100644 --- a/docs/build.md +++ b/docs/Building-from-Source.md @@ -1,11 +1,9 @@ -# Building µLauncher +# Using the command line -## Using the command line - -Install JDK 17 and the Android Sdk. +Install JDK 17 and the Android SDK. Make sure that `JAVA_HOME` and `ANDROID_HOME` are set correctly. -``` +```bash git clone https://github.com/jrpie/Launcher cd Launcher @@ -15,7 +13,7 @@ cd Launcher This will create an apk file at `app/build/outputs/apk/default/release/app-default-release-unsigned.apk`. Note that you need to sign it: -``` +```bash apksigner sign --ks "$YOUR_KEYSTORE" \ --ks-key-alias "$YOUR_ALIAS" \ --ks-pass="pass:$YOUR_PASSWORD" \ @@ -28,13 +26,18 @@ apksigner sign --ks "$YOUR_KEYSTORE" \ app-default-release-unsigned.apk ``` - See [this guide](https://developer.android.com/build/building-cmdline) for further instructions. -## Using Android Studio +# Using Android Studio + Install [Android Studio](https://developer.android.com/studio), import this project and build it. See [this guide](https://developer.android.com/studio/run) -for further instructions. +for further instructions. How to + +# CI Pipeline + +The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds. +> Note: These builds are *not* signed. diff --git a/docs/launcher.md b/docs/Changes-from-Finns-Launcher.md similarity index 100% rename from docs/launcher.md rename to docs/Changes-from-Finns-Launcher.md diff --git a/docs/Contributing.md b/docs/Contributing.md new file mode 100644 index 0000000..bd56a7a --- /dev/null +++ b/docs/Contributing.md @@ -0,0 +1,10 @@ +# Ways to contribute + +- Found a **bug** or have an idea for a **new feature**? [Join the chat](https://s.jrpie.de/launcher-chat) or open an [issue](https://github.com/jrpie/Launcher/issues/). + > Please note that I work on this project in my free time. Thus I might not respond immediately and not all ideas will be implemented. +- Implement a new feature yourself: + 1. [Fork](https://github.com/jrpie/launcher/fork) this repository. + 2. Create a **new branch** named *feature/\* or *fix/\* and commit your changes. + 3. Open a new pull request. +- Add or improve [translations](https://toolate.othing.xyz/projects/jrpie-launcher/). +translation status \ No newline at end of file diff --git a/docs/Gestures-and-Bindings.md b/docs/Gestures-and-Bindings.md new file mode 100644 index 0000000..d87a406 --- /dev/null +++ b/docs/Gestures-and-Bindings.md @@ -0,0 +1,47 @@ +# Available Gestures +## Swipes +- Up, down, left, or right. +- Up, down, left, or right with two fingers. +- Up or down on the left or right edge. +- Left or right on the top or bottom edge. + +## Taps +- Tap on date or time. +- Double tap. +- Long tap. + +## Tap-then-Swipes +tap then swipe up, down, left, or right + +## Complex Gestures +- Draw <, >, V, or Λ. +- Draw <, >, V, or Λ in reverse direction. + +## Hardware Buttons as Gestures +- Back button. +- Volume up or down button. + + +*** + + +# Available Bindings +Any of the above gestures can be bound to any of the following bindings. +## Launcher Bindings +- Open µLauncher settings. +- Open a list of all*, favorite, or private* apps. + +## App Bindings +- Launch an app. +- Launch another Home Screen. + +## Android / Device Bindings +- Toggle private space lock. +- Lock the screen. +- Toggle the torch (flashlight) +- Raise or lower volume. +- Skip to previous or next audio track. +- Open Notifications shade. +- Open Quick Settings shade. + +> \* excludes apps hidden by the user \ No newline at end of file diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 0000000..8ce432c --- /dev/null +++ b/docs/Home.md @@ -0,0 +1,37 @@ +# Welcome to the μLauncher wiki! +We're thrilled you're here and confident you'll love your new Android launcher! Check out this wiki to get familiar with your new app. + +## What is μLauncher? +µLauncher is an *minimal* and *distraction-free* Android home screen that lets you launch apps using *efficient* swipe gestures and button presses. This project is a fork of [finnmglas's app Launcher](https://github.com/finnmglas/Launcher). An incomplete list of changes can be found [here](https://github.com/wassupluke/Launcher/blob/master/docs/launcher.md). + +## Where can I get μLauncher? +Get it on F-Droid +Get it on Accrescent +Get it on Obtainium +Get it on GitHub + +> You can also [get it on Google Play](https://play.google.com/store/apps/details?id=de.jrpie.android.launcher), but this is not recommend. + +## Screenshots +μLauncher Home Screen screenshot +μLauncher Settings screenshot +μLauncher All Apps list view with icons screenshot +μLauncher Favorite Apps list view with icons screenshot +μLauncher Choose App to bind to gesture screenshot +μLauncher App options card from list view with icons screenshot +μLauncher All Apps list view without icons screenshot + \ No newline at end of file diff --git a/docs/Launcher-Settings.md b/docs/Launcher-Settings.md new file mode 100644 index 0000000..ac665fa --- /dev/null +++ b/docs/Launcher-Settings.md @@ -0,0 +1,222 @@ +Tweaks and customizations can be made from within the Launcher Settings page. + +These settings let you change wallpapers, change colors and fonts, enable monochrome app icons, change the app drawer layout, and much more. + +In the following documentation, 'app drawer' will be used to refer to the 'All Apps' and 'Favorite Apps' views. + + +# Appearance + +### Choose a wallpaper +Lets you change the wallpaper using a photos app, file explorer, or native wallpaper setting app. + + +> ### Font + +Set the font used within the app settings. This setting does not affect the date/time [home screen font](https://github.com/wassupluke/Launcher/wiki/Tweaks-and-Customizations/_edit#font-1). + +**type:** `dropdown` + +**options:** `Hack`,`System default`,`Sans serif`,`Serif`,`Monospace`,`Serif monospace` + +> ### Text Shadow + +**type:** `toggle` + +> ### Background (app list and setting) + +**type:** `dropdown` + +**type:** `Transparent`,`Dim`,`Blur`,`Solid` + +> ### Monochrome app icons + +Remove coloring from all app icons. Can help decrease visual stimulus when enabled. + +**type:** `toggle` + + +# Date & Time + +> ### Font + +Set the home screen font for date and time. This setting does not affect the [app settings font](https://github.com/wassupluke/Launcher/wiki/Tweaks-and-Customizations/_edit#font). + +**type:** `dropdown` + +**options:** `Hack`,`System default`,`Sans serif`,`Serif`,`Monospace`,`Serif monospace` + +> ### Color [`[bug]`](https://github.com/jrpie/launcher/issues/151) + +Set the color for the home screen date and time. + +Accepts a HEX color code (consisting of a '#' followed by three sets of two alphanumeric (letters and numbers) characters. A fourth set of two alphanumeric characters may be added to set the transparency of the color. + +[Color wheel picker](https://rgbacolorpicker.com/color-wheel-picker) + +**type:** `HEX`,`RGBA` + +> ### Use localized date format + +Adapt the display of dates and times to the specific conventions of a particular locale or region. Different locales use different date orders (e.g., MM/DD/YYYY in the US, DD/MM/YYYY in Europe). + +**type:** `toggle` + +> ### Show time + +Show the current time on the home screen. + +**type:** `toggle` + +> ### Show seconds + +Show the current time down to the second on the home screen. + +**type:** `toggle` + +> ### Show date + +Show the current date on the home screen. + +**type:** `toggle` + +> ### Flip date and time + +Place the current time above the current date on the home screen. + +**type:** `toggle` + + +# Functionality + +> ### Launch search results + +Launches any app that matches user keyboard input when no other apps match. + +As you type inside the app drawer, the app narrows down the list of apps shown based on the app title matching your text input. For example, if you type, `a`, the app list narrows to any apps with a title containing the letter `a`. Continuing the example, if you then follow your `a` with the letter `m`, the list now shows only apps containing the letter combination `am` in that order. If the only app matching this combination was, for example, `Amazon`, simply typing `am` in the app drawer would immediately launch the `Amazon` app for you. + +This feature becomes more powerful when combined with [renaming](https://github.com/wassupluke/Launcher/wiki/Launcher-Settings/_edit#additional-settings) apps, effectively letting you define custom app names that could be considered 'aliases' or shortcuts. For instance, if you wanted to "bind" the keyboard input `gh` to open your `GitHub` app, you could rename `GitHub` to `GitHub gh`, `gh GitHub`, or simply `gh`. Assuming no other installed apps have the `gh` combination of letters in them, opening the app drawer and typing `gh` would immediately launch your `GitHub` app. + +Press space to temporarily disable this feature and allow text entry without prematurely launching an app. Useful when combined with the [Search the web](https://github.com/wassupluke/Launcher/wiki/Launcher-Settings/_edit#search-the-web) feature. + +**type:** `toggle` + +> ### Search the web + +Press return/enter while searching the app list to launch a web search. + +**type:** `toggle` + +> ### Start keyboard for search + +Automatically open the keyboard when the app drawer is opened. + +**type:** `toggle` + +> ### Double swipe actions + +Enable double swipe (two finger) actions as bindable gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. + +**type:** `toggle` + +> ### Edge swipe actions + +Enable edge swipe (near edges of screen) actions as bindable gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. + +**type:** `toggle` + +> ### Edge width + +Change how large a margin is used for detecting edge gestures. Shows the edge margin preview when using the slider. + +**type:** `slider` + +> ### Choose method for locking the screen + +There are two methods to lock the screen and unfortunately both have downsides. +1. **`Device Admin`** + - Doesn't work with unlocking by fingerprint or face recognition. +2. **`Accessibility Service`** + - Requires excessive privileges. + - μLauncher will use those privileges *only* for locking the screen. + - As a rule of thumb, it is [not recommended](https://android.stackexchange.com/questions/248171/is-it-safe-to-give-accessibility-permission-to-an-app) to grant access to accessibility services on a random app. Always review the [source code](https://github.com/jrpie/Launcher) before granting accessibility permissions so you familiarize yourself with what the code might do. + - On some devices, the start-up PIN will no longer be used for encrypting data after activating an accessibility service. + - This can be [reactivated](https://issuetracker.google.com/issues/37010136#comment36) afterwards. + +**type:** `text buttons` + +**options:** `USE DEVICE ADMIN`,`USE ACCESSIBILITY SERVICE` + +# Apps + +> ### Hidden apps + +Open an app drawer containing only hidden apps. + +> ### Don't show apps that are bound to a gesture in the app list + +Remove certain apps from the app drawer if they are already accessible via a gesture. + +Reduces redundancy and tidies up app drawer. + +**type:** `toggle` + +> ### Hide paused apps + +Remove paused apps from the app drawer. + +**type:** `toggle` + +> ### Hide private space from app list + +Remove private space from app drawer. + +**type:** `toggle` + +> ### Layout of app list + +Change how the apps are displayed when accessing the app drawer. By `Default`, all apps in the drawer will show in a vertically-scrolled list with their app icon and title. `Text` removes the app icons, shows only app titles in the drawer as a vertically-scrolled list. `Grid` shows apps with their app icon and title in a grid layout. + +**type:** `dropdown` + +**options:** `Default`,`Text`,`Grid` + +> ### Reverse the app list + +Enable Z-A sorting of apps in the app drawer. Useful for keeping apps within easier reach from the keyboard. + +**type:** `toggle` + + +# Display + +> ### Rotate screen + +**type:** `toggle` + +> ### Keep screen on + +**type:** `toggle` + +> ### Hide status bar + +Remove the top status bar from the home screen. + +**type:** `toggle` + +> ### Hide navigation bar + +Remove the navigation bar from the home screen. Enabling this setting may make it difficult to use the device if gestures are not setup properly. + +**type:** `toggle` + + +# Additional Settings + +> ### App Drawer Long Press on App + +Access additional per-app details and settings. To use, open the app drawer and long press on any app. + +**type:** `dropdown` + +**options:** `App Info`,`Add to favorites`,`Hide`,`Rename`,`Uninstall` \ No newline at end of file diff --git a/docs/Work-Profile.md b/docs/Work-Profile.md new file mode 100644 index 0000000..38c6598 --- /dev/null +++ b/docs/Work-Profile.md @@ -0,0 +1,3 @@ +# Android Enterprise Work Profile + +µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. \ No newline at end of file diff --git a/launcher.wiki b/launcher.wiki deleted file mode 160000 index d40df78..0000000 --- a/launcher.wiki +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d40df78a56336aa0ae26ad1308e4aac48456dff8 From b2b823446f03c6d66c7e45c8088f2e9e4644d18e Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 30 Apr 2025 20:34:06 -0500 Subject: [PATCH 09/34] remove stray .gitmodules after wiki deletion --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f1aceb4..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "launcher.wiki"] - path = launcher.wiki - url = https://github.com/wassupluke/Launcher.wiki.git From 8f9f8ac928697939ec09be78bdaac0f8c2e143d4 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 30 Apr 2025 22:34:28 -0500 Subject: [PATCH 10/34] improve codestyle --- docs/Building-from-Source.md | 10 +++-- docs/Changes-from-Finns-Launcher.md | 27 +++++++------ docs/Contributing.md | 11 +++++- docs/Gestures-and-Bindings.md | 37 +++++++++++------- docs/Home.md | 59 +++++++++++++++-------------- docs/Launcher-Settings.md | 49 ++++++++++++------------ docs/Work-Profile.md | 3 +- 7 files changed, 111 insertions(+), 85 deletions(-) diff --git a/docs/Building-from-Source.md b/docs/Building-from-Source.md index 3c2ef1f..fc8de3d 100644 --- a/docs/Building-from-Source.md +++ b/docs/Building-from-Source.md @@ -1,4 +1,6 @@ -# Using the command line +# Building from Source + +## Using the command line Install JDK 17 and the Android SDK. Make sure that `JAVA_HOME` and `ANDROID_HOME` are set correctly. @@ -13,6 +15,7 @@ cd Launcher This will create an apk file at `app/build/outputs/apk/default/release/app-default-release-unsigned.apk`. Note that you need to sign it: + ```bash apksigner sign --ks "$YOUR_KEYSTORE" \ --ks-key-alias "$YOUR_ALIAS" \ @@ -29,15 +32,14 @@ apksigner sign --ks "$YOUR_KEYSTORE" \ See [this guide](https://developer.android.com/build/building-cmdline) for further instructions. - -# Using Android Studio +## Using Android Studio Install [Android Studio](https://developer.android.com/studio), import this project and build it. See [this guide](https://developer.android.com/studio/run) for further instructions. How to -# CI Pipeline +## CI Pipeline The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds. > Note: These builds are *not* signed. diff --git a/docs/Changes-from-Finns-Launcher.md b/docs/Changes-from-Finns-Launcher.md index cb290a0..f08678d 100644 --- a/docs/Changes-from-Finns-Launcher.md +++ b/docs/Changes-from-Finns-Launcher.md @@ -1,17 +1,17 @@ -# Notable changes compared to [Finn's Launcher][original-repo]: +# Notable changes compared to Finn's Launcher -µLauncher is a fork of [finnmglas's app Launcher][original-repo]. +µLauncher is a fork of [finnmglas's app Launcher](https://github.com/finnmglas/Launcher). Here is an incomplete list of changes: - - Additional gestures: - - Back - - V,Λ,<,> - - Edge gestures: There is a setting to allow distinguishing swiping at the edges of the screen from swiping in the center. + - Back + - V,Λ,<,> + - Edge gestures: There is a setting to allow distinguishing swiping at the edges of the screen from swiping in the center. + - Compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. - Compatible with [private space](https://source.android.com/docs/security/features/private-space) - Option to rename apps @@ -23,18 +23,20 @@ The decision to create a hard fork was made two years later.--> - The home button now works as expected. - Improved gesture detection. -### Visual +## Visual + - This app uses the system wallpaper instead of a custom solution. - The font has been changed to [Hack][hack-font], other fonts can be selected. - Font Awesome Icons were replaced by Material icons. - The gear button on the home screen was removed. A smaller button is show at the top right when necessary. +## Search -### Search - The search algorithm was modified to prefer matches at the beginning of the app name, i.e. when searching for `"te"`, `"termux"` is sorted before `"notes"`. - The search bar was moved to the bottom of the screen. -### Technical +## Technical + - Improved gesture detection. - Different apps set as default. - Package name was changed to `de.jrpie.android.launcher` to avoid clashing with the original app. @@ -42,9 +44,10 @@ The decision to create a hard fork was made two years later.--> - Fixed some bugs - Some refactoring - The complete list of changes can be viewed [here](https://github.com/jrpie/launcher/compare/340ee731...master). --- - [original-repo]: https://github.com/finnmglas/Launcher - [hack-font]: https://sourcefoundry.org/hack/ + +[original-repo]: https://github.com/finnmglas/Launcher + +[hack-font]: https://sourcefoundry.org/hack/ diff --git a/docs/Contributing.md b/docs/Contributing.md index bd56a7a..cc79e87 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -1,10 +1,17 @@ -# Ways to contribute +# Contributing + +## Ways to contribute - Found a **bug** or have an idea for a **new feature**? [Join the chat](https://s.jrpie.de/launcher-chat) or open an [issue](https://github.com/jrpie/Launcher/issues/). + > Please note that I work on this project in my free time. Thus I might not respond immediately and not all ideas will be implemented. + - Implement a new feature yourself: + 1. [Fork](https://github.com/jrpie/launcher/fork) this repository. 2. Create a **new branch** named *feature/\* or *fix/\* and commit your changes. 3. Open a new pull request. + - Add or improve [translations](https://toolate.othing.xyz/projects/jrpie-launcher/). -translation status \ No newline at end of file + +![translation status](https://toolate.othing.xyz/widget/jrpie-launcher/launcher/horizontal-auto.svg) diff --git a/docs/Gestures-and-Bindings.md b/docs/Gestures-and-Bindings.md index d87a406..09ff172 100644 --- a/docs/Gestures-and-Bindings.md +++ b/docs/Gestures-and-Bindings.md @@ -1,41 +1,52 @@ -# Available Gestures -## Swipes +# Gestures and Bindings + +## Available Gestures + +### Swipes + - Up, down, left, or right. - Up, down, left, or right with two fingers. - Up or down on the left or right edge. - Left or right on the top or bottom edge. -## Taps +### Taps + - Tap on date or time. - Double tap. - Long tap. -## Tap-then-Swipes -tap then swipe up, down, left, or right +### Tap-then-Swipes + +Tap then swipe up, down, left, or right + +### Complex Gestures -## Complex Gestures - Draw <, >, V, or Λ. - Draw <, >, V, or Λ in reverse direction. -## Hardware Buttons as Gestures +### Hardware Buttons as Gestures + - Back button. - Volume up or down button. - *** +## Available Bindings -# Available Bindings Any of the above gestures can be bound to any of the following bindings. -## Launcher Bindings + +### Launcher Bindings + - Open µLauncher settings. - Open a list of all*, favorite, or private* apps. -## App Bindings +### App Bindings + - Launch an app. - Launch another Home Screen. -## Android / Device Bindings +### Android / Device Bindings + - Toggle private space lock. - Lock the screen. - Toggle the torch (flashlight) @@ -44,4 +55,4 @@ Any of the above gestures can be bound to any of the following bindings. - Open Notifications shade. - Open Quick Settings shade. -> \* excludes apps hidden by the user \ No newline at end of file +> \* excludes apps hidden by the user diff --git a/docs/Home.md b/docs/Home.md index 8ce432c..2cb6093 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,37 +1,38 @@ -# Welcome to the μLauncher wiki! +# Welcome to the μLauncher wiki + We're thrilled you're here and confident you'll love your new Android launcher! Check out this wiki to get familiar with your new app. -## What is μLauncher? +## What is μLauncher + µLauncher is an *minimal* and *distraction-free* Android home screen that lets you launch apps using *efficient* swipe gestures and button presses. This project is a fork of [finnmglas's app Launcher](https://github.com/finnmglas/Launcher). An incomplete list of changes can be found [here](https://github.com/wassupluke/Launcher/blob/master/docs/launcher.md). -## Where can I get μLauncher? -Get it on F-Droid -Get it on Accrescent -Get it on Obtainium -Get it on GitHub +## Where can I get μLauncher + +[![Get it on F-Droid](https://fdroid.gitlab.io/artwork/badge/get-it-on.png)](https://f-droid.org/packages/de.jrpie.android.launcher/) + +[![Get it on Accrescent](https://accrescent.app/badges/get-it-on.png)](https://accrescent.app/app/de.jrpie.android.launcher.accrescent) + +[![Get it on Obtainium](https://raw.githubusercontent.com/ImranR98/Obtainium/b1c8ac6f2ab08497189721a788a5763e28ff64cd/assets/graphics/badge_obtainium.png)](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/{%22id%22:%22de.jrpie.android.launcher%22,%22url%22:%22https://github.com/jrpie/Launcher%22,%22author%22:%22jrpie%22,%22name%22:%22%c2%b5Launcher%22,%22additionalSettings%22:%22{\%22apkFilterRegEx\%22:\%22release\%22,\%22invertAPKFilter\%22:false,\%22about\%22:\%22%c2%b5Launcher%20is%20a%20minimal%20home%20screen.\%22}%22}) + +[![Get it on GitHub](https://raw.githubusercontent.com/NeoApplications/Neo-Backup/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png)](https://github.com/jrpie/launcher/releases/latest) > You can also [get it on Google Play](https://play.google.com/store/apps/details?id=de.jrpie.android.launcher), but this is not recommend. ## Screenshots -μLauncher Home Screen screenshot -μLauncher Settings screenshot -μLauncher All Apps list view with icons screenshot -μLauncher Favorite Apps list view with icons screenshot -μLauncher Choose App to bind to gesture screenshot -μLauncher App options card from list view with icons screenshot -μLauncher All Apps list view without icons screenshot - \ No newline at end of file + +![μLauncher Home Screen screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg) + +![μLauncher Settings screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg) + +![μLauncher All Apps list view with icons screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg) + +![μLauncher Favorite Apps list view with icons screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg) + +![μLauncher Choose App to bind to gesture screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg) + +![μLauncher App options card from list view with icons screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg +) + +![μLauncher All Apps list view without icons screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/8.jpg) + + diff --git a/docs/Launcher-Settings.md b/docs/Launcher-Settings.md index ac665fa..1444c73 100644 --- a/docs/Launcher-Settings.md +++ b/docs/Launcher-Settings.md @@ -1,19 +1,20 @@ +# Launcher Settings + Tweaks and customizations can be made from within the Launcher Settings page. These settings let you change wallpapers, change colors and fonts, enable monochrome app icons, change the app drawer layout, and much more. In the following documentation, 'app drawer' will be used to refer to the 'All Apps' and 'Favorite Apps' views. +## Appearance -# Appearance +> ### Choose a wallpaper -### Choose a wallpaper Lets you change the wallpaper using a photos app, file explorer, or native wallpaper setting app. - -> ### Font +> ### Font (in-app font) -Set the font used within the app settings. This setting does not affect the date/time [home screen font](https://github.com/wassupluke/Launcher/wiki/Tweaks-and-Customizations/_edit#font-1). +Set the font used within the app settings. This setting does not affect the date/time home screen font. **type:** `dropdown` @@ -35,12 +36,11 @@ Remove coloring from all app icons. Can help decrease visual stimulus when enabl **type:** `toggle` +## Date & Time -# Date & Time +> ### Font (home screen) -> ### Font - -Set the home screen font for date and time. This setting does not affect the [app settings font](https://github.com/wassupluke/Launcher/wiki/Tweaks-and-Customizations/_edit#font). +Set the home screen font for date and time. This setting does not affect the in-app font. **type:** `dropdown` @@ -86,8 +86,7 @@ Place the current time above the current date on the home screen. **type:** `toggle` - -# Functionality +## Functionality > ### Launch search results @@ -134,20 +133,24 @@ Change how large a margin is used for detecting edge gestures. Shows the edge ma > ### Choose method for locking the screen There are two methods to lock the screen and unfortunately both have downsides. + 1. **`Device Admin`** - - Doesn't work with unlocking by fingerprint or face recognition. + + - Doesn't work with unlocking by fingerprint or face recognition. + 2. **`Accessibility Service`** - - Requires excessive privileges. - - μLauncher will use those privileges *only* for locking the screen. - - As a rule of thumb, it is [not recommended](https://android.stackexchange.com/questions/248171/is-it-safe-to-give-accessibility-permission-to-an-app) to grant access to accessibility services on a random app. Always review the [source code](https://github.com/jrpie/Launcher) before granting accessibility permissions so you familiarize yourself with what the code might do. - - On some devices, the start-up PIN will no longer be used for encrypting data after activating an accessibility service. + + - Requires excessive privileges. + - μLauncher will use those privileges *only* for locking the screen. + - As a rule of thumb, it is [not recommended](https://android.stackexchange.com/questions/248171/is-it-safe-to-give-accessibility-permission-to-an-app) to grant access to accessibility services on a random app. Always review the [source code](https://github.com/jrpie/Launcher) before granting accessibility permissions so you familiarize yourself with what the code might do. + - On some devices, the start-up PIN will no longer be used for encrypting data after activating an accessibility service. - This can be [reactivated](https://issuetracker.google.com/issues/37010136#comment36) afterwards. -**type:** `text buttons` + **type:** `text buttons` -**options:** `USE DEVICE ADMIN`,`USE ACCESSIBILITY SERVICE` + **options:** `USE DEVICE ADMIN`,`USE ACCESSIBILITY SERVICE` -# Apps +## Apps > ### Hidden apps @@ -187,8 +190,7 @@ Enable Z-A sorting of apps in the app drawer. Useful for keeping apps within eas **type:** `toggle` - -# Display +## Display > ### Rotate screen @@ -210,8 +212,7 @@ Remove the navigation bar from the home screen. Enabling this setting may make i **type:** `toggle` - -# Additional Settings +## Additional Settings > ### App Drawer Long Press on App @@ -219,4 +220,4 @@ Access additional per-app details and settings. To use, open the app drawer and **type:** `dropdown` -**options:** `App Info`,`Add to favorites`,`Hide`,`Rename`,`Uninstall` \ No newline at end of file +**options:** `App Info`,`Add to favorites`,`Hide`,`Rename`,`Uninstall` diff --git a/docs/Work-Profile.md b/docs/Work-Profile.md index 38c6598..718fe28 100644 --- a/docs/Work-Profile.md +++ b/docs/Work-Profile.md @@ -1,3 +1,4 @@ # Android Enterprise Work Profile -µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. \ No newline at end of file +µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. + From 6d271970fe082a3aec726dfe4f6f418966d376ba Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 30 Apr 2025 22:39:03 -0500 Subject: [PATCH 11/34] fix links --- docs/Changes-from-Finns-Launcher.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Changes-from-Finns-Launcher.md b/docs/Changes-from-Finns-Launcher.md index f08678d..30ae32b 100644 --- a/docs/Changes-from-Finns-Launcher.md +++ b/docs/Changes-from-Finns-Launcher.md @@ -48,6 +48,6 @@ The complete list of changes can be viewed [here](https://github.com/jrpie/launc --- -[original-repo]: https://github.com/finnmglas/Launcher +\[original-repo\]: [https://github.com/finnmglas/Launcher](https://github.com/finnmglas/Launcher) -[hack-font]: https://sourcefoundry.org/hack/ +\[hack-font\]: [https://sourcefoundry.org/hack/](https://sourcefoundry.org/hack/) From 106254664df7a9d90328f25c077070a3a951c532 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 9 May 2025 21:31:58 +0200 Subject: [PATCH 12/34] fix #151 - move alpha slider to the top to clarify that the format is ARGB not RGBA --- .../main/res/layout/dialog_choose_color.xml | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/dialog_choose_color.xml b/app/src/main/res/layout/dialog_choose_color.xml index 90d13c3..dc7ddca 100644 --- a/app/src/main/res/layout/dialog_choose_color.xml +++ b/app/src/main/res/layout/dialog_choose_color.xml @@ -24,6 +24,21 @@ android:layout_width="match_parent" android:layout_height="10dp" /> + + + + + + - - - - - \ No newline at end of file From 49785e66f2e11fb7761ad4293ced78d140287bd1 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 9 May 2025 23:54:23 +0200 Subject: [PATCH 13/34] improved documentation --- .scripts/release.sh | 19 ++++- app/src/main/res/values/strings.xml | 4 +- docs/Contributing.md | 17 ----- docs/Gestures-and-Bindings.md | 58 --------------- docs/Work-Profile.md | 4 -- docs/actions-and-gestures.md | 71 +++++++++++++++++++ docs/{Building-from-Source.md => build.md} | 2 +- ...from-Finns-Launcher.md => changes-fork.md} | 5 +- docs/contributing.md | 24 +++++++ docs/{Home.md => home.md} | 15 ++-- docs/profiles.md | 21 ++++++ docs/{Launcher-Settings.md => settings.md} | 34 ++++++--- 12 files changed, 173 insertions(+), 101 deletions(-) delete mode 100644 docs/Contributing.md delete mode 100644 docs/Gestures-and-Bindings.md delete mode 100644 docs/Work-Profile.md create mode 100644 docs/actions-and-gestures.md rename docs/{Building-from-Source.md => build.md} (92%) rename docs/{Changes-from-Finns-Launcher.md => changes-fork.md} (89%) create mode 100644 docs/contributing.md rename docs/{Home.md => home.md} (82%) create mode 100644 docs/profiles.md rename docs/{Launcher-Settings.md => settings.md} (72%) diff --git a/.scripts/release.sh b/.scripts/release.sh index f207c87..dc6959d 100755 --- a/.scripts/release.sh +++ b/.scripts/release.sh @@ -1,9 +1,25 @@ #!/bin/bash + +# This script builds all variants of µLauncher to create a release, namely: +# - app-release.apk (GitHub release; used by F-Droid for reproducible builds) +# - launcher-accrescent.apks (Accrescent) +# - app-release.aab (Play Store) + +# This is only intended to work on my (@jrpie) computer. +# To use this script for building a fork you need to: +# - install bundletool.jar and +# - create a keystore and modify the variables below accordingly + export JAVA_HOME="/usr/lib/jvm/java-21-openjdk/" OUTPUT_DIR="$HOME/launcher-release" BUILD_TOOLS_DIR="$HOME/Android/Sdk/build-tools/35.0.0" + +# keystore for the default release KEYSTORE="$HOME/data/keys/launcher_jrpie.jks" +# keystore for the default accrescent release KEYSTORE_ACCRESCENT="$HOME/data/keys/launcher_jrpie_accrescent.jks" + +# keepassxc-password is a custom script to fetch passwords from my password manager KEYSTORE_PASS=$(keepassxc-password "android_keys/launcher") KEYSTORE_ACCRESCENT_PASS=$(keepassxc-password "android_keys/launcher-accrescent") @@ -11,12 +27,11 @@ if [[ $(git status --porcelain) ]]; then echo "There are uncommitted changes." read -p "Continue anyway? (y/n) " -n 1 -r - echo # (optional) move to a new line + echo if ! [[ $REPLY =~ ^[Yy]$ ]] then exit 1 fi - fi rm -rf "$OUTPUT_DIR" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d29128..46fce3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -159,9 +159,9 @@ Functionality - Double swipe actions + Double swipe gestures Swipe with two fingers - Edge swipe actions + Edge swipe gestures Swipe at the edge of the screen Edge width Launch search results diff --git a/docs/Contributing.md b/docs/Contributing.md deleted file mode 100644 index cc79e87..0000000 --- a/docs/Contributing.md +++ /dev/null @@ -1,17 +0,0 @@ -# Contributing - -## Ways to contribute - -- Found a **bug** or have an idea for a **new feature**? [Join the chat](https://s.jrpie.de/launcher-chat) or open an [issue](https://github.com/jrpie/Launcher/issues/). - - > Please note that I work on this project in my free time. Thus I might not respond immediately and not all ideas will be implemented. - -- Implement a new feature yourself: - - 1. [Fork](https://github.com/jrpie/launcher/fork) this repository. - 2. Create a **new branch** named *feature/\* or *fix/\* and commit your changes. - 3. Open a new pull request. - -- Add or improve [translations](https://toolate.othing.xyz/projects/jrpie-launcher/). - -![translation status](https://toolate.othing.xyz/widget/jrpie-launcher/launcher/horizontal-auto.svg) diff --git a/docs/Gestures-and-Bindings.md b/docs/Gestures-and-Bindings.md deleted file mode 100644 index 09ff172..0000000 --- a/docs/Gestures-and-Bindings.md +++ /dev/null @@ -1,58 +0,0 @@ -# Gestures and Bindings - -## Available Gestures - -### Swipes - -- Up, down, left, or right. -- Up, down, left, or right with two fingers. -- Up or down on the left or right edge. -- Left or right on the top or bottom edge. - -### Taps - -- Tap on date or time. -- Double tap. -- Long tap. - -### Tap-then-Swipes - -Tap then swipe up, down, left, or right - -### Complex Gestures - -- Draw <, >, V, or Λ. -- Draw <, >, V, or Λ in reverse direction. - -### Hardware Buttons as Gestures - -- Back button. -- Volume up or down button. - -*** - -## Available Bindings - -Any of the above gestures can be bound to any of the following bindings. - -### Launcher Bindings - -- Open µLauncher settings. -- Open a list of all*, favorite, or private* apps. - -### App Bindings - -- Launch an app. -- Launch another Home Screen. - -### Android / Device Bindings - -- Toggle private space lock. -- Lock the screen. -- Toggle the torch (flashlight) -- Raise or lower volume. -- Skip to previous or next audio track. -- Open Notifications shade. -- Open Quick Settings shade. - -> \* excludes apps hidden by the user diff --git a/docs/Work-Profile.md b/docs/Work-Profile.md deleted file mode 100644 index 718fe28..0000000 --- a/docs/Work-Profile.md +++ /dev/null @@ -1,4 +0,0 @@ -# Android Enterprise Work Profile - -µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. - diff --git a/docs/actions-and-gestures.md b/docs/actions-and-gestures.md new file mode 100644 index 0000000..f5d831f --- /dev/null +++ b/docs/actions-and-gestures.md @@ -0,0 +1,71 @@ +# Gestures and Actions + +µLauncher's central mechanism for accessing important functionality quickly +is to bind actions (e.g. launching an app) to gestures (e.g. swiping up). +These bindings can be configured in µLauncher Settings > ACTIONS. + + +## Available Gestures + +### Swipes + +- Basic swipes: Swipe up, down, left, or right +- Double swipes: Swipe up, down, left, or right with two fingers +- Edge swipes: + - Swipe up or down on the left or right edge + - Swipe left or right on the top or bottom edge + + The size of the edges is configurable in settings. + For a swipe to be detected as an edge swipe, the finger must not leave the respective edge region while swiping. + +### Taps + +- Tap on date or time +- Double tap +- Long click + +### Tap-then-Swipes + +- Tap then swipe up, down, left, or right + + To execute these gesture consistently, it is helpful to think of them as double taps, + where the finger stays on the screen after the second tap and then does a swipe. + The swipe must start very shortly after the tap ended. + +### Complex Gestures + +- Draw <, >, V, or Λ +- Draw <, >, V, or Λ in reverse direction + +### Hardware Buttons as Gestures + +- Back button (or back gesture if gesture navigation is enabled) +- Volume buttons + +*** + +## Available Actions + +To any of the available gestures, one of the following actions can be bound: + +- Launch an app (or a pinned shortcut) +- Open a widget panel. + Widget panels can hold widgets that are not needed on the home screen itself. + They can be created and managed in µLauncher Settings > Manage Widget Panels +- Open a list of all, favorite, or private apps (hidden apps are excluded). + Actions related to private space are only shown if private space is set up on the device. + µLauncher's settings can be accessed from those lists. + If private space is set up, an icon to (un)lock it is shown on the top right. +- Open µLauncher's settings +- Toggle private space lock +- Lock the screen: This allows to lock the screen. + There are two mechanisms by which the screen can be locked, accessibility service and device admin. +- Toggle the flashlight +- Raise, lower or adjust volume +- Play or pause media playback +- Skip to previous or next audio track +- Open notifications panel: Might be useful if the top of your screen is broken. +- Open quick settings panel: Why swipe down twice? +- Open [recent apps](https://developer.android.com/guide/components/activities/recents): Requires accessibility service. Can be used as a workaround for a Android bug. +- Launch another home screen: Allows using another installed home screen temporarily. +- Do nothing: Just prevents showing the message saying that no action is bound to this gesture. diff --git a/docs/Building-from-Source.md b/docs/build.md similarity index 92% rename from docs/Building-from-Source.md rename to docs/build.md index fc8de3d..1ffc338 100644 --- a/docs/Building-from-Source.md +++ b/docs/build.md @@ -42,4 +42,4 @@ for further instructions. How to ## CI Pipeline The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds. -> Note: These builds are *not* signed. +> Note: These builds are *not* signed. They are in built in debug mode and only suitable for testing. diff --git a/docs/Changes-from-Finns-Launcher.md b/docs/changes-fork.md similarity index 89% rename from docs/Changes-from-Finns-Launcher.md rename to docs/changes-fork.md index 30ae32b..8efc965 100644 --- a/docs/Changes-from-Finns-Launcher.md +++ b/docs/changes-fork.md @@ -14,12 +14,15 @@ The decision to create a hard fork was made two years later.--> - Compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. - Compatible with [private space](https://source.android.com/docs/security/features/private-space) +- Support for [app widgets](https://developer.android.com/develop/ui/views/appwidgets/overview) +- Support for [pinned shortcuts](https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts) - Option to rename apps - Option to hide apps - Favorite apps - New actions: - Toggle Torch - Lock screen + - Open a widget panel - The home button now works as expected. - Improved gesture detection. @@ -48,6 +51,6 @@ The complete list of changes can be viewed [here](https://github.com/jrpie/launc --- -\[original-repo\]: [https://github.com/finnmglas/Launcher](https://github.com/finnmglas/Launcher) +\[original-repo\]: [https://github.com/finnmglas/Launcher](https://github.com/finnmglas/Launcher) \[hack-font\]: [https://sourcefoundry.org/hack/](https://sourcefoundry.org/hack/) diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..8e9de53 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,24 @@ +# Contributing + +There are several ways to contribute to this app: +* You can add or improve [translations][toolate]. +
translation status +* If you found a bug or have an idea for a new feature you can [join the chat][chat] or open an [issue][issues]. + + > Please note that I work on this project in my free time. Thus I might not respond immediately and not all ideas will be implemented. + +* You can implement a new feature yourself: + - Create a [fork][fork] of this repository. + - Create a new branch named `feature/` or `fix/` and commit your changes. + - Open a new pull request. + + +See [build.md](build.md) for instructions how to build this project. +The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds. + + +--- + [fork]: https://github.com/jrpie/Launcher/fork/ + [issues]: https://github.com/jrpie/Launcher/issues/ + [chat]: https://s.jrpie.de/launcher-chat + [toolate]: https://toolate.othing.xyz/projects/jrpie-launcher/ diff --git a/docs/Home.md b/docs/home.md similarity index 82% rename from docs/Home.md rename to docs/home.md index 2cb6093..6ddf2b5 100644 --- a/docs/Home.md +++ b/docs/home.md @@ -1,12 +1,12 @@ -# Welcome to the μLauncher wiki +# Welcome to the μLauncher Documentation -We're thrilled you're here and confident you'll love your new Android launcher! Check out this wiki to get familiar with your new app. +## What is μLauncher? -## What is μLauncher +µLauncher is an *minimal* and *distraction-free* Android home screen that lets you launch apps using [swipe gestures and button presses](docs/actions-and-gestured.md). -µLauncher is an *minimal* and *distraction-free* Android home screen that lets you launch apps using *efficient* swipe gestures and button presses. This project is a fork of [finnmglas's app Launcher](https://github.com/finnmglas/Launcher). An incomplete list of changes can be found [here](https://github.com/wassupluke/Launcher/blob/master/docs/launcher.md). +This project is a fork of [finnmglas's app Launcher](https://github.com/finnmglas/Launcher). An incomplete list of changes can be found [here](https://github.com/wassupluke/Launcher/blob/master/docs/launcher.md). -## Where can I get μLauncher +## Where can I get μLauncher? [![Get it on F-Droid](https://fdroid.gitlab.io/artwork/badge/get-it-on.png)](https://f-droid.org/packages/de.jrpie.android.launcher/) @@ -18,6 +18,11 @@ We're thrilled you're here and confident you'll love your new Android launcher! > You can also [get it on Google Play](https://play.google.com/store/apps/details?id=de.jrpie.android.launcher), but this is not recommend. + +## How can I contribute? + +See [docs/contribute](docs/contribute.md) + ## Screenshots ![μLauncher Home Screen screenshot](https://github.com/jrpie/launcher/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg) diff --git a/docs/profiles.md b/docs/profiles.md new file mode 100644 index 0000000..d9eaf52 --- /dev/null +++ b/docs/profiles.md @@ -0,0 +1,21 @@ +# Work Profile + +µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. +Apps from the work profile are shown in the usual app list. + + +# Private Space + +µLauncher is compatible with [private space](https://source.android.com/docs/security/features/private-space). + + +The private space can be (un)locked by a dedicated action. + +It is configurable whether apps from private space are + +1. shown in the usual app list + (in this case, (un)locking is accessible through a lock icon on the top right of the app drawer) + or +2. only shown in a separate list. + + diff --git a/docs/Launcher-Settings.md b/docs/settings.md similarity index 72% rename from docs/Launcher-Settings.md rename to docs/settings.md index 1444c73..f033f62 100644 --- a/docs/Launcher-Settings.md +++ b/docs/settings.md @@ -4,13 +4,14 @@ Tweaks and customizations can be made from within the Launcher Settings page. These settings let you change wallpapers, change colors and fonts, enable monochrome app icons, change the app drawer layout, and much more. -In the following documentation, 'app drawer' will be used to refer to the 'All Apps' and 'Favorite Apps' views. +In the following documentation, 'app drawer' will be used to refer to the 'All Apps', 'Favorite Apps' and 'Private Space' views. ## Appearance > ### Choose a wallpaper -Lets you change the wallpaper using a photos app, file explorer, or native wallpaper setting app. +This triggers Android's mechanism to change the wallpaper using a photos app, file explorer, or native wallpaper setting app. +µLauncher uses the system-wide wallpaper, i.e. this change also affects other launchers. > ### Font (in-app font) @@ -92,11 +93,15 @@ Place the current time above the current date on the home screen. Launches any app that matches user keyboard input when no other apps match. -As you type inside the app drawer, the app narrows down the list of apps shown based on the app title matching your text input. For example, if you type, `a`, the app list narrows to any apps with a title containing the letter `a`. Continuing the example, if you then follow your `a` with the letter `m`, the list now shows only apps containing the letter combination `am` in that order. If the only app matching this combination was, for example, `Amazon`, simply typing `am` in the app drawer would immediately launch the `Amazon` app for you. +As you type inside the app drawer, the app narrows down the list of apps shown based on the app title matching your text input. +With the 'launch search results' setting, once only one matching app remains, it is launched immediately. +Usually it suffices to type two or three characters the single out the desired app. -This feature becomes more powerful when combined with [renaming](https://github.com/wassupluke/Launcher/wiki/Launcher-Settings/_edit#additional-settings) apps, effectively letting you define custom app names that could be considered 'aliases' or shortcuts. For instance, if you wanted to "bind" the keyboard input `gh` to open your `GitHub` app, you could rename `GitHub` to `GitHub gh`, `gh GitHub`, or simply `gh`. Assuming no other installed apps have the `gh` combination of letters in them, opening the app drawer and typing `gh` would immediately launch your `GitHub` app. +This feature becomes more powerful when combined with [renaming](#additional-settings) apps, effectively letting you define custom app names that could be considered 'aliases' or shortcuts. +For instance, if you want the keyboard input `gh` to open your `GitHub` app, you could rename `GitHub` to `GitHub gh`, `gh GitHub`, or simply `gh`. +Assuming no other installed apps have the `gh` combination of letters in them, opening the app drawer and typing `gh` would immediately launch your `GitHub` app. -Press space to temporarily disable this feature and allow text entry without prematurely launching an app. Useful when combined with the [Search the web](https://github.com/wassupluke/Launcher/wiki/Launcher-Settings/_edit#search-the-web) feature. +Press space to temporarily disable this feature and allow text entry without prematurely launching an app. Useful when combined with the [Search the web](#search-the-web) feature. **type:** `toggle` @@ -112,15 +117,15 @@ Automatically open the keyboard when the app drawer is opened. **type:** `toggle` -> ### Double swipe actions +> ### Double swipe gestures -Enable double swipe (two finger) actions as bindable gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. +Enable double swipe (two finger) gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. **type:** `toggle` -> ### Edge swipe actions +> ### Edge swipe gestures -Enable edge swipe (near edges of screen) actions as bindable gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. +Enable edge swipe (near edges of screen) gestures in launcher settings. Does not erase gesture bindings if accidentally turned off. **type:** `toggle` @@ -167,6 +172,7 @@ Reduces redundancy and tidies up app drawer. > ### Hide paused apps Remove paused apps from the app drawer. +For example an app belonging to the work profile is paused when the work profile is inactive. **type:** `toggle` @@ -178,7 +184,12 @@ Remove private space from app drawer. > ### Layout of app list -Change how the apps are displayed when accessing the app drawer. By `Default`, all apps in the drawer will show in a vertically-scrolled list with their app icon and title. `Text` removes the app icons, shows only app titles in the drawer as a vertically-scrolled list. `Grid` shows apps with their app icon and title in a grid layout. +Changes how the apps are displayed when accessing the app drawer. + +- `Default`: All apps in the drawer will show in a vertically-scrolled list with their app icon and title. +- `Text`: Removes the app icons, shows only app titles in the drawer as a vertically-scrolled list. + Work profile and private space apps are distinguished by a different label instead of a badge. +- `Grid`: Shows apps with their app icon and title in a grid layout. **type:** `dropdown` @@ -186,7 +197,8 @@ Change how the apps are displayed when accessing the app drawer. By `Default`, a > ### Reverse the app list -Enable Z-A sorting of apps in the app drawer. Useful for keeping apps within easier reach from the keyboard. +Enable reverse alphabetical sorting of apps in the app drawer. +Useful for keeping apps within easier reach from the keyboard. **type:** `toggle` From 24e90deb627f76c2d528fbc12510ea4825dc9a98 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Sat, 10 May 2025 00:52:56 +0200 Subject: [PATCH 14/34] improve widget management --- .../de/jrpie/android/launcher/Application.kt | 5 --- .../manage/ManageWidgetPanelsActivity.kt | 1 - .../widgets/manage/ManageWidgetsActivity.kt | 5 +-- .../ui/widgets/manage/WidgetManagerView.kt | 32 ++++++++++++------- .../jrpie/android/launcher/widgets/Widget.kt | 3 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt index 775621c..3c2e3bc 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -22,8 +22,6 @@ import de.jrpie.android.launcher.apps.isPrivateSpaceLocked import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion import de.jrpie.android.launcher.preferences.resetPreferences -import de.jrpie.android.launcher.widgets.LauncherWidgetProvider -import de.jrpie.android.launcher.widgets.Widget import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -34,7 +32,6 @@ const val APP_WIDGET_HOST_ID = 42; class Application : android.app.Application() { val apps = MutableLiveData>() - val widgets = MutableLiveData>() val privateSpaceLocked = MutableLiveData() lateinit var appWidgetHost: AppWidgetHost lateinit var appWidgetManager: AppWidgetManager @@ -101,8 +98,6 @@ class Application : android.app.Application() { customAppNames = LauncherPreferences.apps().customNames() } else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) { loadApps() - } else if (pref == LauncherPreferences.widgets().keys().widgets()) { - widgets.postValue(LauncherPreferences.widgets().widgets() ?: setOf()) } } 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 index b18852f..cb57fda 100644 --- 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 @@ -49,7 +49,6 @@ class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { }) } binding.manageWidgetPanelsRecycler.apply { - // improve performance (since content changes don't change the layout size) setHasFixedSize(true) layoutManager = viewManager adapter = viewAdapter 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 d191b70..665a851 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 @@ -34,10 +34,11 @@ class ManageWidgetsActivity : Activity(), UIObject { var panelId: Int = WidgetPanel.HOME.id + + // We can't observe the livedata because this is not an AppCompatActivity 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(R.id.manage_widgets_container).updateWidgets(this, LauncherPreferences.widgets().widgets() ) @@ -63,7 +64,7 @@ class ManageWidgetsActivity : Activity(), UIObject { findViewById(R.id.manage_widgets_container).let { it.widgetPanelId = panelId - it.updateWidgets(this, (application as Application).widgets.value) + it.updateWidgets(this, LauncherPreferences.widgets().widgets()) } } 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 7a355f7..f6c6c0d 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 @@ -19,6 +19,7 @@ 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.GRID_SIZE import de.jrpie.android.launcher.widgets.Widget import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition @@ -47,21 +48,27 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe - 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) + enum class EditMode(val resize: (dx: Int, dy: Int, screenWidth: Int, screenHeight: Int, rect: Rect) -> Rect) { + MOVE({ dx, dy, sw, sh, rect -> + val cdx = dx.coerceIn(-rect.left, sw - rect.right) + val cdy = dy.coerceIn(-rect.top, sh - rect.bottom) + Rect(rect.left + cdx, rect.top + cdy, rect.right + cdx, rect.bottom + cdy) }), - TOP({ dx, dy, rect -> - Rect(rect.left, min(rect.top + dy, rect.bottom - 200), rect.right, rect.bottom) + TOP({ dx, dy, sw, sh, rect -> + val cdy = dy.coerceIn(-rect.top, rect.bottom - rect.top - (2 * sh / GRID_SIZE) + 5) + Rect(rect.left, rect.top + cdy, rect.right, rect.bottom) }), - BOTTOM({ dx, dy, rect -> - Rect(rect.left, rect.top, rect.right, max(rect.top + 200, rect.bottom + dy)) + BOTTOM({ dx, dy, sw, sh, rect -> + val cdy = dy.coerceIn((2 * sh / GRID_SIZE) + 5 + rect.top - rect.bottom, sh - rect.bottom) + Rect(rect.left, rect.top, rect.right, rect.bottom + cdy) }), - LEFT({ dx, dy, rect -> - Rect(min(rect.left + dx, rect.right - 200), rect.top, rect.right, rect.bottom) + LEFT({ dx, dy, sw, sh, rect -> + val cdx = dx.coerceIn(-rect.left, rect.right - rect.left - (2 * sw / GRID_SIZE) + 5) + Rect(rect.left + cdx, rect.top, rect.right, rect.bottom) }), - RIGHT({ dx, dy, rect -> - Rect(rect.left, rect.top, max(rect.left + 200, rect.right + dx), rect.bottom) + RIGHT({ dx, dy, sw, sh, rect -> + val cdx = dx.coerceIn((2 * sw / GRID_SIZE) + 5 + rect.left - rect.right, sw - rect.right) + Rect(rect.left, rect.top, rect.right + cdx, rect.bottom) }), } @@ -120,6 +127,7 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe val absoluteNewPosition = view.mode?.resize( distanceX.toInt(), distanceY.toInt(), + width, height, start ) ?: return true val newPosition = WidgetPosition.fromAbsoluteRect( @@ -162,7 +170,7 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe if (widgets == null) { return } - children.mapNotNull { it as? WidgetOverlayView }.forEach { removeView(it) } + children.filter { it is WidgetOverlayView }.forEach { removeView(it) } widgets.filter { it.panelId == widgetPanelId }.forEach { widget -> WidgetOverlayView(activity).let { 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 dbe667b..e31250b 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 @@ -57,7 +57,8 @@ sealed class Widget { return Json.decodeFromString(serialized) } fun byId(context: Context, id: Int): Widget? { - return (context.applicationContext as Application).widgets.value?.firstOrNull { + // TODO: do some caching + return LauncherPreferences.widgets().widgets().firstOrNull() { it.id == id } } From f5b8953601016816fad92042d5d95c55709db756 Mon Sep 17 00:00:00 2001 From: Sven van de Lagemaat Date: Mon, 14 Apr 2025 08:14:03 +0000 Subject: [PATCH 15/34] Added translation using Weblate (Dutch) --- app/src/main/res/values-nl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-nl/strings.xml diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..a6b3dae --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From ba2c117eb5bbd88a186d0b745011c21679cdc58a Mon Sep 17 00:00:00 2001 From: Sven van de Lagemaat Date: Mon, 14 Apr 2025 08:16:32 +0000 Subject: [PATCH 16/34] Translated using Weblate (Dutch) Currently translated at 14.2% (3 of 21 strings) Translation: jrpie-Launcher/metadata Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/nl/ --- fastlane/metadata/android/nl-NL/full_description.txt | 4 ++++ fastlane/metadata/android/nl-NL/short_description.txt | 1 + fastlane/metadata/android/nl-NL/title.txt | 1 + 3 files changed, 6 insertions(+) create mode 100644 fastlane/metadata/android/nl-NL/full_description.txt create mode 100644 fastlane/metadata/android/nl-NL/short_description.txt create mode 100644 fastlane/metadata/android/nl-NL/title.txt diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt new file mode 100644 index 0000000..3040dfd --- /dev/null +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -0,0 +1,4 @@ +µLauncher is een thuisscherm die je andere apps laat starten met gebruik van veeg gebaren en knoppen indrukken. +Het is minimalistisch, efficiënt en vrij van afleiding. + +Je thuisscherm laat alleen de datum, tijd en achtergrond zien. diff --git a/fastlane/metadata/android/nl-NL/short_description.txt b/fastlane/metadata/android/nl-NL/short_description.txt new file mode 100644 index 0000000..43e1dbf --- /dev/null +++ b/fastlane/metadata/android/nl-NL/short_description.txt @@ -0,0 +1 @@ +Een afleidingsvrije, minimalistisch thuisscherm voor Android. diff --git a/fastlane/metadata/android/nl-NL/title.txt b/fastlane/metadata/android/nl-NL/title.txt new file mode 100644 index 0000000..4305604 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/title.txt @@ -0,0 +1 @@ +µLauncher From b9fa079048b5e24a4efd73c7b9b5d0338fbcd46b Mon Sep 17 00:00:00 2001 From: Vladi69 Date: Sat, 19 Apr 2025 09:45:17 +0000 Subject: [PATCH 17/34] Translated using Weblate (Italian) Currently translated at 87.9% (227 of 258 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/ --- app/src/main/res/values-it/strings.xml | 201 +++++++++++++++---------- 1 file changed, 121 insertions(+), 80 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 318ad43..33ab7dd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2,15 +2,15 @@ Scorri verso destra sul bordo inferiore dello schermo Aspetto - Scegliere + Imposta app Tema - Questo launcher è progettato per essere minimale, efficiente e privo di distrazioni. Non contiene pagamenti, pubblicità o servizi di tracciamento. + μLauncher is designed to be minimal, efficient and free of distraction. \n\nIt contains no ads and collects no data. Predefinito - Non mostrare applicazioni collegate a gesti nella lista delle app + Non mostrare applicazioni abbinate a gesti nella lista delle app Testo Impostare µLauncher come servizio accessibilità permette l\'azione di blocco dello schermo. Si precisa che sono richiesti permessi eccessivi. Non si dovrebbe mai concedere con leggerezza permessi ad alcuna app. µLauncher utilizza i servizi di accessibilità esclusivamente per il blocco dello schermo. Puoi verificare il codice sorgente. Si segnala che il blocco schermo si può attivare anche concedendo a µLauncher i permessi di amministratore, tuttavia tale metodo non funziona con lo sblocco tramite impronta o riconoscimento facciale. - Sans serif (senza grazie) - Trascina due dita dal basso verso l\'alto + Sans serif + Scorri con due dita verso l\'alto Negozio non trovato Griglia


Puoi cambiare le tue scelte in seguito nelle impostazioni. ]]>
- Impossibile aprire l\'applicazione - Desideri modificare le impostazioni? - Apri le impostazioni per abbinare un\'azione a questo gesto + Impossibile avviare l\'app + Modificare le impostazioni? + Apri impostazioni per associare un\'azione a questo gesto Impostazioni Launcher Meta Scorri verso il basso con due dita Sinistra - Scorrere verso sinistra - Due dita verso sinistra - Scorrere verso sinistra con due dita + Scorri verso sinistra + Due dita a sx + Scorri verso sinistra con due dita Destra Scorri verso destra - Due dita verso destra - Scorri a destra con due dita + Due dita verso dx + Scorri verso destra con due dita Destra (in alto) - Scorri verso destra sul bordo superiore dello schermo + Scorri verso destra sul bordo alto dello schermo Scorri verso sinistra sul bordo inferiore dello schermo Scorri verso sinistra sul bordo superiore dello schermo - Verso l\'alto (lato sinistro) - Verso l\'alto - Striscia il dito dal basso verso l\'alto - Verso il basso - Trascina due dita dall\'alto verso il basso - Trascina un dito dall\'alto verso il basso - Destra (bordo inferiore) - Sinistra (bordo inferiore) - Sinistra (bordo superiore) + Su (bordo sinistro) + Su + Scorri su + Giù + Scorri con due dita giù + Scorri verso il basso + Destra (in basso) + Sinistra (in basso) + Sinistra (in alto) Scorri verso l\'alto sul bordo sinistro dello schermo - Alto (lato destro) + Su (bordo destro) Scorri verso l\'alto sul bordo destro dello schermo - Basso (bordo sinistro) + Giù (bordo sinistro) Scorri verso il basso sul bordo sinistro dello schermo - Basso (Lato destro) - Scorri verso il basso sul lato destro dello schermo - Aumenta il volume - Doppio click - Doppio click in un\'area vuota - Tocco prolungato - Tocco prolungato su un\'area vuota + Giù (bordo destro) + Scorri verso il basso sul bordo destro dello schermo + Volume + + Doppio tap + Doppio tap in spazio vuoto + Tap lungo + Tap lungo in spazio vuoto Data - Premi sulla data + Tap sulla data Ora - Premi sull\'ora - Premi il pulsante di aumento del volume - Riduci il volume - Premi il pulsante per ridurre il volume - Installa le applicazioni + Tap sull\'ora + Premi il pulsante Volume + + Volume - + Premi il pulsante Volume - + Installa apps Icone monocromatiche - Mostra l\'ora - Mostra la data - Usa il formato ora locale + Mostra orario + Mostra data + Usa formato data locale Mostra i secondi Scorri con due dita - Azioni a scorrimento con due dita + Azioni di scorrimento a due dita Apri il risultato della ricerca - Azioni a scorrimento ai lati dello schermo - Scorri sul lato dello schermo - Larghezza margine laterale - Vedi il codice sorgente + Azioni di scorrimento sui bordi dello schermo + Scorri sui bordi dello schermo + Larghezza bordo + Vai al codice sorgente Tutte le applicazioni Applicazioni preferite - Musica: passa alla traccia successiva - Musica: riduci il volume - Musica: torna alla traccia precedente - Musica: Aumenta il volume + Musica: traccia successiva + Volume - + Musica: traccia precedente + Volume + Annulla Impostazioni rapide Azione necessaria per abilitare il blocco dello schermo. - Ombreggiatura del testo - Sfondo (lista applicazioni e impostazioni) + Ombreggiatura testo + Sfondo (lista apps e impostazioni) Font Inverti data e ora - Scegli uno sfondo + Imposta sfondo Schermo - Mantieni lo schermo acceso - Ruota lo schermo - Funzionalità - Apri automaticamente la tastiera per cercare + Mantieni schermo acceso + Ruota schermo + Funzioni + Apri automaticamente la tastiera Sensibilità - Scegli un\'applicazione + Scegli applicazione Configurazione Abbiamo impostato alcune app predefinite per te. Puoi modificarle ora se lo desideri: Puoi anche cambiare la tua selezione in seguito. @@ -145,22 +145,22 @@ Sfocato Solido Predefinito di sistema - Serif (con grazie) - Monospace (a larghezza fissa) - Serif monospace (a larghezza fissa con grazie) - Applicazioni + Serif + Monospace + Serif monospace + Apps Applicazioni nascoste - Configurazione della lista applicazioni + Configura la lista applicazioni Predefinito Offuscato - Imposta μLauncher come predefinito per la schermata principale - Informazioni sulle applicazioni - Apri il tutorial del launcher - Ripristina le impostazioni predefinite - Stai per eliminare tutte le preferenze impostate. Vuoi continuare? + Imposta μLauncher come launcher predefinito + Informazioni app + Apri il tutorial di µLauncher + Ripristina le impostazioni + Stai per eliminare le preferenze impostate. Continuare? Segnala un bug Contatta lo sviluppatore del fork - Partecipa alla chat di μLauncher + Unisciti alla chat di μLauncher Informativa sulla privacy Contatta lo sviluppatore originale Unisciti a noi su Discord! @@ -168,7 +168,7 @@ Applicazioni preferite Applicazioni nascoste Applicazioni - Altri + Altro Disinstalla Informazioni sull\'app Aggiungi ai preferiti @@ -178,21 +178,21 @@ Rinomina Cerca Impostazioni μLauncher - Espandi il pannello notifiche + Espandi pannello notifiche Non fare niente - Blocca lo schermo - Accendi/spegni la torcia + Blocca schermo + Torcia ON/OFF Tutorial - Prenditi qualche secondo per imparare ad usare questo launcher! + 👋\n\nPrenditi qualche secondo per imparare ad usare questo launcher! Concetto - L\'app è open source (sotto licenza MIT) e disponibile su GitHub! Visita il nostro archivio! + E\' software libero (licenza MIT)!\nAssicurati di controllare il repository! Utilizzo La schermata principale contiene solo data e ora. Nessuna distrazione. Questa funzione richiede Android 6 o successivi. Rinomina %1$s Dinamico Colore - Due dita verso l\'alto + Scorri con due dita verso l\'alto Sono consapevole che questo concederà privilegi estesi a µLauncher. Accetto che µLauncher utilizzi il servizio di accessibilità per fornire funzionalità non correlate all\'accessibilità. Accetto che µLauncher non raccolga alcun dato. @@ -208,17 +208,17 @@ Attiva Servizi di Accessibilità Sono consapevole che esistono altre opzioni (utilizzando i privilegi di amministratore del dispositivo o il pulsante di accensione). Attivazione dei Servizi di Accessibilità - Cerca su internet - Premi invio durante la ricerca nell\'elenco delle app per avviare una ricerca su internet. + Cerca sul web + Premi invio durante la ricerca delle app per avviare una ricerca web. Cerca (senza avvio automatico) Licenze Open Source Licenze Open Source Segnala un bug - Grazie per aver contribuito a migliorare µLauncher!\nSi prega di aggiungere le seguenti informazioni alla segnalazione del bug: + Grazie per il tuo contributo al miglioramento di µLauncher!\nAggiungi le seguenti informazioni alla segnalazione del bug: Copia negli appunti Non segnalare pubblicamente le vulnerabilità di sicurezza su GitHub, ma utilizza invece: Annulla - Premi spazio per disabilitare temporaneamente questa funzionalità. + Premi spazio per disabilitare temporaneamente questa funzione Segnala una vulnerabilità di sicurezza Crea una segnalazione Spazio privato bloccato @@ -228,4 +228,45 @@ Impossibile aprire l\'URL: nessun browser trovato. Non è stata trovata un\'applicazione per gestire la ricerca. privilegi più ampi a µLauncher.
µLauncher utilizzerà questi privilegi solo per bloccare lo schermo. µLauncher non raccoglierà mai alcun dato. In particolare, µLauncher non usa il servizio di accessibilità per raccogliere nessun dato.]]>
+ Spazio privato + Spazio privato + Regola volume + Associa al gesto + Nascondi barra di stato + Nascondi barra di navigazione + Dona + App recenti + Tap + Su + Tap e scorri su + Tap + Giù + Tap e scorri verso il basso + Tap + Sinistra + Tap + Destra + Alto sx -> centro dx -> basso sx + Tap e scorri vesro sinistra + Tap e scorri verso destra + (Inverso)]]> + Basso sx -> centro dx -> alto sx + Alto dx -> centro sx -> basso dx + V + Alto sx -> centro basso -> alto dx + V (Reverse) + Alto dx -> centro basso -> alto sx + Λ + Basso sx -> centro alto -> basso dx + Basso dx -> centro alto -> basso sx + + + Λ (Inverso) + "Musica: Riproduci / Pausa" + Inverti la lista applicazioni + Azioni + Pulsante / gesto indietro + Indietro + Basso dx -> centro sx -> alto dx + Nascondi lo spazio privato dalla lista app + Lancia un altro launcher + Aggiungi scorciatoia + ]]> + Mostra in lista app
From 3d4bddbb4c5015373c8b8b8f140d218bf5898e13 Mon Sep 17 00:00:00 2001 From: Vladi69 Date: Sat, 19 Apr 2025 21:23:55 +0000 Subject: [PATCH 18/34] Translated using Weblate (Italian) Currently translated at 98.0% (253 of 258 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/ --- app/src/main/res/values-it/strings.xml | 138 +++++++++++-------------- 1 file changed, 63 insertions(+), 75 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 33ab7dd..de71a7b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,39 +1,19 @@ - Scorri verso destra sul bordo inferiore dello schermo + Scorri a destra sul bordo inferiore dello schermo Aspetto - Imposta app + Associa app Tema - μLauncher is designed to be minimal, efficient and free of distraction. \n\nIt contains no ads and collects no data. + μLauncher è progettato per essere minimale, efficiente e privo di distrazione.\n\nNon contiene annunci e non raccoglie dati. Predefinito - Non mostrare applicazioni abbinate a gesti nella lista delle app + Non mostrare nella lista le applicazioni associate a gesti Testo - Impostare µLauncher come servizio accessibilità permette l\'azione di blocco dello schermo. Si precisa che sono richiesti permessi eccessivi. Non si dovrebbe mai concedere con leggerezza permessi ad alcuna app. µLauncher utilizza i servizi di accessibilità esclusivamente per il blocco dello schermo. Puoi verificare il codice sorgente. Si segnala che il blocco schermo si può attivare anche concedendo a µLauncher i permessi di amministratore, tuttavia tale metodo non funziona con lo sblocco tramite impronta o riconoscimento facciale. + Impostare µLauncher come servizio accessibilità permette l\'azione di blocco dello schermo. Nota che sono richiesti permessi eccessivi. Non si dovrebbero mai concedere con leggerezza permessi ad alcuna app. µLauncher utilizza i servizi di accessibilità esclusivamente per il blocco dello schermo. Puoi verificare il codice sorgente. Nota che il blocco schermo si può attivare anche concedendo a µLauncher i permessi di amministratore, tuttavia tale metodo non funziona con lo sblocco tramite impronta o riconoscimento facciale. Sans serif - Scorri con due dita verso l\'alto - Negozio non trovato + Scorri verso l\'alto con due dita + Store non trovato Griglia - Scegli un sistema di blocco - Esistono due modi di bloccare lo schermo. - Sfortunatamente entrambi presentano svantaggi:

- -

Amministratore Dispositivo

- Non funziona con impronta e riconoscimento facciale. - -
-
- -

Servizio Accessibilità

- Richiede privilegi eccessivi. - µLauncher userà quei privilegi esclusivamente per bloccare lo schermo. -
- (Non ci si dovrebbe mai fidare di un\'applicazione qualsiasi appena scaricata su queste cose, ma puoi verificare il codice sorgente.) - - -



- Puoi cambiare le tue scelte in seguito nelle impostazioni. - ]]>
+ "<a href=\"https://issuetracker.google.com/issues/37010136#comment36\"><![CDATA[\n <h1>Scegli un sistema di blocco</h1>\n Esistono due modi di bloccare lo schermo.\n Sfortunatamente entrambi presentano svantaggi:<br/><br/>\n\n <h3>Amministratore Dispositivo</h3>\n Non funziona con impronta e riconoscimento facciale.\n\n <br/>\n <br/>\n\n <h3>Servizio Accessibilità</h3>\n Richiede privilegi eccessivi.\n µLauncher userà quei privilegi esclusivamente per bloccare lo schermo.\n <br/>\n (Non ci si dovrebbe mai fidare di un\\\'applicazione qualsiasi appena scaricata su queste cose, ma puoi verificare il <a href=\\\"https://github.com/jrpie/Launcher\\\">codice sorgente</a>.)\n\n In alcuni dispositivi, il PIN iniziale non verrà più utilizzato per la crittografia dei dati dopo aver attivato un servizio di accessibilità.\n\n Può essere <a href=\"https://issuetracker.google.com/issues/37010136#comment36\">riattivato</a> dopo.\n\n\n <br/><br/><br/><br/>\n Puoi cambiare le tue scelte in seguito nelle impostazioni.\n ]]>" Impossibile avviare l\'app Modificare le impostazioni? Apri impostazioni per associare un\'azione a questo gesto @@ -43,32 +23,32 @@ Scorri verso il basso con due dita Sinistra Scorri verso sinistra - Due dita a sx + Due dita a sinistra Scorri verso sinistra con due dita Destra Scorri verso destra - Due dita verso dx + Due dita a destra Scorri verso destra con due dita Destra (in alto) - Scorri verso destra sul bordo alto dello schermo - Scorri verso sinistra sul bordo inferiore dello schermo - Scorri verso sinistra sul bordo superiore dello schermo + Scorri a destra sul bordo superiore dello schermo + Scorri a sinistra sul bordo inferiore dello schermo + Scorri a sinistra sul bordo superiore dello schermo Su (bordo sinistro) Su - Scorri su + Scorri verso l\'alto Giù - Scorri con due dita giù + Scorri giù con due dita Scorri verso il basso Destra (in basso) Sinistra (in basso) Sinistra (in alto) - Scorri verso l\'alto sul bordo sinistro dello schermo + Scorri in alto sul bordo sinistro dello schermo Su (bordo destro) - Scorri verso l\'alto sul bordo destro dello schermo + Scorri in alto sul bordo destro dello schermo Giù (bordo sinistro) - Scorri verso il basso sul bordo sinistro dello schermo + Scorri in basso sul bordo sinistro dello schermo Giù (bordo destro) - Scorri verso il basso sul bordo destro dello schermo + Scorri in basso sul bordo destro dello schermo Volume + Doppio tap Doppio tap in spazio vuoto @@ -86,55 +66,55 @@ Mostra orario Mostra data Usa formato data locale - Mostra i secondi + Mostra secondi Scorri con due dita - Azioni di scorrimento a due dita + Azioni a due dita Apri il risultato della ricerca - Azioni di scorrimento sui bordi dello schermo + Azioni sui bordi dello schermo Scorri sui bordi dello schermo Larghezza bordo - Vai al codice sorgente + Codice sorgente Tutte le applicazioni Applicazioni preferite Musica: traccia successiva - Volume - + Abbassa volume Musica: traccia precedente - Volume + + Alza volume Annulla Impostazioni rapide - Azione necessaria per abilitare il blocco dello schermo. + Azione necessaria per abilitare il blocco schermo. Ombreggiatura testo Sfondo (lista apps e impostazioni) Font Inverti data e ora Imposta sfondo Schermo - Mantieni schermo acceso + Mantieni lo schermo acceso Ruota schermo Funzioni Apri automaticamente la tastiera Sensibilità Scegli applicazione Configurazione - Abbiamo impostato alcune app predefinite per te. Puoi modificarle ora se lo desideri: - Puoi anche cambiare la tua selezione in seguito. + Abbiamo impostato alcune app predefinite. Puoi modificarle ora se vuoi: + Puoi anche modificare la selezione in seguito. Iniziamo! - Sei pronto per iniziare! Spero questa applicazione ti risulti preziosa! - Finn (che ha ideato il launcher)\n \te Josia (che ha aggiunto qualche miglioramento e mantiene il fork μLauncher) + Sei pronto per iniziare!\n\nSpero che lo apprezzi!\n\n- Finn (che ha fatto Launcher) e Josia (che ha apportato alcuni miglioramenti e mantiene il fork μLauncher) Inizia Impostazioni Altre opzioni - Puoi aprire le tue app facendo scorrere il dito sullo schermo o premendo un pulsante. Configura i gesti nella prossima slide. - Errore: impossibile espandere la barra di stato. Questa azione utilizza funzionalità non incluse nelle API Android pubbliche. Sfortunatamente, non sembra funzionare sul tuo dispositivo. - Applicazione nascosta. Puoi renderla nuovamente visibile nelle impostazioni. - µLauncher deve essere autorizzato come amministratore del dispositivo per bloccare lo schermo. - Abilita il blocco dello schermo - Nessuna camera con torcia rilevata. - Errore: impossibile accedere alla torcia. - Il servizio accessibilità per µLauncher non è attivo. Per favore attivalo nelle impostazioni + Puoi avviare le tue app principali scorrendo il dito sullo schermo o premendo un pulsante. + Errore: impossibile espandere la barra di stato. Questa azione utilizza funzionalità non incluse nelle API Android. Sfortunatamente, non sembra funzionare sul tuo dispositivo. + Applicazione nascosta. Puoi renderla nuovamente visibile dalle impostazioni. + µLauncher deve amministrare il dispositivo per poter bloccare lo schermo. + Abilita il blocco schermo + Nessuna camera con torcia rilevata + Errore: impossibile accedere alla torcia + Il servizio accessibilità per µLauncher non è attivo. Attivalo nelle impostazioni Errore: impossibile bloccare lo schermo. (Se hai appena aggiornato l\'app, prova a disabilitare e riattivare il servizio accessibilità nelle impostazioni del telefono) - Errore: Il blocco schermo tramite accessibilità non è supportato su questo dispositivo. Per favore usa invece il servizio amministratore dispositivo. - µLauncher - blocco schermo + Errore: Il blocco schermo tramite accessibilità non è supportato su questo dispositivo. In alternativa usa il servizio amministratore dispositivo. + μLauncher Scegli come bloccare lo schermo Usa il servizio accessibilità Usa l\'amministratore dispositivo @@ -148,16 +128,16 @@ Serif Monospace Serif monospace - Apps + Applicazioni Applicazioni nascoste Configura la lista applicazioni Predefinito Offuscato - Imposta μLauncher come launcher predefinito + Imposta μLauncher come predefinito Informazioni app - Apri il tutorial di µLauncher + Tutorial di µLauncher Ripristina le impostazioni - Stai per eliminare le preferenze impostate. Continuare? + Stai per ripristinare tutte le impostazioni. Continuare? Segnala un bug Contatta lo sviluppatore del fork Unisciti alla chat di μLauncher @@ -170,7 +150,7 @@ Applicazioni Altro Disinstalla - Informazioni sull\'app + Informazioni app Aggiungi ai preferiti Rimuovi dai preferiti Nascondi @@ -192,13 +172,13 @@ Rinomina %1$s Dinamico Colore - Scorri con due dita verso l\'alto + Scorri su con due dita Sono consapevole che questo concederà privilegi estesi a µLauncher. Accetto che µLauncher utilizzi il servizio di accessibilità per fornire funzionalità non correlate all\'accessibilità. Accetto che µLauncher non raccolga alcun dato. Nascondi le app in pausa - Attiva/Disattiva Blocco Spazio Privato - Questa funzionalità richiede Android 15 o successivi. + Blocca/Sblocca Spazio Privato + Questa funzione richiede Android 15 o successivi. Rosso Trasparente Blu @@ -209,24 +189,24 @@ Sono consapevole che esistono altre opzioni (utilizzando i privilegi di amministratore del dispositivo o il pulsante di accensione). Attivazione dei Servizi di Accessibilità Cerca sul web - Premi invio durante la ricerca delle app per avviare una ricerca web. + Invio in ricerca app per avviare una ricerca web Cerca (senza avvio automatico) Licenze Open Source Licenze Open Source Segnala un bug Grazie per il tuo contributo al miglioramento di µLauncher!\nAggiungi le seguenti informazioni alla segnalazione del bug: Copia negli appunti - Non segnalare pubblicamente le vulnerabilità di sicurezza su GitHub, ma utilizza invece: + Non segnalare le vulnerabilità di sicurezza pubblicamente su GitHub, ma usa invece: Annulla - Premi spazio per disabilitare temporaneamente questa funzione + Spazio per disabilitare temporaneamente Segnala una vulnerabilità di sicurezza Crea una segnalazione Spazio privato bloccato Spazio privato sbloccato Spazio privato non disponibile - µLauncher deve essere la schermata iniziale predefinita per accedere allo spazio privato. + µLauncher deve essere il launcher predefinito per poter accedere allo spazio privato. Impossibile aprire l\'URL: nessun browser trovato. - Non è stata trovata un\'applicazione per gestire la ricerca. + Nessuna applicazione trovata per gestire la ricerca. privilegi più ampi a µLauncher.
µLauncher utilizzerà questi privilegi solo per bloccare lo schermo. µLauncher non raccoglierà mai alcun dato. In particolare, µLauncher non usa il servizio di accessibilità per raccogliere nessun dato.]]>
Spazio privato Spazio privato @@ -250,7 +230,7 @@ Alto dx -> centro sx -> basso dx V Alto sx -> centro basso -> alto dx - V (Reverse) + V (Inverso) Alto dx -> centro basso -> alto sx Λ Basso sx -> centro alto -> basso dx @@ -265,8 +245,16 @@ Indietro Basso dx -> centro sx -> alto dx Nascondi lo spazio privato dalla lista app - Lancia un altro launcher + Avvia un altro launcher Aggiungi scorciatoia ]]> Mostra in lista app + Blocca spazio privato + Sblocca spazio privato + Versione + Errore: impossibile abilitare il servizio di accessibilità + Quando corrisponde una sola app, viene avviata automaticamente.\nPuoi disabilitare l\'avvio con uno spazio prima della query. + Tutte le app + Puoi cercare rapidamente tra tutte le app nella lista app.\n\nScorri su per la lista o associa ad un gesto diverso. + Errore: impossibile mostrare le app recenti. (Se hai appena aggiornato l\'app, prova a disabilitare e riabilitare il servizio di accessibilità dalle impostazioni del telefono)
From 68eb59a9348bf71a2d9a19d3d228ec748002f635 Mon Sep 17 00:00:00 2001 From: Vladi69 Date: Sat, 19 Apr 2025 17:39:37 +0000 Subject: [PATCH 19/34] Translated using Weblate (Italian) Currently translated at 4.5% (1 of 22 strings) Translation: jrpie-Launcher/metadata Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/26.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/26.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/26.txt b/fastlane/metadata/android/it-IT/changelogs/26.txt new file mode 100644 index 0000000..d43e091 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/26.txt @@ -0,0 +1 @@ +bugfix From aabc607ed63c377753a5c226e17067ddf63dffe2 Mon Sep 17 00:00:00 2001 From: Vladi69 Date: Tue, 22 Apr 2025 07:10:03 +0000 Subject: [PATCH 20/34] Translated using Weblate (Italian) Currently translated at 99.6% (257 of 258 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/ --- app/src/main/res/values-it/strings.xml | 51 ++++++++++++++++++++------ 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index de71a7b..c7e8fcb 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -13,7 +13,31 @@ Scorri verso l\'alto con due dita Store non trovato Griglia - "<a href=\"https://issuetracker.google.com/issues/37010136#comment36\"><![CDATA[\n <h1>Scegli un sistema di blocco</h1>\n Esistono due modi di bloccare lo schermo.\n Sfortunatamente entrambi presentano svantaggi:<br/><br/>\n\n <h3>Amministratore Dispositivo</h3>\n Non funziona con impronta e riconoscimento facciale.\n\n <br/>\n <br/>\n\n <h3>Servizio Accessibilità</h3>\n Richiede privilegi eccessivi.\n µLauncher userà quei privilegi esclusivamente per bloccare lo schermo.\n <br/>\n (Non ci si dovrebbe mai fidare di un\\\'applicazione qualsiasi appena scaricata su queste cose, ma puoi verificare il <a href=\\\"https://github.com/jrpie/Launcher\\\">codice sorgente</a>.)\n\n In alcuni dispositivi, il PIN iniziale non verrà più utilizzato per la crittografia dei dati dopo aver attivato un servizio di accessibilità.\n\n Può essere <a href=\"https://issuetracker.google.com/issues/37010136#comment36\">riattivato</a> dopo.\n\n\n <br/><br/><br/><br/>\n Puoi cambiare le tue scelte in seguito nelle impostazioni.\n ]]>" + Scegli un sistema di blocco + Esistono due modi di bloccare lo schermo. + Sfortunatamente entrambi presentano svantaggi:

+ +

Amministratore Dispositivo

+ Non funziona con impronta e riconoscimento facciale. + +
+
+ +

Servizio Accessibilità

+ Richiede privilegi eccessivi. + µLauncher userà quei privilegi esclusivamente per bloccare lo schermo. +
+ (Non ci si dovrebbe mai fidare di un\'applicazione qualsiasi appena scaricata su queste cose, ma puoi verificare il codice sorgente.) + + In alcuni dispositivi, il PIN iniziale non verrà più utilizzato per la crittografia dei dati dopo aver attivato un servizio di accessibilità. + + Può essere riattivato dopo. + + +



+ Puoi cambiare le tue scelte in seguito nelle impostazioni. + ]]>
Impossibile avviare l\'app Modificare le impostazioni? Apri impostazioni per associare un\'azione a questo gesto @@ -109,15 +133,15 @@ Applicazione nascosta. Puoi renderla nuovamente visibile dalle impostazioni. µLauncher deve amministrare il dispositivo per poter bloccare lo schermo. Abilita il blocco schermo - Nessuna camera con torcia rilevata - Errore: impossibile accedere alla torcia + Nessuna camera con torcia rilevata. + Errore: impossibile accedere alla torcia. Il servizio accessibilità per µLauncher non è attivo. Attivalo nelle impostazioni Errore: impossibile bloccare lo schermo. (Se hai appena aggiornato l\'app, prova a disabilitare e riattivare il servizio accessibilità nelle impostazioni del telefono) Errore: Il blocco schermo tramite accessibilità non è supportato su questo dispositivo. In alternativa usa il servizio amministratore dispositivo. μLauncher Scegli come bloccare lo schermo Usa il servizio accessibilità - Usa l\'amministratore dispositivo + Usa Amministratore dispositivo Scegli un sistema di blocco dello schermo Scuro Chiaro @@ -132,7 +156,7 @@ Applicazioni nascoste Configura la lista applicazioni Predefinito - Offuscato + Soffuso Imposta μLauncher come predefinito Informazioni app Tutorial di µLauncher @@ -180,7 +204,7 @@ Blocca/Sblocca Spazio Privato Questa funzione richiede Android 15 o successivi. Rosso - Trasparente + Alpha Blu Verde Colore @@ -189,7 +213,7 @@ Sono consapevole che esistono altre opzioni (utilizzando i privilegi di amministratore del dispositivo o il pulsante di accensione). Attivazione dei Servizi di Accessibilità Cerca sul web - Invio in ricerca app per avviare una ricerca web + Invio in ricerca app per avviare una ricerca web. Cerca (senza avvio automatico) Licenze Open Source Licenze Open Source @@ -198,7 +222,7 @@ Copia negli appunti Non segnalare le vulnerabilità di sicurezza pubblicamente su GitHub, ma usa invece: Annulla - Spazio per disabilitare temporaneamente + Spazio per disabilitare temporaneamente. Segnala una vulnerabilità di sicurezza Crea una segnalazione Spazio privato bloccato @@ -207,7 +231,12 @@ µLauncher deve essere il launcher predefinito per poter accedere allo spazio privato. Impossibile aprire l\'URL: nessun browser trovato. Nessuna applicazione trovata per gestire la ricerca. - privilegi più ampi a µLauncher.
µLauncher utilizzerà questi privilegi solo per bloccare lo schermo. µLauncher non raccoglierà mai alcun dato. In particolare, µLauncher non usa il servizio di accessibilità per raccogliere nessun dato.]]>
+ privilegi estesi a μLauncher.
μLauncher userà questi privilegi solo per eseguire le seguenti azioni: +
    +
  • Blocco shermo
  • +
  • App recenti
  • +
+ μLauncher non raccoglierà mai alcun dato. In particolare, μLauncher non utilizza il servizio di accessibilità per raccogliere dati.]]>
Spazio privato Spazio privato Regola volume @@ -238,7 +267,7 @@ Λ (Inverso) - "Musica: Riproduci / Pausa" + Musica: Riproduci / Pausa Inverti la lista applicazioni Azioni Pulsante / gesto indietro @@ -252,7 +281,7 @@ Blocca spazio privato Sblocca spazio privato Versione - Errore: impossibile abilitare il servizio di accessibilità + Errore: impossibile abilitare il servizio di accessibilità. Quando corrisponde una sola app, viene avviata automaticamente.\nPuoi disabilitare l\'avvio con uno spazio prima della query. Tutte le app Puoi cercare rapidamente tra tutte le app nella lista app.\n\nScorri su per la lista o associa ad un gesto diverso. From bcf722ae34019ae9caa0682285f996611ad8adb2 Mon Sep 17 00:00:00 2001 From: Nicola Bortoletto Date: Wed, 23 Apr 2025 19:41:28 +0000 Subject: [PATCH 21/34] Translated using Weblate (Italian) Currently translated at 100.0% (259 of 259 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/ --- app/src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c7e8fcb..71bc28e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -286,4 +286,5 @@ Tutte le app Puoi cercare rapidamente tra tutte le app nella lista app.\n\nScorri su per la lista o associa ad un gesto diverso. Errore: impossibile mostrare le app recenti. (Se hai appena aggiornato l\'app, prova a disabilitare e riabilitare il servizio di accessibilità dalle impostazioni del telefono) + Chiudi la tastiera durante lo scorrimento From 0f92e6cd988d7d0e6440907fcce50a06268d2d58 Mon Sep 17 00:00:00 2001 From: class0068 Date: Thu, 24 Apr 2025 20:57:36 +0000 Subject: [PATCH 22/34] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (259 of 259 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 608e57b..32502fa 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -284,4 +284,5 @@ 错误:启用“无障碍”服务失败。 错误:无法展示最近应用屏幕。(如果您刚刚升级了本启动器,请尝试在手机设置中手动禁用再重新启用“无障碍”服务。) 启动其他启动器 + 滚动应用程序列表时自动隐藏键盘 From 3ac1794e14f3f2ad62a0ee0caa447d541790be9e Mon Sep 17 00:00:00 2001 From: Vossa Excelencia Date: Sun, 27 Apr 2025 13:42:12 +0000 Subject: [PATCH 23/34] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (280 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/pt_BR/ --- app/src/main/res/values-pt-rBR/strings.xml | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index fcdd9e8..3efb611 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -102,7 +102,7 @@ Diminuir volume Música: Próximo Música: Anterior - Não faça nada + Não fazer nada