From 6aa95eedf2cb5622532fdf834b144b177c91b168 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Sat, 10 May 2025 12:30:23 -0500 Subject: [PATCH 01/49] add clipboard function to version number --- .../launcher/ui/settings/meta/SettingsFragmentMeta.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt index dea0bcf..759d8cd 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt @@ -132,7 +132,12 @@ class SettingsFragmentMeta : Fragment(), UIObject { startActivity(Intent(this.context, LegalInfoActivity::class.java)) } + // version binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME + binding.settingsMetaTextVersion.setOnClickListener { + val deviceInfo = getDeviceInfo() + copyToClipboard(requireContext(), deviceInfo) + } } } From 4d61557a70a36151287752b9b1f65e63e93e91cf Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 13:03:54 +0200 Subject: [PATCH 02/49] fix #155 - close button not working in SelectWidgetActivity --- .../launcher/ui/widgets/manage/SelectWidgetActivity.kt | 5 +++++ 1 file changed, 5 insertions(+) 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 a1bd3b5..0efdb43 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 @@ -95,6 +95,11 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { layoutManager = viewManager adapter = viewAdapter } + + binding.selectWidgetClose.setOnClickListener { + setResult(RESULT_CANCELED) + finish() + } } override fun getTheme(): Resources.Theme { From b5b65a4c42abf50aae9ed6a96846828c50e4cb2d Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 13:42:04 +0200 Subject: [PATCH 03/49] catch IllegalArgumentException when accessing torch --- .../java/de/jrpie/android/launcher/actions/TorchManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt b/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt index 7e694c6..6768116 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt @@ -78,7 +78,8 @@ class TorchManager(context: Context) { cameraManager.setTorchMode(camera, !torchEnabled) } - } catch (e: CameraAccessException) { + } catch (e: Exception) { + // CameraAccessException, IllegalArgumentException Toast.makeText( context, context.getString(R.string.alert_torch_access_exception), From 1ba747946982a8849d5ebbee035d97cfb855b992 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 13:46:45 +0200 Subject: [PATCH 04/49] fix #156 - show popup over widget --- .../launcher/ui/widgets/manage/WidgetOverlayView.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 1b8a2d2..bb2a9da 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 @@ -2,11 +2,13 @@ package de.jrpie.android.launcher.ui.widgets.manage import android.content.Context import android.graphics.Canvas +import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View +import android.view.ViewGroup import android.widget.PopupMenu import androidx.core.graphics.toRectF import de.jrpie.android.launcher.R @@ -20,8 +22,9 @@ 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 { +class WidgetOverlayView : ViewGroup { + private val popupAnchor = View(context) val paint = Paint() val handlePaint = Paint() @@ -29,6 +32,8 @@ class WidgetOverlayView : View { var mode: WidgetManagerView.EditMode? = null class Handle(val mode: WidgetManagerView.EditMode, val position: Rect) init { + addView(popupAnchor) + setWillNotDraw(false) handlePaint.style = Paint.Style.STROKE handlePaint.setARGB(255, 255, 255, 255) @@ -84,12 +89,13 @@ class WidgetOverlayView : View { //preview?.bounds = bounds //preview?.draw(canvas) - + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + popupAnchor.layout(0,0,0,0) } fun showPopupMenu() { val widget = Widget.byId(context, widgetId)?: return - val menu = PopupMenu(context, this) + val menu = PopupMenu(context, popupAnchor) menu.menu.let { it.add( context.getString(R.string.widget_menu_remove) From 23bc58806c487581baa866800d04a7445afa14c7 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 13:47:21 +0200 Subject: [PATCH 05/49] fix #161 - draw shadow around widget control elements --- .../ui/widgets/manage/WidgetOverlayView.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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 bb2a9da..61006b8 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 @@ -24,24 +24,30 @@ private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt() */ class WidgetOverlayView : ViewGroup { + private val paint = Paint() + private val handlePaint = Paint() + private val selectedHandlePaint = Paint() + private val popupAnchor = View(context) - val paint = Paint() - val handlePaint = Paint() - val selectedHandlePaint = Paint() var mode: WidgetManagerView.EditMode? = null + class Handle(val mode: WidgetManagerView.EditMode, val position: Rect) init { addView(popupAnchor) setWillNotDraw(false) handlePaint.style = Paint.Style.STROKE - handlePaint.setARGB(255, 255, 255, 255) + handlePaint.color = Color.WHITE + handlePaint.strokeWidth = 2f + handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK) selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE selectedHandlePaint.setARGB(100, 255, 255, 255) + handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK) paint.style = Paint.Style.STROKE - paint.setARGB(255, 255, 255, 255) + paint.color = Color.WHITE + paint.setShadowLayer(10f,0f,0f, Color.BLACK) } private var preview: Drawable? = null @@ -80,14 +86,15 @@ class WidgetOverlayView : ViewGroup { } } val bounds = getBounds() + canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint) if (mode == null) { return } - //preview?.bounds = bounds //preview?.draw(canvas) + } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { popupAnchor.layout(0,0,0,0) From 22e44ca9f2abf3a4f5f50c6194c233d93c22bc8b Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 15:01:33 +0200 Subject: [PATCH 06/49] fix #160 - consistent position of widget container --- app/src/main/AndroidManifest.xml | 1 + .../launcher/preferences/legacy/Version4.kt | 2 - .../jrpie/android/launcher/ui/HomeActivity.kt | 10 +-- .../de/jrpie/android/launcher/ui/UIObject.kt | 11 ++- .../ui/widgets/WidgetPanelActivity.kt | 19 ++++- .../widgets/manage/ManageWidgetsActivity.kt | 70 ++++++++++++------- .../res/layout/activity_manage_widgets.xml | 7 +- 7 files changed, 77 insertions(+), 43 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e60a85b..1b10784 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ android:exported="false" /> .onCreate(savedInstanceState) super.onCreate() - widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.Companion.HOME.id) + widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id) val binding = ActivityWidgetPanelBinding.inflate(layoutInflater) setContentView(binding.root) + + // The widget container should extend below the status and navigation bars, + // so let's set an empty WindowInsetsListener to prevent it from being moved. + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> + windowInsets + } + binding.widgetPanelWidgetContainer.widgetPanelId = widgetPanelId binding.widgetPanelWidgetContainer.updateWidgets( this, @@ -38,6 +46,13 @@ class WidgetPanelActivity : Activity(), UIObject { return mTheme } + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + + if (hasFocus && LauncherPreferences.display().hideNavigationBar()) { + hideNavigationBar() + } + } override fun onStart() { super.onStart() 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 a841919..6fec855 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 @@ -8,14 +8,14 @@ import android.content.res.Resources import android.graphics.Rect import android.os.Bundle import android.util.Log +import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import com.google.android.material.floatingactionbutton.FloatingActionButton +import androidx.core.view.updateLayoutParams import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.databinding.ActivityManageWidgetsBinding 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 @@ -30,14 +30,16 @@ 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 { +class ManageWidgetsActivity : UIObject, Activity() { private var panelId: Int = WidgetPanel.HOME.id + private lateinit var binding: ActivityManageWidgetsBinding private var sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> if (prefKey == LauncherPreferences.widgets().keys().widgets()) { - findViewById(R.id.manage_widgets_container).updateWidgets(this, + binding.manageWidgetsContainer.updateWidgets( + this, LauncherPreferences.widgets().widgets() ) } @@ -46,21 +48,33 @@ class ManageWidgetsActivity : Activity(), UIObject { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) super.onCreate() - setContentView(R.layout.activity_manage_widgets) + binding = ActivityManageWidgetsBinding.inflate(layoutInflater) + setContentView(binding.root) panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id - findViewById(R.id.manage_widgets_button_add).setOnClickListener { + binding.manageWidgetsButtonAdd.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 + // The widget container should extend below the status and navigation bars, + // so let's set an empty WindowInsetsListener to prevent it from being moved. + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> + windowInsets } - findViewById(R.id.manage_widgets_container).let { + // The button must not be placed under the navigation bar + ViewCompat.setOnApplyWindowInsetsListener(binding.manageWidgetsButtonAdd) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams { + leftMargin = insets.left + bottomMargin = insets.bottom + rightMargin = insets.right + } + WindowInsetsCompat.CONSUMED + } + + binding.manageWidgetsContainer.let { it.widgetPanelId = panelId it.updateWidgets(this, LauncherPreferences.widgets().widgets()) } @@ -77,20 +91,23 @@ class ManageWidgetsActivity : Activity(), UIObject { override fun onResume() { super.onResume() - findViewById(R.id.manage_widgets_container).updateWidgets(this, + binding.manageWidgetsContainer.updateWidgets( + this, LauncherPreferences.widgets().widgets() ) } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + + if (hasFocus && LauncherPreferences.display().hideNavigationBar()) { + hideNavigationBar() + } + } + 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 + return modifyTheme(super.getTheme()) } override fun onDestroy() { @@ -100,7 +117,7 @@ class ManageWidgetsActivity : Activity(), UIObject { } - fun selectWidget() { + private fun selectWidget() { val appWidgetHost = (application as Application).appWidgetHost startActivityForResult( Intent(this, SelectWidgetActivity::class.java).also { @@ -117,7 +134,7 @@ class ManageWidgetsActivity : Activity(), UIObject { } - fun createWidget(data: Intent) { + private 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 @@ -127,9 +144,10 @@ class ManageWidgetsActivity : Activity(), UIObject { val display = windowManager.defaultDisplay val position = WidgetPosition.fromAbsoluteRect( - Rect(0,0, - min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth), - min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight) + Rect( + 0, 0, + min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth), + min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight) ), display.width, display.height diff --git a/app/src/main/res/layout/activity_manage_widgets.xml b/app/src/main/res/layout/activity_manage_widgets.xml index c77f0e3..66404ee 100644 --- a/app/src/main/res/layout/activity_manage_widgets.xml +++ b/app/src/main/res/layout/activity_manage_widgets.xml @@ -1,5 +1,6 @@ - + - \ No newline at end of file + + From 8173993601b9763673076d19ab6f5d8a25d3c63c Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 15:37:18 +0200 Subject: [PATCH 07/49] add debug info widget in debug mode --- .../launcher/preferences/Preferences.kt | 35 ++++++++++++---- .../jrpie/android/launcher/ui/HomeActivity.kt | 7 ++-- .../android/launcher/ui/widgets/ClockView.kt | 4 +- .../launcher/ui/widgets/DebugInfoView.kt | 17 ++++++++ .../launcher/widgets/DebugInfoWidget.kt | 42 +++++++++++++++++++ .../widgets/LauncherWidgetProvider.kt | 10 ++--- .../jrpie/android/launcher/widgets/Widget.kt | 1 - .../jrpie/android/launcher/widgets/Widgets.kt | 4 +- .../layout/{home.xml => activity_home.xml} | 0 .../layout/{clock.xml => widget_clock.xml} | 0 app/src/main/res/layout/widget_debug_info.xml | 16 +++++++ 11 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt rename app/src/main/res/layout/{home.xml => activity_home.xml} (100%) rename app/src/main/res/layout/{clock.xml => widget_clock.xml} (100%) create mode 100644 app/src/main/res/layout/widget_debug_info.xml 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 34cf569..fd86d79 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 @@ -16,6 +16,7 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio 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.DebugInfoWidget import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition import de.jrpie.android.launcher.widgets.deleteAllWidgets @@ -95,17 +96,33 @@ fun resetPreferences(context: Context) { ) ) + if (BuildConfig.DEBUG) { + LauncherPreferences.widgets().widgets( + LauncherPreferences.widgets().widgets().also { + it.add( + DebugInfoWidget( + (context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(), + WidgetPosition(1, 1, 10, 4), + WidgetPanel.HOME.id + ) + ) + } + ) + } val hidden: MutableSet = mutableSetOf() - val launcher = DetailedAppInfo.fromAppInfo( - AppInfo( - BuildConfig.APPLICATION_ID, - HomeActivity::class.java.name, - INVALID_USER - ), context - ) - launcher?.getRawInfo()?.let { hidden.add(it) } - Log.i(TAG,"Hiding ${launcher?.getRawInfo()}") + + if (!BuildConfig.DEBUG) { + val launcher = DetailedAppInfo.fromAppInfo( + AppInfo( + BuildConfig.APPLICATION_ID, + HomeActivity::class.java.name, + INVALID_USER + ), context + ) + launcher?.getRawInfo()?.let { hidden.add(it) } + Log.i(TAG, "Hiding ${launcher?.getRawInfo()}") + } LauncherPreferences.apps().hidden(hidden) Action.resetToDefaultActions(context) 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 957c902..f501107 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 @@ -12,11 +12,10 @@ import android.view.MotionEvent import android.view.View import android.window.OnBackInvokedDispatcher import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction -import de.jrpie.android.launcher.databinding.HomeBinding +import de.jrpie.android.launcher.databinding.ActivityHomeBinding import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity @@ -35,7 +34,7 @@ import de.jrpie.android.launcher.ui.tutorial.TutorialActivity */ class HomeActivity : UIObject, Activity() { - private lateinit var binding: HomeBinding + private lateinit var binding: ActivityHomeBinding private var touchGestureDetector: TouchGestureDetector? = null private var sharedPreferencesListener = @@ -60,7 +59,7 @@ class HomeActivity : UIObject, Activity() { // Initialise layout - binding = HomeBinding.inflate(layoutInflater) + binding = ActivityHomeBinding.inflate(layoutInflater) setContentView(binding.root) 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 index 33c4888..54d3869 100644 --- 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 @@ -6,13 +6,13 @@ 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.databinding.WidgetClockBinding 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) + val binding: WidgetClockBinding = WidgetClockBinding.inflate(LayoutInflater.from(context), this, true) init { initClock() setOnClicks() diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt new file mode 100644 index 0000000..d0ab70a --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt @@ -0,0 +1,17 @@ +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 de.jrpie.android.launcher.databinding.WidgetDebugInfoBinding +import de.jrpie.android.launcher.getDeviceInfo + +class DebugInfoView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) { + + val binding: WidgetDebugInfoBinding = WidgetDebugInfoBinding.inflate(LayoutInflater.from(context), this, true) + + init { + binding.debugInfoText.text = getDeviceInfo() + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt new file mode 100644 index 0000000..01ecddc --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt @@ -0,0 +1,42 @@ +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.DebugInfoView +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +@Serializable +@SerialName("widget:debuginfo") +class DebugInfoWidget( + override val id: Int, + override var position: WidgetPosition, + override val panelId: Int, + override var allowInteraction: Boolean = true +) : Widget() { + + override fun createView(activity: Activity): View { + return DebugInfoView(activity, null, id) + } + + override fun findView(views: Sequence): DebugInfoView? { + return views.mapNotNull { it as? DebugInfoView }.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 index 018b29b..92f33a9 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt @@ -37,13 +37,14 @@ class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidge } } -class LauncherClockWidgetProvider : LauncherWidgetProvider() { - override fun loadLabel(context: Context): CharSequence? { +data object LauncherClockWidgetProvider : LauncherWidgetProvider() { + + override fun loadLabel(context: Context): CharSequence { return context.getString(R.string.widget_clock_label) } - override fun loadDescription(context: Context): CharSequence? { + override fun loadDescription(context: Context): CharSequence { return context.getString(R.string.widget_clock_description) } @@ -54,5 +55,4 @@ class LauncherClockWidgetProvider : LauncherWidgetProvider() { override fun loadIcon(context: Context): Drawable? { return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24) } -} - +} \ No newline at end of file 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 e31250b..28539a2 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 @@ -4,7 +4,6 @@ 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 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 b7f140b..cded50c 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 @@ -53,7 +53,7 @@ fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidget fun getAppWidgetProviders( context: Context ): List { - val list = mutableListOf(LauncherClockWidgetProvider()) + val list = mutableListOf(LauncherClockWidgetProvider) val appWidgetManager = context.getAppWidgetManager() val profiles = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -68,11 +68,9 @@ fun getAppWidgetProviders( context: Context ): List { }.flatten() ) - return list } - fun updateWidget(widget: Widget) { LauncherPreferences.widgets().widgets( (LauncherPreferences.widgets().widgets() ?: setOf()) diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/activity_home.xml similarity index 100% rename from app/src/main/res/layout/home.xml rename to app/src/main/res/layout/activity_home.xml diff --git a/app/src/main/res/layout/clock.xml b/app/src/main/res/layout/widget_clock.xml similarity index 100% rename from app/src/main/res/layout/clock.xml rename to app/src/main/res/layout/widget_clock.xml diff --git a/app/src/main/res/layout/widget_debug_info.xml b/app/src/main/res/layout/widget_debug_info.xml new file mode 100644 index 0000000..1de9e43 --- /dev/null +++ b/app/src/main/res/layout/widget_debug_info.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file From 30e9fcd20f47c7e2b2fd69c0346b1d94e4f4c3ec Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 16:05:15 +0200 Subject: [PATCH 08/49] show number of widgets for widget panel --- .../manage/ManageWidgetPanelsActivity.kt | 8 +++++--- .../manage/WidgetPanelsRecyclerAdapter.kt | 6 ++++++ .../android/launcher/widgets/WidgetPanel.kt | 6 ++++++ .../res/layout/list_widget_panels_row.xml | 20 ++++++++++++++++--- app/src/main/res/values/strings.xml | 4 ++-- 5 files changed, 36 insertions(+), 8 deletions(-) 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 cb57fda..163777f 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 @@ -18,13 +18,16 @@ import de.jrpie.android.launcher.widgets.updateWidgetPanel class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { + @SuppressLint("NotifyDataSetChanged") private val sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> - if (prefKey == LauncherPreferences.widgets().keys().customPanels()) { + if ( + prefKey == LauncherPreferences.widgets().keys().customPanels() + || prefKey == LauncherPreferences.widgets().keys().widgets() + ) { viewAdapter.widgetPanels = (LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray() - @SuppressLint("NotifyDataSetChanged") viewAdapter.notifyDataSetChanged() } } @@ -76,7 +79,6 @@ class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { ) ) } - true } } 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 index 40c2c2f..d27ba9a 100644 --- 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 @@ -26,10 +26,16 @@ class WidgetPanelsRecyclerAdapter( class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var labelView: TextView = itemView.findViewById(R.id.list_widget_panels_label) + var infoView: TextView = itemView.findViewById(R.id.list_widget_panels_info) } override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { viewHolder.labelView.text = widgetPanels[i].label + val numWidgets = widgetPanels[i].getWidgets().size + viewHolder.infoView.text = context.resources.getQuantityString( + R.plurals.widget_panel_number_of_widgets, + numWidgets, numWidgets + ) viewHolder.itemView.setOnClickListener { onSelectWidgetPanel(widgetPanels[i]) 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 index 93e588d..e56983a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt @@ -32,6 +32,12 @@ class WidgetPanel(val id: Int, var label: String) { .filter { it.panelId == this.id }.forEach { it.delete(context) } } + fun getWidgets(): List { + return LauncherPreferences.widgets().widgets().filter { + it.panelId == this.id + } + } + companion object { val HOME = WidgetPanel(0, "home") diff --git a/app/src/main/res/layout/list_widget_panels_row.xml b/app/src/main/res/layout/list_widget_panels_row.xml index 53f7449..835050f 100644 --- a/app/src/main/res/layout/list_widget_panels_row.xml +++ b/app/src/main/res/layout/list_widget_panels_row.xml @@ -1,6 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46fce3d..d399d12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -406,8 +406,8 @@ Widget Panel #%1$d - Contains %d widget. - Contains %d widgets. + Contains %1$d widget. + Contains %1$d widgets. From 33ccea8cbcad5fc6daead8daf59716017c0c0474 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Mon, 12 May 2025 16:33:59 +0200 Subject: [PATCH 09/49] fix #162 - place new widgets in free area if possible --- .../widgets/manage/ManageWidgetsActivity.kt | 17 +++--- .../launcher/widgets/WidgetPosition.kt | 58 ++++++++++++++++--- 2 files changed, 60 insertions(+), 15 deletions(-) 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 6fec855..4b5c0c2 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 @@ -17,9 +17,12 @@ import de.jrpie.android.launcher.databinding.ActivityManageWidgetsBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.UIObject import de.jrpie.android.launcher.widgets.AppWidget +import de.jrpie.android.launcher.widgets.GRID_SIZE import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition +import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt // http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html @@ -143,14 +146,12 @@ class ManageWidgetsActivity : UIObject, Activity() { 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 widgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) + + val position = WidgetPosition.findFreeSpace( + WidgetPanel.byId(panelId), + max(3, (GRID_SIZE * (widgetInfo.minWidth) / display.width.toFloat()).roundToInt()), + max(3, (GRID_SIZE * (widgetInfo.minHeight) / display.height.toFloat()).roundToInt()) ) val widget = AppWidget(appWidgetId, position, panelId, provider) 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 index b575665..e51f00c 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt @@ -11,9 +11,20 @@ const val GRID_SIZE: Short = 12 @Serializable data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) { + constructor(rect: Rect) : this( + rect.left.toShort(), + rect.top.toShort(), + (rect.right - rect.left).toShort(), + (rect.bottom - rect.top).toShort() + ) + + fun toRect(): Rect { + return Rect(x.toInt(), y.toInt(), x + width, y + height) + } + fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight= screenHeight / GRID_SIZE.toFloat() + val gridHeight = screenHeight / GRID_SIZE.toFloat() return Rect( (x * gridWidth).toInt(), @@ -23,25 +34,33 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig ) } + companion object { fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight= screenHeight / 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 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) + return WidgetPosition(x, y, w, h) } - fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition { + fun center( + minWidth: Int, + minHeight: Int, + screenWidth: Int, + screenHeight: Int + ): WidgetPosition { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight= screenHeight / GRID_SIZE.toFloat() + val gridHeight = screenHeight / GRID_SIZE.toFloat() val cellsWidth = ceil(minWidth / gridWidth).toInt().toShort() val cellsHeight = ceil(minHeight / gridHeight).toInt().toShort() @@ -52,7 +71,32 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig cellsWidth, cellsHeight ) + } + fun findFreeSpace( + widgetPanel: WidgetPanel?, + minWidth: Int, + minHeight: Int + ): WidgetPosition { + val rect = Rect(0, 0, minWidth, minHeight) + if (widgetPanel == null) { + return WidgetPosition(rect) + } + + val widgets = widgetPanel.getWidgets().map { it.position.toRect() } + + for (x in 0.. Date: Mon, 12 May 2025 16:36:17 +0200 Subject: [PATCH 10/49] 0.2.1 --- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/46.txt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/46.txt diff --git a/app/build.gradle b/app/build.gradle index 7e92f3b..42f0a2b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { minSdkVersion 21 targetSdkVersion 35 compileSdk 35 - versionCode 45 - versionName "0.2.0" + versionCode 46 + versionName "0.2.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/fastlane/metadata/android/en-US/changelogs/46.txt b/fastlane/metadata/android/en-US/changelogs/46.txt new file mode 100644 index 0000000..7d7f599 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/46.txt @@ -0,0 +1,2 @@ +* Fixed several bugs related to widgets +* Copy device info when clicking the version number (thank you, wassupluke!) From 6dfdb09cf67d476e46c84e3ea77f2cbfb1f1c75d Mon Sep 17 00:00:00 2001 From: Anonymous Date: Sat, 10 May 2025 00:07:20 +0000 Subject: [PATCH 11/49] Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.2% (278 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 | 2 +- 1 file changed, 1 insertion(+), 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 3efb611..0d42f2e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -332,7 +332,7 @@ Contém %d widget. Contém %d widgets. - + Ok Painéis de widget From 435ce32fbd0552b358a78a4d0144b05506b332f1 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Sat, 10 May 2025 00:07:19 +0000 Subject: [PATCH 12/49] Translated using Weblate (Arabic) Currently translated at 99.2% (278 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/ar/ --- app/src/main/res/values-ar/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b0f66f9..22e7179 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -310,7 +310,7 @@ يحتوي على %d أدوات. يحتوي على %d أدوات. يحتوي على %d أداة. - + إغلاق لوحة المفاتيح عند التمرير From ede651525b99c5155dc5e65c1fd1e871a156c6bf Mon Sep 17 00:00:00 2001 From: renar Date: Mon, 12 May 2025 08:24:39 +0000 Subject: [PATCH 13/49] Translated using Weblate (German) Currently translated at 88.9% (249 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/de/ --- app/src/main/res/values-de/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d07ff7d..0b4090c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -97,9 +97,9 @@ Bildschirm nicht ausschalten Bildschirm drehen Funktionalität - Doppelte Wischaktionen + Doppelte Wischgesten Mit zwei Fingern wischen - Kantenaktionen + Kantengesten Kantenbreite Suchergebnis starten Beim Durchsuchen der Apps Enter drücken, um stattdessen im Internet zu suchen. From 070d232681daf9ad2ff36b5dcafd338a07edb7a4 Mon Sep 17 00:00:00 2001 From: class0068 Date: Sun, 11 May 2025 14:46:38 +0000 Subject: [PATCH 14/49] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 98.9% (277 of 280 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 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 32502fa..ebd3c7c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -285,4 +285,22 @@ 错误:无法展示最近应用屏幕。(如果您刚刚升级了本启动器,请尝试在手机设置中手动禁用再重新启用“无障碍”服务。) 启动其他启动器 滚动应用程序列表时自动隐藏键盘 + 设置小部件 + 删除 + 开启交互功能 + 关闭交互功能 + 时钟 + 删除 + 重命名 + 小部件面板 #%1$d + 小部件面板 + 选择小部件面板 + 打开小部件面板 + 创建新面板 + 启动器 > 设置小部件面板”中进行创建。]]> + 桌面小部件 + 该小部件面板不存在 + μLauncher 默认时钟小部件 + 选择小部件 + 设置小部件面板 From 12986c15b3e947b701e1cdfb60886aeba60c391d Mon Sep 17 00:00:00 2001 From: renar Date: Mon, 12 May 2025 13:43:57 +0000 Subject: [PATCH 15/49] Translated using Weblate (Italian) Currently translated at 99.6% (279 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/ --- app/src/main/res/values-it/strings.xml | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 71bc28e..10362a0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -93,9 +93,9 @@ Mostra secondi Scorri con due dita - Azioni a due dita + Scorrimento a due dita Apri il risultato della ricerca - Azioni sui bordi dello schermo + Scorrimento sui bordi dello schermo Scorri sui bordi dello schermo Larghezza bordo Codice sorgente @@ -287,4 +287,29 @@ 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 + Gestione widget + Rimuovi + Gestione pannelli widget + Scegli widget + Configura + Abilita interazione + Disabilita interazione + Orologio + Orologio predefinito di μLauncher + Elimina + Rinomina + Pannello widget #%1$d + Ok + Pannelli widget + + Contiene %d widget. + Contiene %d widget. + Contiene %d widget. + + Crea nuovo pannello widget + Apri pannello widget + Questo pannello widget non esiste più. + Widget + Seleziona pannello widget + Launcher > Gestione Pannelli Widget.]]> From 374b688ddf4ba269dcd58f01ce4854d8f27c4d54 Mon Sep 17 00:00:00 2001 From: renar Date: Mon, 12 May 2025 08:28:08 +0000 Subject: [PATCH 16/49] Translated using Weblate (Dutch) Currently translated at 100.0% (280 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/nl/ --- app/src/main/res/values-nl/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 8da53d4..c6bf7bf 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -289,9 +289,9 @@ Navigatiebalk verbergen Scherm draaien Functionaliteit - Dubbele veeg acties + Dubbele veeg gebaaren Met twee vingers vegen - Hoek-acties + Hoek-gebaren Kantbreedte Start zoekresultaten Spatie drukken om deze functie tijdelijk te onderdruken. From 520e7d5c0d8588e182a1ee8032513f2e8a39ffd7 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Mon, 12 May 2025 15:07:20 +0000 Subject: [PATCH 17/49] Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.9% (277 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 | 2 +- 1 file changed, 1 insertion(+), 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 0d42f2e..3efb611 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -332,7 +332,7 @@ Contém %d widget. Contém %d widgets. - + Ok Painéis de widget From 916a272e8fe7507f9768ffe11dbb28049fb25238 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Mon, 12 May 2025 15:07:19 +0000 Subject: [PATCH 18/49] Translated using Weblate (Arabic) Currently translated at 98.9% (277 of 280 strings) Translation: jrpie-Launcher/Launcher Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/ar/ --- app/src/main/res/values-ar/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 22e7179..b0f66f9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -310,7 +310,7 @@ يحتوي على %d أدوات. يحتوي على %d أدوات. يحتوي على %d أداة. - + إغلاق لوحة المفاتيح عند التمرير From 04330ff4074d07a8698363d68b3e5f464cef4215 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Tue, 13 May 2025 13:33:48 +0200 Subject: [PATCH 19/49] add crash handler --- app/src/main/AndroidManifest.xml | 6 +- .../de/jrpie/android/launcher/Application.kt | 8 ++ .../de/jrpie/android/launcher/Functions.kt | 13 ++- .../jrpie/android/launcher/Notifications.kt | 87 +++++++++++++++++++ .../launcher/preferences/Preferences.kt | 2 + .../launcher/ui/ReportCrashActivity.kt | 57 ++++++++++++ .../tutorial/tabs/TutorialFragment5Finish.kt | 4 + .../res/drawable/baseline_bug_report_24.xml | 11 +++ .../main/res/layout/activity_report_crash.xml | 59 +++++++++++++ app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 25 +++++- build.gradle | 4 +- 12 files changed, 270 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/Notifications.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt create mode 100644 app/src/main/res/drawable/baseline_bug_report_24.xml create mode 100644 app/src/main/res/layout/activity_report_crash.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b10784..087ec28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ tools:ignore="QueryAllPackagesPermission" /> + + @@ -80,6 +82,9 @@ + - \ 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 3c2e3bc..cf9e697 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -25,6 +25,7 @@ import de.jrpie.android.launcher.preferences.resetPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlin.system.exitProcess const val APP_WIDGET_HOST_ID = 42; @@ -106,6 +107,11 @@ class Application : android.app.Application() { // TODO Error: Invalid resource ID 0x00000000. // DynamicColors.applyToActivitiesIfAvailable(this) + Thread.setDefaultUncaughtExceptionHandler { _, throwable -> + sendCrashNotification(this@Application, throwable) + exitProcess(1) + } + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { torchManager = TorchManager(this) @@ -157,6 +163,8 @@ class Application : android.app.Application() { removeUnusedShortcuts(this) } loadApps() + + createNotificationChannels(this) } fun getCustomAppNames(): HashMap { 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 9679ae5..b6df30b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -6,9 +6,6 @@ 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 @@ -227,3 +224,13 @@ fun copyToClipboard(context: Context, text: String) { val clipData = ClipData.newPlainText("Debug Info", text) clipboardManager.setPrimaryClip(clipData) } + +fun writeEmail(context: Context, to: String, subject: String, text: String) { + val intent = Intent(Intent.ACTION_SENDTO) + intent.setData("mailto:".toUri()) + intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(to)) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.putExtra(Intent.EXTRA_TEXT, text) + context.startActivity(Intent.createChooser(intent, context.getString(R.string.send_email))) +} + diff --git a/app/src/main/java/de/jrpie/android/launcher/Notifications.kt b/app/src/main/java/de/jrpie/android/launcher/Notifications.kt new file mode 100644 index 0000000..0cf0efb --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/Notifications.kt @@ -0,0 +1,87 @@ +package de.jrpie.android.launcher + +import android.app.Activity +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import de.jrpie.android.launcher.ui.EXTRA_CRASH_LOG +import de.jrpie.android.launcher.ui.ReportCrashActivity +import java.io.PrintWriter +import java.io.StringWriter +import kotlin.random.Random + +private val NOTIFICATION_CHANNEL_CRASH = "launcher:crash" + +fun createNotificationChannels(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationChannel = NotificationChannel( + NOTIFICATION_CHANNEL_CRASH, + context.getString(R.string.notification_channel_crash), + NotificationManager.IMPORTANCE_HIGH + ) + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(notificationChannel) + } +} + +fun requestNotificationPermission(activity: Activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return + } + + val permission = + (activity.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) + + if (!permission) { + ActivityCompat.requestPermissions( + activity, + arrayOf( android.Manifest.permission.POST_NOTIFICATIONS ), + 1 + ) + } +} + +fun sendCrashNotification(context: Context, throwable: Throwable) { + val stringWriter = StringWriter() + val printWriter = PrintWriter(stringWriter) + throwable.printStackTrace(printWriter) + + val intent = Intent(context, ReportCrashActivity::class.java) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.putExtra(EXTRA_CRASH_LOG, stringWriter.toString()) + + val pendingIntent = PendingIntent.getActivity( + context, + Random.nextInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_CRASH) + .setSmallIcon(R.drawable.baseline_bug_report_24) + .setContentTitle(context.getString(R.string.notification_crash_title)) + .setContentText(context.getString(R.string.notification_crash_explanation)) + .setContentIntent(pendingIntent) + .setAutoCancel(false) + .setPriority(NotificationCompat.PRIORITY_HIGH) + + val notificationManager = NotificationManagerCompat.from(context) + try { + notificationManager.notify( + 0, + builder.build() + ) + } catch (e: SecurityException) { + Log.e("Crash Notification", "Could not send notification") + } +} 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 fd86d79..8936675 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 @@ -14,6 +14,7 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio 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.sendCrashNotification import de.jrpie.android.launcher.ui.HomeActivity import de.jrpie.android.launcher.widgets.ClockWidget import de.jrpie.android.launcher.widgets.DebugInfoWidget @@ -76,6 +77,7 @@ fun migratePreferencesToNewVersion(context: Context) { } } catch (e: Exception) { Log.e(TAG, "Unable to restore preferences:\n${e.stackTrace}") + sendCrashNotification(context, e) resetPreferences(context) } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt new file mode 100644 index 0000000..5d95769 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt @@ -0,0 +1,57 @@ +package de.jrpie.android.launcher.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.copyToClipboard +import de.jrpie.android.launcher.databinding.ActivityReportCrashBinding +import de.jrpie.android.launcher.getDeviceInfo +import de.jrpie.android.launcher.openInBrowser +import de.jrpie.android.launcher.writeEmail + +const val EXTRA_CRASH_LOG = "crashLog" + +class ReportCrashActivity : AppCompatActivity() { + // We don't know what caused the crash, so this Activity should use as little functionality as possible. + // In particular it is not a UIObject (and hence looks quite ugly) + private lateinit var binding: ActivityReportCrashBinding + private var report: String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Initialise layout + binding = ActivityReportCrashBinding.inflate(layoutInflater) + setContentView(binding.root) + + setTitle(R.string.report_crash_title) + setSupportActionBar(binding.reportCrashAppbar) + supportActionBar?.setDisplayHomeAsUpEnabled(false) + + report = intent.getStringExtra(EXTRA_CRASH_LOG) + + binding.reportCrashButtonCopy.setOnClickListener { + copyToClipboard(this, + "Device Info:\n${getDeviceInfo()}\n\nCrash Log:\n${report}") + } + + binding.reportCrashButtonMail.setOnClickListener { + writeEmail( + this, + getString(R.string.settings_meta_report_bug_mail), + "Crash in μLauncher", + "Hi!\nUnfortunately, μLauncher crashed:\n" + + "\nDevice Info\n\n${getDeviceInfo()}\n\n" + + "\nCrash Log\n\n${report}\n" + + "\nAdditional Information\n\n" + + "[Please add additional information: What did you do when the crash happened? Do you know how to trigger it? ... ]" + ) + } + binding.reportCrashButtonReport.setOnClickListener { + openInBrowser( + getString(R.string.settings_meta_report_bug_link), + this + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt index 8feaa07..e15cef1 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import de.jrpie.android.launcher.BuildConfig.VERSION_CODE import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding import de.jrpie.android.launcher.preferences.LauncherPreferences +import de.jrpie.android.launcher.requestNotificationPermission import de.jrpie.android.launcher.setDefaultHomeScreen import de.jrpie.android.launcher.ui.UIObject @@ -31,8 +32,10 @@ class TutorialFragment5Finish : Fragment(), UIObject { override fun onStart() { super.onStart() super.onStart() + requestNotificationPermission(requireActivity()) } + override fun setOnClicks() { super.setOnClicks() binding.tutorialFinishButtonStart.setOnClickListener { finishTutorial() } @@ -44,6 +47,7 @@ class TutorialFragment5Finish : Fragment(), UIObject { LauncherPreferences.internal().startedTime(System.currentTimeMillis() / 1000L) } context?.let { setDefaultHomeScreen(it, checkDefault = true) } + activity?.finish() } } diff --git a/app/src/main/res/drawable/baseline_bug_report_24.xml b/app/src/main/res/drawable/baseline_bug_report_24.xml new file mode 100644 index 0000000..ef399ba --- /dev/null +++ b/app/src/main/res/drawable/baseline_bug_report_24.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/layout/activity_report_crash.xml b/app/src/main/res/layout/activity_report_crash.xml new file mode 100644 index 0000000..c7f2110 --- /dev/null +++ b/app/src/main/res/layout/activity_report_crash.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + +