205 lines
6 KiB
Dart
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");
|
|
}
|
|
}
|