Explorar o código

feat: homewidget for android

Prateek Sunal hai 1 ano
pai
achega
ba4465512c

+ 2 - 2
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")

+ 7 - 0
android/app/src/main/AndroidManifest.xml

@@ -85,6 +85,13 @@
             android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
         <meta-data android:name="firebase_analytics_collection_deactivated"
             android:value="true" />
+        <receiver android:name="SlideshowWidgetProvider" android:exported="true">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/slideshow_widget" />
+        </receiver>
     </application>
 
     <!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->

+ 2 - 2
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)
     }

+ 58 - 0
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)
+        }
+    }
+}

+ 5 - 0
android/app/src/main/res/drawable/widget_background.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+        <solid android:color="#FFFFFF"/>
+    <corners android:radius="16dp"/>
+</shape>

+ 31 - 0
android/app/src/main/res/layout/slideshow_layout.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:id="@+id/widget_container">
+
+    <TextView
+        android:id="@+id/widget_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="20sp"
+        android:textStyle="bold"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:background="@drawable/widget_background"
+        tools:visibility="visible"
+        tools:text="Title" />
+
+    <ImageView
+        android:id="@+id/widget_img"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="fitCenter"
+        android:adjustViewBounds="true"
+        android:visibility="visible"
+        tools:visibility="visible"
+        />
+</LinearLayout>

+ 9 - 0
android/app/src/main/res/xml/slideshow_widget.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="100dp"
+    android:minHeight="100dp"
+    android:updatePeriodMillis="600000"
+    android:initialLayout="@layout/slideshow_layout"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>

+ 5 - 0
lib/services/favorites_service.dart

@@ -210,6 +210,11 @@ class FavoritesService {
     return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID!);
   }
 
+  Future<int?> getFavoriteCollectionID() async {
+    final collection = await _getFavoritesCollection();
+    return collection?.id;
+  }
+
   Future<int> _getOrCreateFavoriteCollectionID() async {
     if (_cachedFavoritesCollectionID != null) {
       return _cachedFavoritesCollectionID!;

+ 70 - 5
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<ScaffoldState>();
+
 class HomeWidget extends StatefulWidget {
   const HomeWidget({
     Key? key,
@@ -107,6 +115,11 @@ class _HomeWidgetState extends State<HomeWidget> {
   @override
   void initState() {
     _logger.info("Building initstate");
+    if (FavoritesService.instance.hasFavorites()) {
+      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+        initHomeWidget();
+      });
+    }
     _tabChangedEventSubscription =
         Bus.instance.on<TabChangedEvent>().listen((event) {
       _selectedTabIndex = event.selectedIndex;
@@ -120,11 +133,13 @@ class _HomeWidgetState extends State<HomeWidget> {
         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<HomeWidget> {
     super.initState();
   }
 
+  Future<void> 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<void> _autoLogoutAlert() async {
     final AlertDialog alert = AlertDialog(
       title: Text(S.of(context).sessionExpired),

+ 0 - 1
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';

+ 1 - 0
lib/ui/viewer/search/result/no_result_widget.dart

@@ -31,6 +31,7 @@ class _NoResultWidgetState extends State<NoResultWidget> {
     InheritedAllSectionsExamples.of(context)
         .allSectionsExamplesFuture
         .then((value) {
+      if (value.isEmpty) return;
       for (int i = 0; i < searchTypes.length; i++) {
         final querySuggestions = <String>[];
         for (int j = 0; j < 2 && j < value[i].length; j++) {

+ 10 - 2
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:

+ 2 - 1
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