From 00346ebb3147e49e8dbc81e7e30c70efc08a4b64 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 14 May 2023 18:40:33 +0200 Subject: [PATCH] #638 widget: option to update on tap --- CHANGELOG.md | 1 + .../thibault/aves/HomeWidgetProvider.kt | 83 +++++++++++++------ lib/l10n/app_en.arb | 1 + lib/view/src/settings/enums.dart | 2 + lib/widget_common.dart | 8 +- lib/widgets/home_page.dart | 1 + .../aves_model/lib/src/settings/enums.dart | 2 +- untranslated.json | 55 +++++++++--- 8 files changed, 112 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ca19c7a..5c27ee26a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - option to set the Tags page as home - support for animated PNG - Info: added day filter with item date +- Widget: option to update image on tap ### Changed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt index 20961ea40..c65609042 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt @@ -18,6 +18,7 @@ import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.streams.ImageByteStreamHandler import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler +import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.FlutterUtils import deckers.thibault.aves.utils.LogUtils import io.flutter.FlutterInjector @@ -40,11 +41,11 @@ class HomeWidgetProvider : AppWidgetProvider() { val widgetInfo = appWidgetManager.getAppWidgetOptions(widgetId) defaultScope.launch { - val backgroundBytes = getBytes(context, widgetId, widgetInfo, drawEntryImage = false) - updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundBytes) + val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false) + updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundProps) - val imageBytes = getBytes(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false) - updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageBytes) + val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false) + updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps) } } } @@ -59,8 +60,8 @@ class HomeWidgetProvider : AppWidgetProvider() { } imageByteFetchJob = defaultScope.launch { delay(500) - val imageBytes = getBytes(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true) - updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageBytes) + val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true) + updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps) } } @@ -76,13 +77,13 @@ class HomeWidgetProvider : AppWidgetProvider() { return Pair(widthPx, heightPx) } - private suspend fun getBytes( + private suspend fun getProps( context: Context, widgetId: Int, widgetInfo: Bundle, drawEntryImage: Boolean, reuseEntry: Boolean = false, - ): ByteArray? { + ): FieldMap? { val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo) if (widthPx == 0 || heightPx == 0) return null @@ -90,7 +91,7 @@ class HomeWidgetProvider : AppWidgetProvider() { val messenger = flutterEngine!!.dartExecutor val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL) try { - val bytes = suspendCoroutine { cont -> + val props = suspendCoroutine { cont -> defaultScope.launch { FlutterUtils.runOnUiThread { channel.invokeMethod("drawWidget", hashMapOf( @@ -116,7 +117,8 @@ class HomeWidgetProvider : AppWidgetProvider() { } } } - if (bytes is ByteArray) return bytes + @Suppress("unchecked_cast") + return props as FieldMap? } catch (e: Exception) { Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId widthPx=$widthPx heightPx=$heightPx", e) } @@ -128,9 +130,16 @@ class HomeWidgetProvider : AppWidgetProvider() { appWidgetManager: AppWidgetManager, widgetId: Int, widgetInfo: Bundle, - bytes: ByteArray?, + props: FieldMap?, ) { - bytes ?: return + props ?: return + + val bytes = props["bytes"] as ByteArray? + val updateOnTap = props["updateOnTap"] as Boolean? + if (bytes == null || updateOnTap == null) { + Log.e(LOG_TAG, "missing arguments") + return + } val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo) if (widthPx == 0 || heightPx == 0) return @@ -139,24 +148,11 @@ class HomeWidgetProvider : AppWidgetProvider() { val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888) bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes)) - // set a unique URI to prevent the intent (and its extras) from being shared by different widgets - val intent = Intent(MainActivity.INTENT_ACTION_WIDGET_OPEN, Uri.parse("widget://$widgetId"), context, MainActivity::class.java) - .putExtra(MainActivity.EXTRA_KEY_WIDGET_ID, widgetId) - - val activity = PendingIntent.getActivity( - context, - 0, - intent, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_UPDATE_CURRENT - } - ) + val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId) val views = RemoteViews(context.packageName, R.layout.app_widget).apply { setImageViewBitmap(R.id.widget_img, bitmap) - setOnClickPendingIntent(R.id.widget_img, activity) + setOnClickPendingIntent(R.id.widget_img, pendingIntent) } appWidgetManager.updateAppWidget(widgetId, views) @@ -166,6 +162,39 @@ class HomeWidgetProvider : AppWidgetProvider() { } } + private fun buildUpdateIntent(context: Context, widgetId: Int): PendingIntent { + val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, Uri.parse("widget://$widgetId"), context, HomeWidgetProvider::class.java) + .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(widgetId)) + + return PendingIntent.getBroadcast( + context, + 0, + intent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + ) + } + + private fun buildOpenAppIntent(context: Context, widgetId: Int): PendingIntent { + // set a unique URI to prevent the intent (and its extras) from being shared by different widgets + val intent = Intent(MainActivity.INTENT_ACTION_WIDGET_OPEN, Uri.parse("widget://$widgetId"), context, MainActivity::class.java) + .putExtra(MainActivity.EXTRA_KEY_WIDGET_ID, widgetId) + + return PendingIntent.getActivity( + context, + 0, + intent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + ) + } + companion object { private val LOG_TAG = LogUtils.createTag() private const val WIDGET_DART_ENTRYPOINT = "widgetMain" diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a83d54d89..bcd049bbd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -281,6 +281,7 @@ "widgetOpenPageHome": "Open home", "widgetOpenPageCollection": "Open collection", "widgetOpenPageViewer": "Open viewer", + "widgetTapUpdateWidget": "Update widget", "storageVolumeDescriptionFallbackPrimary": "Internal storage", "storageVolumeDescriptionFallbackNonPrimary": "SD card", diff --git a/lib/view/src/settings/enums.dart b/lib/view/src/settings/enums.dart index 3ac2afdf4..dedfc4018 100644 --- a/lib/view/src/settings/enums.dart +++ b/lib/view/src/settings/enums.dart @@ -275,6 +275,8 @@ extension ExtraWidgetOpenPageView on WidgetOpenPage { return context.l10n.widgetOpenPageCollection; case WidgetOpenPage.viewer: return context.l10n.widgetOpenPageViewer; + case WidgetOpenPage.updateWidget: + return context.l10n.widgetTapUpdateWidget; } } } diff --git a/lib/widget_common.dart b/lib/widget_common.dart index 4ea33f334..4197f0311 100644 --- a/lib/widget_common.dart +++ b/lib/widget_common.dart @@ -34,7 +34,7 @@ void widgetMainCommon(AppFlavor flavor) async { }); } -Future _drawWidget(dynamic args) async { +Future> _drawWidget(dynamic args) async { final widgetId = args['widgetId'] as int; final widthPx = args['widthPx'] as int; final heightPx = args['heightPx'] as int; @@ -47,12 +47,16 @@ Future _drawWidget(dynamic args) async { entry: entry, devicePixelRatio: devicePixelRatio, ); - return painter.drawWidget( + final bytes = await painter.drawWidget( widthPx: widthPx, heightPx: heightPx, outline: settings.getWidgetOutline(widgetId), shape: settings.getWidgetShape(widgetId), ); + return { + 'bytes': bytes, + 'updateOnTap': settings.getWidgetOpenPage(widgetId) == WidgetOpenPage.updateWidget, + }; } Future _getWidgetEntry(int widgetId, bool reuseEntry) async { diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index c130f009b..5f55824e1 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -113,6 +113,7 @@ class _HomePageState extends State { final page = settings.getWidgetOpenPage(widgetId); switch (page) { case WidgetOpenPage.home: + case WidgetOpenPage.updateWidget: break; case WidgetOpenPage.collection: _initialFilters = settings.getWidgetCollectionFilters(widgetId); diff --git a/plugins/aves_model/lib/src/settings/enums.dart b/plugins/aves_model/lib/src/settings/enums.dart index 1c868f99d..2508a8f3b 100644 --- a/plugins/aves_model/lib/src/settings/enums.dart +++ b/plugins/aves_model/lib/src/settings/enums.dart @@ -44,6 +44,6 @@ enum ViewerTransition { slide, parallax, fade, zoomIn, none } enum WidgetDisplayedItem { random, mostRecent } -enum WidgetOpenPage { home, collection, viewer } +enum WidgetOpenPage { home, collection, viewer, updateWidget } enum WidgetShape { rrect, circle, heart } diff --git a/untranslated.json b/untranslated.json index 7e2beb5f6..78c4c844a 100644 --- a/untranslated.json +++ b/untranslated.json @@ -178,6 +178,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -765,6 +766,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -1245,6 +1247,7 @@ "maxBrightnessAlways", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "exportEntryDialogQuality", "settingsAskEverytime", "settingsVideoPlaybackTile", @@ -1267,6 +1270,7 @@ "maxBrightnessAlways", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "exportEntryDialogQuality", "settingsAskEverytime", "settingsVideoPlaybackTile", @@ -1284,7 +1288,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "es": [ @@ -1295,7 +1300,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "eu": [ @@ -1306,7 +1312,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "fa": [ @@ -1382,6 +1389,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "rootDirectoryDescription", "otherDirectoryDescription", "restrictedAccessDialogMessage", @@ -1820,7 +1828,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "gl": [ @@ -1891,6 +1900,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -2555,6 +2565,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -3199,6 +3210,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -3674,7 +3686,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "id": [ @@ -3685,7 +3698,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "it": [ @@ -3696,7 +3710,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "ja": [ @@ -3722,6 +3737,7 @@ "subtitlePositionBottom", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "vaultBinUsageDialogMessage", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", @@ -3757,7 +3773,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "lt": [ @@ -3791,6 +3808,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle", @@ -4036,6 +4054,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -4521,6 +4540,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "patternDialogEnter", "patternDialogConfirm", "exportEntryDialogQuality", @@ -4567,6 +4587,7 @@ "vaultLockTypePattern", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle", @@ -4653,6 +4674,7 @@ "videoResumptionModeAlways", "wallpaperTargetHome", "wallpaperTargetHomeLock", + "widgetTapUpdateWidget", "setCoverDialogCustom", "newVaultWarningDialogMessage", "newVaultDialogTitle", @@ -5145,6 +5167,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "widgetTapUpdateWidget", "storageVolumeDescriptionFallbackPrimary", "storageVolumeDescriptionFallbackNonPrimary", "rootDirectoryDescription", @@ -5570,7 +5593,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "pt": [ @@ -5581,7 +5605,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "ro": [ @@ -5597,6 +5622,7 @@ "maxBrightnessAlways", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "exportEntryDialogQuality", "settingsAskEverytime", "settingsVideoPlaybackTile", @@ -5619,6 +5645,7 @@ "maxBrightnessAlways", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "exportEntryDialogQuality", "statePageTitle", "stateEmpty", @@ -5668,6 +5695,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "otherDirectoryDescription", "storageAccessDialogMessage", "restrictedAccessDialogMessage", @@ -6121,6 +6149,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle", @@ -6501,6 +6530,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle", @@ -6547,7 +6577,8 @@ "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", - "cropAspectRatioSquare" + "cropAspectRatioSquare", + "widgetTapUpdateWidget" ], "zh": [ @@ -6575,6 +6606,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle", @@ -6649,6 +6681,7 @@ "settingsVideoEnablePip", "videoResumptionModeNever", "videoResumptionModeAlways", + "widgetTapUpdateWidget", "newVaultWarningDialogMessage", "newVaultDialogTitle", "configureVaultDialogTitle",