import 'dart:math'; import 'package:feet/api/api.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:workmanager/workmanager.dart'; @pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((taskName, inputData) async { print("Native called background task: $taskName"); if (taskName == 'fetchNotifications') { final sharedPreferences = await SharedPreferences.getInstance(); try { var initialized = await FlutterLocalNotificationsPlugin().initialize( const InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), ), ); if (initialized == false) { print("Notification plugin did not initialize; cancelling task"); return true; } var apiUrl = sharedPreferences.getString('apiUrl'), apiKey = sharedPreferences.getString('apiKey'), latest = sharedPreferences.getInt('newestKnownPost'); if (apiUrl == null || apiKey == null) { print("API URL or API key not set; cancelling task"); return true; } if (latest == null || latest == 0) { print("newestKnownPost is not set; cancelling task"); return true; } var api = FeverAPI( apiKey: apiKey, apiUrl: apiUrl, sharedPrefs: sharedPreferences, ); var response = await api.request().withItems().sinceId(latest).execute(); var items = api.cache.items.getAll().values.where((post) => !post.isRead); print("Found ${items.length} new unread posts (out of " "${api.cache.items.size} total)"); print(response); // Only fetch feeds and favicons if necessary to avoid wasting bandwidth if (items.isNotEmpty) { print("New posts found, fetching feeds and favicons"); await api.request().withFeeds().withFavicons().execute(); } for (var item in items) { var feed = api.cache.feeds.get(item.feedId); print("Notifying for post: ${item.id}"); AndroidBitmap? iconData; var favicon = api.cache.favicons.get(feed?.faviconId ?? -1); if (favicon != null) { try { iconData = ByteArrayAndroidBitmap.fromBase64String( favicon.data.split(',')[1]); } catch (e) { print("Failed to decode favicon: $e - Continuing"); } } await FlutterLocalNotificationsPlugin().show( Random().nextInt(10000000), // Can't use the post ID here because // FreshRSS's IDs are too large feed != null ? 'New post - ${feed.title}' : 'New post', item.title, NotificationDetails( android: AndroidNotificationDetails( 'new_posts', 'New posts', autoCancel: true, channelShowBadge: true, importance: Importance.low, largeIcon: iconData, when: item.createdOnTime.millisecondsSinceEpoch, showWhen: true, ), ), ); } } catch (e) { print(e); return false; } return true; } return true; }); } void setupBackgroundTasks() async { WidgetsFlutterBinding.ensureInitialized(); await Workmanager().initialize( callbackDispatcher, isInDebugMode: kDebugMode, ); await Permission.notification.request(); // Runs every 15 minutes await Workmanager().registerPeriodicTask( 'fetch_notifications', 'fetchNotifications', constraints: Constraints(networkType: NetworkType.connected), initialDelay: const Duration(seconds: 60), ); }