feat: homewidget for android
This commit is contained in:
parent
39fa2543fe
commit
ba4465512c
|
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 34
|
||||||
ndkVersion "26.0.10792818"
|
ndkVersion "26.0.10792818"
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -57,7 +57,7 @@ android {
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
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")
|
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||||
|
|
|
@ -85,6 +85,13 @@
|
||||||
android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
|
android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
|
||||||
<meta-data android:name="firebase_analytics_collection_deactivated"
|
<meta-data android:name="firebase_analytics_collection_deactivated"
|
||||||
android:value="true" />
|
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>
|
</application>
|
||||||
|
|
||||||
<!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
|
<!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.ente.photos
|
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.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||||
|
|
||||||
class MainActivity: FlutterFragmentActivity() {
|
class MainActivity : FlutterFragmentActivity() {
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
GeneratedPluginRegistrant.registerWith(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!);
|
return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int?> getFavoriteCollectionID() async {
|
||||||
|
final collection = await _getFavoritesCollection();
|
||||||
|
return collection?.id;
|
||||||
|
}
|
||||||
|
|
||||||
Future<int> _getOrCreateFavoriteCollectionID() async {
|
Future<int> _getOrCreateFavoriteCollectionID() async {
|
||||||
if (_cachedFavoritesCollectionID != null) {
|
if (_cachedFavoritesCollectionID != null) {
|
||||||
return _cachedFavoritesCollectionID!;
|
return _cachedFavoritesCollectionID!;
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import "dart:math";
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import "package:flutter_animate/flutter_animate.dart";
|
import "package:flutter_animate/flutter_animate.dart";
|
||||||
import "package:flutter_local_notifications/flutter_local_notifications.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:logging/logging.dart';
|
||||||
import 'package:media_extension/media_extension_action_types.dart';
|
import 'package:media_extension/media_extension_action_types.dart';
|
||||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||||
import 'package:move_to_background/move_to_background.dart';
|
import 'package:move_to_background/move_to_background.dart';
|
||||||
import 'package:photos/core/configuration.dart';
|
import 'package:photos/core/configuration.dart';
|
||||||
|
import "package:photos/core/constants.dart";
|
||||||
import 'package:photos/core/event_bus.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/ente_theme_data.dart';
|
||||||
import 'package:photos/events/account_configured_event.dart';
|
import 'package:photos/events/account_configured_event.dart';
|
||||||
import 'package:photos/events/backup_folders_updated_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/app_lifecycle_service.dart';
|
||||||
import 'package:photos/services/collections_service.dart';
|
import 'package:photos/services/collections_service.dart';
|
||||||
import "package:photos/services/entity_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/local_sync_service.dart';
|
||||||
import "package:photos/services/notification_service.dart";
|
import "package:photos/services/notification_service.dart";
|
||||||
import 'package:photos/services/update_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/ui/viewer/search/search_widget.dart';
|
||||||
import 'package:photos/utils/dialog_util.dart';
|
import 'package:photos/utils/dialog_util.dart';
|
||||||
import "package:photos/utils/navigation_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:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
import "package:shared_preferences/shared_preferences.dart";
|
import "package:shared_preferences/shared_preferences.dart";
|
||||||
import 'package:uni_links/uni_links.dart';
|
import 'package:uni_links/uni_links.dart';
|
||||||
|
|
||||||
|
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
class HomeWidget extends StatefulWidget {
|
class HomeWidget extends StatefulWidget {
|
||||||
const HomeWidget({
|
const HomeWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -107,6 +115,11 @@ class _HomeWidgetState extends State<HomeWidget> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_logger.info("Building initstate");
|
_logger.info("Building initstate");
|
||||||
|
if (FavoritesService.instance.hasFavorites()) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
initHomeWidget();
|
||||||
|
});
|
||||||
|
}
|
||||||
_tabChangedEventSubscription =
|
_tabChangedEventSubscription =
|
||||||
Bus.instance.on<TabChangedEvent>().listen((event) {
|
Bus.instance.on<TabChangedEvent>().listen((event) {
|
||||||
_selectedTabIndex = event.selectedIndex;
|
_selectedTabIndex = event.selectedIndex;
|
||||||
|
@ -120,11 +133,13 @@ class _HomeWidgetState extends State<HomeWidget> {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"TabChange going from $_selectedTabIndex to ${event.selectedIndex} souce: ${event.source}",
|
"TabChange going from $_selectedTabIndex to ${event.selectedIndex} souce: ${event.source}",
|
||||||
);
|
);
|
||||||
_pageController.animateToPage(
|
if (_pageController.hasClients) {
|
||||||
event.selectedIndex,
|
_pageController.animateToPage(
|
||||||
duration: const Duration(milliseconds: 100),
|
event.selectedIndex,
|
||||||
curve: Curves.easeIn,
|
duration: const Duration(milliseconds: 100),
|
||||||
);
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_subscriptionPurchaseEvent =
|
_subscriptionPurchaseEvent =
|
||||||
|
@ -228,6 +243,56 @@ class _HomeWidgetState extends State<HomeWidget> {
|
||||||
super.initState();
|
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 {
|
Future<void> _autoLogoutAlert() async {
|
||||||
final AlertDialog alert = AlertDialog(
|
final AlertDialog alert = AlertDialog(
|
||||||
title: Text(S.of(context).sessionExpired),
|
title: Text(S.of(context).sessionExpired),
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
|
@ -31,6 +31,7 @@ class _NoResultWidgetState extends State<NoResultWidget> {
|
||||||
InheritedAllSectionsExamples.of(context)
|
InheritedAllSectionsExamples.of(context)
|
||||||
.allSectionsExamplesFuture
|
.allSectionsExamplesFuture
|
||||||
.then((value) {
|
.then((value) {
|
||||||
|
if (value.isEmpty) return;
|
||||||
for (int i = 0; i < searchTypes.length; i++) {
|
for (int i = 0; i < searchTypes.length; i++) {
|
||||||
final querySuggestions = <String>[];
|
final querySuggestions = <String>[];
|
||||||
for (int j = 0; j < 2 && j < value[i].length; j++) {
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
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:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1638,10 +1646,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: receive_sharing_intent
|
name: receive_sharing_intent
|
||||||
sha256: "0a910f5b91fe88b05a80448124c18753df1952c80fe216f5f3cda8296a646567"
|
sha256: "8fdf5927934041264becf65199ef8057b8b176e879d95ffa0420cd2a6219c0fd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.4"
|
version: "1.6.7"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -89,6 +89,7 @@ dependencies:
|
||||||
fluttertoast: ^8.0.6
|
fluttertoast: ^8.0.6
|
||||||
freezed_annotation: ^2.2.0
|
freezed_annotation: ^2.2.0
|
||||||
google_nav_bar: ^5.0.5
|
google_nav_bar: ^5.0.5
|
||||||
|
home_widget: ^0.4.1
|
||||||
html_unescape: ^2.0.0
|
html_unescape: ^2.0.0
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
image: ^4.0.17
|
image: ^4.0.17
|
||||||
|
@ -133,7 +134,7 @@ dependencies:
|
||||||
pointycastle: ^3.7.3
|
pointycastle: ^3.7.3
|
||||||
provider: ^6.0.0
|
provider: ^6.0.0
|
||||||
quiver: ^3.0.1
|
quiver: ^3.0.1
|
||||||
receive_sharing_intent: ^1.4.5
|
receive_sharing_intent: ^1.6.7
|
||||||
scrollable_positioned_list: ^0.3.5
|
scrollable_positioned_list: ^0.3.5
|
||||||
sentry: ^7.9.0
|
sentry: ^7.9.0
|
||||||
sentry_flutter: ^7.9.0
|
sentry_flutter: ^7.9.0
|
||||||
|
|
Loading…
Reference in a new issue