feat: homewidget for android

This commit is contained in:
Prateek Sunal 2024-02-07 23:31:08 +05:30
parent 39fa2543fe
commit ba4465512c
13 changed files with 202 additions and 13 deletions

View file

@ -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")

View file

@ -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 -->

View file

@ -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)
}

View file

@ -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)
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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!;

View file

@ -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),

View file

@ -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';

View file

@ -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++) {

View file

@ -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:

View file

@ -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