feet/lib/notifications/tasks.dart
2023-02-05 21:44:14 +01:00

205 lines
6 KiB
Dart

import 'dart:math';
import 'package:feet/api/api.dart';
import 'package:feet/main.dart';
import 'package:feet/pages/article.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('notification_icon'),
),
);
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<Object>? 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,
payload: 'item/${item.id}',
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();
await FlutterLocalNotificationsPlugin().initialize(
const InitializationSettings(
android: AndroidInitializationSettings('notification_icon'),
),
onSelectNotification: handleNotificationClick,
);
var launchDetails =
await FlutterLocalNotificationsPlugin().getNotificationAppLaunchDetails();
if (launchDetails?.payload != null && launchDetails!.payload!.isNotEmpty) {
handleNotificationClick(launchDetails.payload);
}
// Test notification
//if (kDebugMode) {
// await FlutterLocalNotificationsPlugin().show(
// Random().nextInt(1000000),
// "Test Notification",
// "Test Notification Body",
// const NotificationDetails(
// android: AndroidNotificationDetails(
// 'new_posts',
// 'New posts',
// ),
// ),
// payload: "item/1675609277381551");
//}
// Runs every 15 minutes
await Workmanager().registerPeriodicTask(
'fetch_notifications',
'fetchNotifications',
constraints: Constraints(networkType: NetworkType.connected),
initialDelay: const Duration(seconds: 60),
);
}
Future<void> handleNotificationClick(String? payload) async {
print("Notification click: $payload");
if (globalContext == null) {
print("Global context not available.");
return;
}
if (payload == null) {
print("Didn't receive a payload");
return;
}
try {
if (payload.startsWith('item/')) {
if (!api.loggedIn()) {
print("API client is not logged in");
return;
}
var id = toInt(payload.split('/')[1]);
var item = api.cache.items.get(id);
if (item == null) {
var res = await api.request().withItems().withIds([id]).execute();
item = api.cache.items.get(id);
if (item == null) throw 'Didn\'t receive item $id from API: $res';
}
Navigator.of(globalContext!).push(
MaterialPageRoute(
builder: (context) => ArticlePage(article: item!),
),
);
if (!item.isRead) {
await api
.request()
.markItem(item, ItemMarkType.read)
.execute();
}
}
} catch (e) {
print("Error while handling notification click: $e");
}
}