From ba4465512c3d686af63c169355d76613d79ad7db Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Wed, 7 Feb 2024 23:31:08 +0530 Subject: [PATCH] feat: homewidget for android --- android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 7 ++ .../kotlin/io/ente/photos/MainActivity.kt | 4 +- .../io/ente/photos/SlideshowWidgetProvider.kt | 58 ++++++++++++++ .../main/res/drawable/widget_background.xml | 5 ++ .../src/main/res/layout/slideshow_layout.xml | 31 ++++++++ .../app/src/main/res/xml/slideshow_widget.xml | 9 +++ lib/services/favorites_service.dart | 5 ++ lib/ui/tabs/home_widget.dart | 75 +++++++++++++++++-- lib/ui/viewer/file/zoomable_image.dart | 1 - .../search/result/no_result_widget.dart | 1 + pubspec.lock | 12 ++- pubspec.yaml | 3 +- 13 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 android/app/src/main/kotlin/io/ente/photos/SlideshowWidgetProvider.kt create mode 100644 android/app/src/main/res/drawable/widget_background.xml create mode 100644 android/app/src/main/res/layout/slideshow_layout.xml create mode 100644 android/app/src/main/res/xml/slideshow_widget.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index e2a33381c..a241cbe7e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 ndkVersion "26.0.10792818" sourceSets { @@ -57,7 +57,7 @@ android { signingConfigs { release { - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH")) + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS") keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD") storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD") diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4672bed21..8530c82e3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -85,6 +85,13 @@ android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" /> + + + + + + diff --git a/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt b/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt index 28241499b..8200954d3 100644 --- a/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt +++ b/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt @@ -1,10 +1,10 @@ package io.ente.photos -import io.flutter.embedding.android.FlutterFragmentActivity; +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity: FlutterFragmentActivity() { +class MainActivity : FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) } diff --git a/android/app/src/main/kotlin/io/ente/photos/SlideshowWidgetProvider.kt b/android/app/src/main/kotlin/io/ente/photos/SlideshowWidgetProvider.kt new file mode 100644 index 000000000..2be6dc4d5 --- /dev/null +++ b/android/app/src/main/kotlin/io/ente/photos/SlideshowWidgetProvider.kt @@ -0,0 +1,58 @@ +package io.ente.photos + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.SharedPreferences +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import es.antonborri.home_widget.HomeWidgetLaunchIntent +import es.antonborri.home_widget.HomeWidgetProvider +import java.io.File + +class SlideshowWidgetProvider : HomeWidgetProvider() { + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray, + widgetData: SharedPreferences + ) { + appWidgetIds.forEach { widgetId -> + val views = + RemoteViews(context.packageName, R.layout.slideshow_layout).apply { + // Open App on Widget Click + val pendingIntent = + HomeWidgetLaunchIntent.getActivity( + context, + MainActivity::class.java + ) + setOnClickPendingIntent(R.id.widget_container, pendingIntent) + + // Show Images saved with `renderFlutterWidget` + val imagePath = widgetData.getString("slideshow", null) + var imageExists: Boolean = false + if (imagePath != null) { + val imageFile = File(imagePath) + imageExists = imageFile.exists() + } + if (imageExists) { + Log.d("SlideshowWidgetProvider", "Image exists: $imagePath") + setViewVisibility(R.id.widget_img, View.VISIBLE) + setViewVisibility(R.id.widget_title, View.GONE) + + val myBitmap: Bitmap = BitmapFactory.decodeFile(imagePath) + setImageViewBitmap(R.id.widget_img, myBitmap) + } else { + Log.d("SlideshowWidgetProvider", "Image doesn't exists: $imagePath") + setViewVisibility(R.id.widget_img, View.GONE) + setViewVisibility(R.id.widget_title, View.VISIBLE) + setTextViewText(R.id.widget_title, "No Image Loaded") + } + } + + appWidgetManager.updateAppWidget(widgetId, views) + } + } +} diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 000000000..6cc47c13d --- /dev/null +++ b/android/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/slideshow_layout.xml b/android/app/src/main/res/layout/slideshow_layout.xml new file mode 100644 index 000000000..7d5f688a7 --- /dev/null +++ b/android/app/src/main/res/layout/slideshow_layout.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/slideshow_widget.xml b/android/app/src/main/res/xml/slideshow_widget.xml new file mode 100644 index 000000000..27ba2fac4 --- /dev/null +++ b/android/app/src/main/res/xml/slideshow_widget.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/lib/services/favorites_service.dart b/lib/services/favorites_service.dart index 976847ae3..5388a6e67 100644 --- a/lib/services/favorites_service.dart +++ b/lib/services/favorites_service.dart @@ -210,6 +210,11 @@ class FavoritesService { return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID!); } + Future getFavoriteCollectionID() async { + final collection = await _getFavoritesCollection(); + return collection?.id; + } + Future _getOrCreateFavoriteCollectionID() async { if (_cachedFavoritesCollectionID != null) { return _cachedFavoritesCollectionID!; diff --git a/lib/ui/tabs/home_widget.dart b/lib/ui/tabs/home_widget.dart index ca66011da..018f96185 100644 --- a/lib/ui/tabs/home_widget.dart +++ b/lib/ui/tabs/home_widget.dart @@ -1,17 +1,21 @@ import 'dart:async'; import 'dart:io'; +import "dart:math"; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import "package:flutter_animate/flutter_animate.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart"; +import 'package:home_widget/home_widget.dart' as hw; import 'package:logging/logging.dart'; import 'package:media_extension/media_extension_action_types.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:move_to_background/move_to_background.dart'; import 'package:photos/core/configuration.dart'; +import "package:photos/core/constants.dart"; import 'package:photos/core/event_bus.dart'; +import "package:photos/db/files_db.dart"; import 'package:photos/ente_theme_data.dart'; import 'package:photos/events/account_configured_event.dart'; import 'package:photos/events/backup_folders_updated_event.dart'; @@ -29,6 +33,7 @@ import 'package:photos/models/selected_files.dart'; import 'package:photos/services/app_lifecycle_service.dart'; import 'package:photos/services/collections_service.dart'; import "package:photos/services/entity_service.dart"; +import "package:photos/services/favorites_service.dart"; import 'package:photos/services/local_sync_service.dart'; import "package:photos/services/notification_service.dart"; import 'package:photos/services/update_service.dart'; @@ -56,10 +61,13 @@ import "package:photos/ui/viewer/gallery/collection_page.dart"; import 'package:photos/ui/viewer/search/search_widget.dart'; import 'package:photos/utils/dialog_util.dart'; import "package:photos/utils/navigation_util.dart"; +import "package:photos/utils/thumbnail_util.dart"; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import "package:shared_preferences/shared_preferences.dart"; import 'package:uni_links/uni_links.dart'; +final scaffoldKey = GlobalKey(); + class HomeWidget extends StatefulWidget { const HomeWidget({ Key? key, @@ -107,6 +115,11 @@ class _HomeWidgetState extends State { @override void initState() { _logger.info("Building initstate"); + if (FavoritesService.instance.hasFavorites()) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + initHomeWidget(); + }); + } _tabChangedEventSubscription = Bus.instance.on().listen((event) { _selectedTabIndex = event.selectedIndex; @@ -120,11 +133,13 @@ class _HomeWidgetState extends State { debugPrint( "TabChange going from $_selectedTabIndex to ${event.selectedIndex} souce: ${event.source}", ); - _pageController.animateToPage( - event.selectedIndex, - duration: const Duration(milliseconds: 100), - curve: Curves.easeIn, - ); + if (_pageController.hasClients) { + _pageController.animateToPage( + event.selectedIndex, + duration: const Duration(milliseconds: 100), + curve: Curves.easeIn, + ); + } } }); _subscriptionPurchaseEvent = @@ -228,6 +243,56 @@ class _HomeWidgetState extends State { super.initState(); } + Future initHomeWidget() async { + final collectionID = + await FavoritesService.instance.getFavoriteCollectionID(); + final res = await FilesDB.instance.getFilesInCollection( + collectionID!, + galleryLoadStartTime, + galleryLoadEndTime, + ); + + final files = res.files; + final randomNumber = Random().nextInt(files.length); + final randomFile = files[randomNumber]; + final cachedThumbnail = await getThumbnailFromServer(randomFile); + var img = Image.memory(cachedThumbnail); + var imgProvider = img.image; + await precacheImage(imgProvider, context); + img = Image.memory(cachedThumbnail); + imgProvider = img.image; + final image = await decodeImageFromList(cachedThumbnail); + final size = min(image.width.toDouble(), image.height.toDouble()); + + final widget = ClipRRect( + borderRadius: BorderRadius.circular(32), + child: Container( + width: size, + height: size, + decoration: BoxDecoration( + color: Colors.black, + image: DecorationImage(image: imgProvider, fit: BoxFit.cover), + ), + ), + ); + + await hw.HomeWidget.renderFlutterWidget( + widget, + logicalSize: Size(size, size), + key: "slideshow", + ); + + await hw.HomeWidget.updateWidget( + name: 'SlideshowWidgetProvider', + androidName: 'SlideshowWidgetProvider', + qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider', + iOSName: 'SlideshowWidget', + ); + + _logger + .info(">>> HomeWidget rendered with size ${img.width}x${img.height}"); + } + Future _autoLogoutAlert() async { final AlertDialog alert = AlertDialog( title: Text(S.of(context).sessionExpired), diff --git a/lib/ui/viewer/file/zoomable_image.dart b/lib/ui/viewer/file/zoomable_image.dart index 5a1d47b29..ce49830e3 100644 --- a/lib/ui/viewer/file/zoomable_image.dart +++ b/lib/ui/viewer/file/zoomable_image.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:io'; -import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; diff --git a/lib/ui/viewer/search/result/no_result_widget.dart b/lib/ui/viewer/search/result/no_result_widget.dart index a15f616a7..9ebb9cf80 100644 --- a/lib/ui/viewer/search/result/no_result_widget.dart +++ b/lib/ui/viewer/search/result/no_result_widget.dart @@ -31,6 +31,7 @@ class _NoResultWidgetState extends State { InheritedAllSectionsExamples.of(context) .allSectionsExamplesFuture .then((value) { + if (value.isEmpty) return; for (int i = 0; i < searchTypes.length; i++) { final querySuggestions = []; for (int j = 0; j < 2 && j < value[i].length; j++) { diff --git a/pubspec.lock b/pubspec.lock index afc164331..cc7b5a351 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -914,6 +914,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + home_widget: + dependency: "direct main" + description: + name: home_widget + sha256: c58a9e6d3b94490f1a8d5ddcbeeeeebc79abd0befe5889c26b0713fb09be6857 + url: "https://pub.dev" + source: hosted + version: "0.4.1" html: dependency: transitive description: @@ -1638,10 +1646,10 @@ packages: dependency: "direct main" description: name: receive_sharing_intent - sha256: "0a910f5b91fe88b05a80448124c18753df1952c80fe216f5f3cda8296a646567" + sha256: "8fdf5927934041264becf65199ef8057b8b176e879d95ffa0420cd2a6219c0fd" url: "https://pub.dev" source: hosted - version: "1.6.4" + version: "1.6.7" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7a5746dde..badac8992 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,6 +89,7 @@ dependencies: fluttertoast: ^8.0.6 freezed_annotation: ^2.2.0 google_nav_bar: ^5.0.5 + home_widget: ^0.4.1 html_unescape: ^2.0.0 http: ^1.1.0 image: ^4.0.17 @@ -133,7 +134,7 @@ dependencies: pointycastle: ^3.7.3 provider: ^6.0.0 quiver: ^3.0.1 - receive_sharing_intent: ^1.4.5 + receive_sharing_intent: ^1.6.7 scrollable_positioned_list: ^0.3.5 sentry: ^7.9.0 sentry_flutter: ^7.9.0