From 3e80ec37deb78f1cdf6e8edc38553daa5906a32c Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Tue, 8 Aug 2023 15:44:37 +0200 Subject: [PATCH] Add home widget feature displaying the first four missing anime --- android/app/build.gradle | 1 + android/app/src/main/AndroidManifest.xml | 8 ++ .../fr/ziedelth/jais/HomeWidgetProvider.kt | 74 ++++++++++++++++++ .../res/drawable/home_widget_background.xml | 5 ++ .../main/res/layout/home_widget_layout.xml | 47 ++++++++++++ .../src/main/res/xml/home_widget_provider.xml | 9 +++ .../animes/missing_anime_controller.dart | 7 +- lib/controllers/app_controller.dart | 5 ++ lib/controllers/episode_tab_controller.dart | 4 +- lib/controllers/home_widget_controller.dart | 76 +++++++++++++++++++ lib/main.dart | 16 +++- lib/widgets/episodes/episode_see_widget.dart | 1 + lib/widgets/watchlist_button.dart | 3 + pubspec.lock | 9 +++ pubspec.yaml | 6 +- 15 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 android/app/src/main/kotlin/fr/ziedelth/jais/HomeWidgetProvider.kt create mode 100644 android/app/src/main/res/drawable/home_widget_background.xml create mode 100644 android/app/src/main/res/layout/home_widget_layout.xml create mode 100644 android/app/src/main/res/xml/home_widget_provider.xml create mode 100644 lib/controllers/home_widget_controller.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 5e4aedaa..17e44ad9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -82,4 +82,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 141c6ce1..3e204d40 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,6 +30,14 @@ + + + + + + + diff --git a/android/app/src/main/kotlin/fr/ziedelth/jais/HomeWidgetProvider.kt b/android/app/src/main/kotlin/fr/ziedelth/jais/HomeWidgetProvider.kt new file mode 100644 index 00000000..b5642ba6 --- /dev/null +++ b/android/app/src/main/kotlin/fr/ziedelth/jais/HomeWidgetProvider.kt @@ -0,0 +1,74 @@ +package fr.ziedelth.jais + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.SharedPreferences +import android.graphics.BitmapFactory +import android.net.Uri +import android.view.View +import android.widget.RemoteViews + +import es.antonborri.home_widget.HomeWidgetBackgroundIntent +import es.antonborri.home_widget.HomeWidgetLaunchIntent +import es.antonborri.home_widget.HomeWidgetProvider + +class HomeWidgetProvider : HomeWidgetProvider() { + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) { + appWidgetIds.forEach { widgetId -> + val views = RemoteViews(context.packageName, R.layout.home_widget_layout).apply { + // Open App on Widget Click + val pendingIntent = HomeWidgetLaunchIntent.getActivity( + context, + MainActivity::class.java) + setOnClickPendingIntent(R.id.home_widget_container, pendingIntent) + + // Show Images saved with `renderFlutterWidget` + val image1 = widgetData.getString("image0", null) + + if (image1 != null) { + setImageViewBitmap(R.id.widget_img1, BitmapFactory.decodeFile(image1)) + setViewVisibility(R.id.widget_img1, View.VISIBLE) + } else { + setViewVisibility(R.id.widget_img1, View.GONE) + } + + val image2 = widgetData.getString("image1", null) + + if (image2 != null) { + setImageViewBitmap(R.id.widget_img2, BitmapFactory.decodeFile(image2)) + setViewVisibility(R.id.widget_img2, View.VISIBLE) + } else { + setViewVisibility(R.id.widget_img2, View.GONE) + } + + val image3 = widgetData.getString("image2", null) + + if (image3 != null) { + setImageViewBitmap(R.id.widget_img3, BitmapFactory.decodeFile(image3)) + setViewVisibility(R.id.widget_img3, View.VISIBLE) + } else { + setViewVisibility(R.id.widget_img3, View.GONE) + } + + val image4 = widgetData.getString("image3", null) + + if (image4 != null) { + setImageViewBitmap(R.id.widget_img4, BitmapFactory.decodeFile(image4)) + setViewVisibility(R.id.widget_img4, View.VISIBLE) + } else { + setViewVisibility(R.id.widget_img4, View.GONE) + } + + // Nothing in your watchlist yet + if (image1 != null || image2 != null || image3 != null || image4 != null) { + setViewVisibility(R.id.widget_nothing, View.GONE) + } else { + setViewVisibility(R.id.widget_nothing, View.VISIBLE) + setTextViewText(R.id.widget_nothing, "Nothing in your watchlist yet") + } + } + + appWidgetManager.updateAppWidget(widgetId, views) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/home_widget_background.xml b/android/app/src/main/res/drawable/home_widget_background.xml new file mode 100644 index 00000000..ca954faa --- /dev/null +++ b/android/app/src/main/res/drawable/home_widget_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/home_widget_layout.xml b/android/app/src/main/res/layout/home_widget_layout.xml new file mode 100644 index 00000000..ea3d0040 --- /dev/null +++ b/android/app/src/main/res/layout/home_widget_layout.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/home_widget_provider.xml b/android/app/src/main/res/xml/home_widget_provider.xml new file mode 100644 index 00000000..503011d8 --- /dev/null +++ b/android/app/src/main/res/xml/home_widget_provider.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/lib/controllers/animes/missing_anime_controller.dart b/lib/controllers/animes/missing_anime_controller.dart index d07d2589..081836ab 100644 --- a/lib/controllers/animes/missing_anime_controller.dart +++ b/lib/controllers/animes/missing_anime_controller.dart @@ -8,9 +8,10 @@ import 'package:jais/widgets/animes/missing_anime_widget.dart'; class MissingAnimeController extends DataController with AbstractFilter { - MissingAnimeController({required super.notifyListenersCallback}) - : super( - limit: 12, + MissingAnimeController({ + required super.notifyListenersCallback, + super.limit = 12, + }) : super( loadingWidget: const MissingAnimeLoaderWidget(), fromJson: (json) => MissingAnime.fromJson(json), toWidget: (anime) => MissingAnimeWidget(missingAnime: anime), diff --git a/lib/controllers/app_controller.dart b/lib/controllers/app_controller.dart index 03caf81d..34c1c6ae 100644 --- a/lib/controllers/app_controller.dart +++ b/lib/controllers/app_controller.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:jais/controllers/datas/collection_data_controller.dart'; import 'package:jais/controllers/filter_controller.dart'; +import 'package:jais/controllers/home_widget_controller.dart'; import 'package:jais/controllers/url_controller.dart'; import 'package:jais/firebase_options.dart'; import 'package:jais/utils.dart'; @@ -13,6 +14,8 @@ class AppController with ChangeNotifier { CollectionDataController('animeWatchlist'); static final CollectionDataController seen = CollectionDataController('episodesSeen'); + static final HomeWidgetController homeWidgetController = + HomeWidgetController(); bool _inProgress = true; bool _hasInternet = false; @@ -23,6 +26,7 @@ class AppController with ChangeNotifier { AppController() { checkInternetConnection(); + homeWidgetController.init(); } Future checkInternetConnection() async { @@ -48,5 +52,6 @@ class AppController with ChangeNotifier { await FirebaseMessaging.instance.requestPermission(); await FirebaseMessaging.instance.subscribeToTopic('all'); + await homeWidgetController.notify(); } } diff --git a/lib/controllers/episode_tab_controller.dart b/lib/controllers/episode_tab_controller.dart index 43dff544..dc043663 100644 --- a/lib/controllers/episode_tab_controller.dart +++ b/lib/controllers/episode_tab_controller.dart @@ -17,8 +17,8 @@ class EpisodeTabController with ChangeNotifier { episodeController.reset(); notify(); - missingAnimeController.load().whenComplete(() => notify()); - episodeController.load().whenComplete(() => notify()); + missingAnimeController.load().whenComplete(notify); + episodeController.load().whenComplete(notify); } void notify() { diff --git a/lib/controllers/home_widget_controller.dart b/lib/controllers/home_widget_controller.dart new file mode 100644 index 00000000..3f1a3c17 --- /dev/null +++ b/lib/controllers/home_widget_controller.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:home_widget/home_widget.dart'; +import 'package:jais/controllers/animes/missing_anime_controller.dart'; +import 'package:jais/controllers/logger.dart'; +import 'package:jais/models/anime.dart'; +import 'package:jais/utils.dart'; +import 'package:jais/widgets/animes/anime_image.dart'; +import 'package:jais/widgets/animes/missing_anime_widget.dart'; + +class HomeWidgetController { + late MissingAnimeController missingAnimeController; + static const int limit = 4; + + void init() { + missingAnimeController = + MissingAnimeController(limit: 4, notifyListenersCallback: () {}); + } + + Future notify() async { + missingAnimeController.reset(); + await missingAnimeController.load(); + + try { + final List animeImages = []; + + for (int i = 0; i < limit; i++) { + final MissingAnimeWidget? missingAnimeWidget = + missingAnimeController.list.elementAtOrNull(i) + as MissingAnimeWidget?; + + if (missingAnimeWidget == null) { + break; + } + + final Anime anime = missingAnimeWidget.missingAnime.anime; + + animeImages.add( + AnimeImage( + anime: anime, + width: Const.missingAnimeImageWith * 10, + height: Const.missingAnimeImageHeight * 10, + radius: 360, + ), + ); + } + + info( + 'EpisodeTabController', + 'updateHomeWidget() - ${animeImages.length} images', + ); + + if (animeImages.isNotEmpty) { + await Future.wait([ + for (final AnimeImage image in animeImages) + HomeWidget.renderFlutterWidget( + image, + logicalSize: const Size( + Const.missingAnimeImageWith * 10, + Const.missingAnimeImageHeight * 10, + ), + key: 'image${animeImages.indexOf(image)}', + ), + ]); + } else { + await Future.wait([ + for (int i = 0; i < limit; i++) + HomeWidget.saveWidgetData('image$i', null), + ]); + } + + await HomeWidget.updateWidget(name: 'HomeWidgetProvider'); + } catch (exception, stackTrace) { + error('EpisodeTabController', '$exception', exception, stackTrace); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index a3a757b8..91987da4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:home_widget/home_widget.dart'; import 'package:jais/controllers/animes/anime_detail_controller.dart'; import 'package:jais/controllers/animes/anime_diary_controller.dart'; import 'package:jais/controllers/animes/anime_search_controller.dart'; @@ -24,11 +25,22 @@ Future main() async { ); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { static const Color _mainWhiteThemeColor = Color(0xFFa32d26); static const Color _mainDarkThemeColor = Color(0xFFfde5c9); - const MyApp({super.key}); + @override + void initState() { + super.initState(); + HomeWidget.setAppGroupId('jais'); + } @override Widget build(BuildContext context) { diff --git a/lib/widgets/episodes/episode_see_widget.dart b/lib/widgets/episodes/episode_see_widget.dart index 0c0709b0..ac860c04 100644 --- a/lib/widgets/episodes/episode_see_widget.dart +++ b/lib/widgets/episodes/episode_see_widget.dart @@ -25,6 +25,7 @@ class _EpisodeSeeWidgetState extends State { } setState(() {}); + await AppController.homeWidgetController.notify(); }, child: Icon( isWatched ? Icons.visibility : Icons.visibility_off, diff --git a/lib/widgets/watchlist_button.dart b/lib/widgets/watchlist_button.dart index 77578558..93488011 100644 --- a/lib/widgets/watchlist_button.dart +++ b/lib/widgets/watchlist_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:jais/controllers/app_controller.dart'; class WatchlistButton extends StatefulWidget { final bool inWatchlist; @@ -47,6 +48,8 @@ class _WatchlistButtonState extends State { setState(() { _inWatchlist = !_inWatchlist; }); + + await AppController.homeWidgetController.notify(); }, ); } diff --git a/pubspec.lock b/pubspec.lock index 5045c677..a303708c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -408,6 +408,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + home_widget: + dependency: "direct main" + description: + path: "." + ref: e02d386487bf47324fb7ca5112eccb345a26cf06 + resolved-ref: e02d386487bf47324fb7ca5112eccb345a26cf06 + url: "https://github.com/Ziedelth/flutter_home_widget.git" + source: git + version: "0.3.0" html: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c6a4f20f..b4942a22 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,10 @@ dependencies: flutter: sdk: flutter flutter_svg: ^2.0.7 + home_widget: + git: + url: https://github.com/Ziedelth/flutter_home_widget.git + ref: e02d386487bf47324fb7ca5112eccb345a26cf06 http: ^0.13.6 intl: ^0.18.1 json_annotation: ^4.8.1 @@ -25,9 +29,9 @@ dependencies: dev_dependencies: build_runner: ^2.4.6 - flutterando_metrics: ^5.7.6 flutter_launcher_icons: ^0.13.1 flutter_native_splash: ^2.3.2 + flutterando_metrics: ^5.7.6 json_serializable: ^6.7.1 lint: ^2.1.2