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