feat: homewidget for android
This commit is contained in:
parent
39fa2543fe
commit
ba4465512c
13 changed files with 202 additions and 13 deletions
|
@ -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")
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
android/app/src/main/res/drawable/widget_background.xml
Normal file
5
android/app/src/main/res/drawable/widget_background.xml
Normal 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>
|
31
android/app/src/main/res/layout/slideshow_layout.xml
Normal file
31
android/app/src/main/res/layout/slideshow_layout.xml
Normal 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>
|
9
android/app/src/main/res/xml/slideshow_widget.xml
Normal file
9
android/app/src/main/res/xml/slideshow_widget.xml
Normal 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>
|
|
@ -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!;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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++) {
|
||||
|
|
12
pubspec.lock
12
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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue