Implement background notifications, I think
This commit is contained in:
parent
3270f5d888
commit
4ec79e09e0
|
@ -1,6 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="xyz.janderedev.feet">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -41,5 +42,12 @@
|
|||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'feed.dart';
|
||||
part 'item.dart';
|
||||
|
@ -21,12 +22,16 @@ class FeverAPI {
|
|||
String? apiKey;
|
||||
String? apiUrl;
|
||||
|
||||
late final APICache cache;
|
||||
|
||||
/// We use a shared HTTP client for all requests
|
||||
final _httpClient = http.Client();
|
||||
|
||||
late final APICache cache;
|
||||
/// If set, the `newestKnownPost` value will be updated automatically.
|
||||
/// This is used for notifications.
|
||||
SharedPreferences? sharedPrefs;
|
||||
|
||||
FeverAPI({this.apiKey, this.apiUrl}) {
|
||||
FeverAPI({this.apiKey, this.apiUrl, this.sharedPrefs}) {
|
||||
cache = APICache(this);
|
||||
}
|
||||
|
||||
|
@ -64,19 +69,20 @@ class FeverAPI {
|
|||
}
|
||||
|
||||
APIRequestBuilder request() {
|
||||
return APIRequestBuilder(this, _httpClient);
|
||||
return APIRequestBuilder(this, _httpClient, sharedPrefs);
|
||||
}
|
||||
}
|
||||
|
||||
class APIRequestBuilder {
|
||||
final FeverAPI api;
|
||||
final SharedPreferences? _sharedPrefs;
|
||||
final http.Client _httpClient;
|
||||
|
||||
List<String> args = [];
|
||||
Item? _markedItem;
|
||||
ItemMarkType? _markedItemAs;
|
||||
|
||||
APIRequestBuilder(this.api, this._httpClient);
|
||||
APIRequestBuilder(this.api, this._httpClient, this._sharedPrefs);
|
||||
|
||||
void _addArg(String arg) {
|
||||
if (!args.contains(arg)) args.add(arg);
|
||||
|
@ -173,12 +179,19 @@ class APIRequestBuilder {
|
|||
}
|
||||
|
||||
if (data.containsKey('items')) {
|
||||
var currentHighest = _sharedPrefs?.getInt('newestKnownPost') ?? 0;
|
||||
|
||||
List<Item> items = (data['items'] as List<dynamic>)
|
||||
.map((json) => Item.fromJSON(api, json))
|
||||
.toList();
|
||||
|
||||
for (var item in items) {
|
||||
if (item.id > currentHighest) {
|
||||
currentHighest = item.id;
|
||||
}
|
||||
|
||||
api.cache.items.set(item.id, item);
|
||||
_sharedPrefs?.setInt('newestKnownPost', currentHighest);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:feet/notifications/tasks.dart';
|
||||
import 'package:feet/widgets/centered_page_hint.dart';
|
||||
import 'package:feet/widgets/filter_menu.dart';
|
||||
import 'package:feet/widgets/homepage_post.dart';
|
||||
|
@ -15,6 +16,7 @@ import 'api/api.dart';
|
|||
const FETCH_MAX_POSTS = 1000;
|
||||
|
||||
void main() {
|
||||
setupBackgroundTasks();
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
|
@ -27,6 +29,7 @@ class MyApp extends StatelessWidget {
|
|||
Future<bool> _loadPrefs() async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
this.prefs = prefs;
|
||||
api.sharedPrefs = prefs;
|
||||
var apiKey = prefs.getString('apiKey'), apiUrl = prefs.getString('apiUrl');
|
||||
|
||||
if (apiUrl != null && apiKey != null) {
|
||||
|
|
116
lib/notifications/tasks.dart
Normal file
116
lib/notifications/tasks.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
import 'dart:convert';
|
||||
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;
|
||||
}
|
||||
|
||||
var api = FeverAPI(
|
||||
apiKey: apiKey,
|
||||
apiUrl: apiUrl,
|
||||
sharedPrefs: sharedPreferences,
|
||||
);
|
||||
|
||||
await api.request().withItems().sinceId(latest ?? 0).execute();
|
||||
var items =
|
||||
api.cache.items.getAll().values.where((post) => !post.isRead);
|
||||
|
||||
// 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,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'new_posts',
|
||||
'New posts',
|
||||
autoCancel: true,
|
||||
channelShowBadge: true,
|
||||
importance: Importance.low,
|
||||
largeIcon: iconData,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void setupBackgroundTasks() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
Workmanager().initialize(
|
||||
callbackDispatcher,
|
||||
isInDebugMode: kDebugMode,
|
||||
);
|
||||
|
||||
await Permission.notification.request();
|
||||
|
||||
// Runs every 15 minutes
|
||||
Workmanager().registerPeriodicTask(
|
||||
'fetch-notifications',
|
||||
'fetchNotifications',
|
||||
initialDelay: const Duration(seconds: 30),
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ import FlutterMacOS
|
|||
import Foundation
|
||||
|
||||
import dynamic_color
|
||||
import flutter_local_notifications
|
||||
import path_provider_macos
|
||||
import share_plus
|
||||
import shared_preferences_macos
|
||||
|
@ -14,6 +15,7 @@ import wakelock_macos
|
|||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
|
|
104
pubspec.lock
104
pubspec.lock
|
@ -1,6 +1,14 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -89,6 +97,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "3350efa144252eaa4264055dded4404a94b770cfe914f1d08c20953aee55cac2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.4"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -150,6 +166,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "11d12ac8c713d28a654036c6e61464ef039eb560296282b17b933682ede0b4b8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: "0824c36855ef1f159e1b957e4ff43dfd171372d6915424bc5004e277db4f1bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_math_fork:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -344,6 +384,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.7"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -525,6 +613,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -741,6 +837,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
workmanager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: workmanager
|
||||
sha256: e0be7e35d644643f164ee45d2ce14414f0e0fdde19456aa66065f35a0b1d2ea1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -42,6 +42,9 @@ dependencies:
|
|||
share_plus: ^6.3.0
|
||||
flutter_html: ^2.2.1
|
||||
url_launcher: ^6.1.7
|
||||
workmanager: ^0.5.1
|
||||
flutter_local_notifications: ^9.1.4
|
||||
permission_handler: ^10.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -7,12 +7,15 @@
|
|||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue