mirror of
https://github.com/go-vikunja/app
synced 2024-06-02 18:49:47 +00:00
added workmanager for background sync
moved all notification stuff to own class for a cleaner flow
This commit is contained in:
parent
f84ad663c5
commit
debab1d689
|
@ -47,6 +47,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
bool expired = false;
|
||||
late Client _client;
|
||||
UserService? _newUserService;
|
||||
NotificationClass _notificationClass = NotificationClass();
|
||||
|
||||
|
||||
User? get currentUser => _currentUser;
|
||||
|
@ -74,13 +75,10 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
|
||||
ListService get listService => new ListAPIService(client, _storage);
|
||||
|
||||
notifs.FlutterLocalNotificationsPlugin get notificationsPlugin => new notifs.FlutterLocalNotificationsPlugin();
|
||||
|
||||
TaskServiceOptions get taskServiceOptions => new TaskServiceOptions();
|
||||
|
||||
NotificationClass get notifications => new NotificationClass();
|
||||
NotificationClass get notifications => _notificationClass;
|
||||
|
||||
notifs.NotificationAppLaunchDetails? notifLaunch;
|
||||
|
||||
LabelService get labelService => new LabelAPIService(client);
|
||||
|
||||
|
@ -89,23 +87,6 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
LabelTaskBulkAPIService get labelTaskBulkService =>
|
||||
new LabelTaskBulkAPIService(client);
|
||||
|
||||
var androidSpecificsDueDate = notifs.AndroidNotificationDetails(
|
||||
"Vikunja1",
|
||||
"Due Date Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
var androidSpecificsReminders = notifs.AndroidNotificationDetails(
|
||||
"Vikunja2",
|
||||
"Reminder Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
late notifs.IOSNotificationDetails iOSSpecifics;
|
||||
late notifs.NotificationDetails platformChannelSpecificsDueDate;
|
||||
late notifs.NotificationDetails platformChannelSpecificsReminders;
|
||||
|
||||
late String currentTimeZone;
|
||||
|
||||
|
@ -115,8 +96,8 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
{
|
||||
if(duration.inMinutes > 0) {
|
||||
Workmanager().registerPeriodicTask(
|
||||
"update-tasks", "update-tasks", frequency: duration,
|
||||
initialDelay: Duration.zero, inputData: {})
|
||||
"update-tasks", "update-tasks", frequency: duration, constraints: Constraints(networkType: NetworkType.connected),
|
||||
initialDelay: Duration(seconds: 15), inputData: {"client_token": client.token, "client_base": client.base})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -130,19 +111,12 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
_newUserService = UserAPIService(client);
|
||||
_loadCurrentUser();
|
||||
tz.initializeTimeZones();
|
||||
iOSSpecifics = notifs.IOSNotificationDetails();
|
||||
platformChannelSpecificsDueDate = notifs.NotificationDetails(
|
||||
android: androidSpecificsDueDate, iOS: iOSSpecifics);
|
||||
platformChannelSpecificsReminders = notifs.NotificationDetails(
|
||||
android: androidSpecificsReminders, iOS: iOSSpecifics);
|
||||
notificationInitializer();
|
||||
notifications.notificationInitializer();
|
||||
settingsManager.getVersionNotifications().then((value) {
|
||||
if(value == "1") {
|
||||
versionChecker.postVersionCheckSnackbar();
|
||||
}
|
||||
});
|
||||
|
||||
updateWorkmanagerDuration();
|
||||
}
|
||||
|
||||
void changeUser(User newUser, {String? token, String? base}) async {
|
||||
|
@ -164,53 +138,14 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
// Set current user in storage
|
||||
await _storage.write(key: 'currentUser', value: newUser.id.toString());
|
||||
client.configure(token: token, base: base, authenticated: true);
|
||||
updateWorkmanagerDuration();
|
||||
|
||||
setState(() {
|
||||
_currentUser = newUser;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
void notificationInitializer() async {
|
||||
currentTimeZone = await FlutterTimezone.getLocalTimezone();
|
||||
notifLaunch = await notificationsPlugin.getNotificationAppLaunchDetails();
|
||||
await notifications.initNotifications(notificationsPlugin);
|
||||
requestIOSPermissions(notificationsPlugin);
|
||||
}
|
||||
|
||||
Future<void> scheduleDueNotifications() async {
|
||||
final tasks = await taskService.getAll();
|
||||
if(tasks == null) {
|
||||
dev.log("did not receive tasks on notification update");
|
||||
return;
|
||||
}
|
||||
await notificationsPlugin.cancelAll();
|
||||
for (final task in tasks) {
|
||||
for (final reminder in task.reminderDates) {
|
||||
scheduleNotification(
|
||||
"Reminder",
|
||||
"This is your reminder for '" + task.title + "'",
|
||||
notificationsPlugin,
|
||||
reminder,
|
||||
currentTimeZone,
|
||||
platformChannelSpecificsReminders,
|
||||
id: (reminder.millisecondsSinceEpoch / 1000).floor(),
|
||||
);
|
||||
}
|
||||
if (task.hasDueDate) {
|
||||
scheduleNotification(
|
||||
"Due Reminder",
|
||||
"The task '" + task.title + "' is due.",
|
||||
notificationsPlugin,
|
||||
task.dueDate!,
|
||||
currentTimeZone,
|
||||
platformChannelSpecificsDueDate,
|
||||
id: task.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
print("notifications scheduled successfully");
|
||||
}
|
||||
|
||||
|
||||
void logoutUser(BuildContext context) {
|
||||
_storage.deleteAll().then((_) {
|
||||
|
@ -265,6 +200,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
} catch (otherExceptions) {
|
||||
loadedCurrentUser = User(id: int.parse(currentUser), username: '');
|
||||
}
|
||||
updateWorkmanagerDuration();
|
||||
setState(() {
|
||||
_currentUser = loadedCurrentUser;
|
||||
_loading = false;
|
||||
|
@ -277,7 +213,7 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
|||
return new Center(child: new CircularProgressIndicator());
|
||||
}
|
||||
if(client.authenticated) {
|
||||
scheduleDueNotifications();
|
||||
notifications.scheduleDueNotifications(taskService);
|
||||
}
|
||||
return new _VikunjaGlobalInherited(
|
||||
data: this,
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:vikunja_app/api/task_implementation.dart';
|
||||
import 'package:vikunja_app/api/client.dart';
|
||||
import 'package:vikunja_app/service/services.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'package:vikunja_app/global.dart';
|
||||
import 'package:vikunja_app/pages/home.dart';
|
||||
import 'package:vikunja_app/pages/user/login.dart';
|
||||
import 'package:vikunja_app/theme/theme.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
|
||||
import 'package:http/http.dart' as httpl;
|
||||
|
||||
import 'managers/notifications.dart';
|
||||
|
||||
class IgnoreCertHttpOverrides extends HttpOverrides {
|
||||
bool ignoreCerts = false;
|
||||
IgnoreCertHttpOverrides(bool _ignore) {ignoreCerts = _ignore;}
|
||||
|
||||
IgnoreCertHttpOverrides(bool _ignore) {
|
||||
ignoreCerts = _ignore;
|
||||
}
|
||||
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
|
@ -22,35 +34,59 @@ class IgnoreCertHttpOverrides extends HttpOverrides {
|
|||
void callbackDispatcher() {
|
||||
Workmanager().executeTask((task, inputData) {
|
||||
print("Native called background task: $task"); //simpleTask will be emitted here.
|
||||
if(task == "update-tasks") {
|
||||
//TODO
|
||||
if (task == "update-tasks" && inputData != null) {
|
||||
Client client = Client(null,
|
||||
token: inputData["client_token"],
|
||||
base: inputData["client_base"],
|
||||
authenticated: true);
|
||||
tz.initializeTimeZones();
|
||||
|
||||
return SettingsManager(new FlutterSecureStorage())
|
||||
.getIgnoreCertificates()
|
||||
.then((value) async {
|
||||
print("ignoring: $value");
|
||||
client.reload_ignore_certs(value == "1");
|
||||
|
||||
TaskAPIService taskService = TaskAPIService(client);
|
||||
NotificationClass nc = NotificationClass();
|
||||
await nc.notificationInitializer();
|
||||
return nc
|
||||
.scheduleDueNotifications(taskService)
|
||||
.then((value) => Future.value(true));
|
||||
});
|
||||
} else {
|
||||
return Future.value(true);
|
||||
}
|
||||
return get(Uri.parse("https://webhook.site/"), headers: {"task":"$task", "data":"$inputData"}).then((value) => Future.value(true));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
|
||||
runApp(VikunjaGlobal(
|
||||
child: new VikunjaApp(home: HomePage(), key: UniqueKey(),),
|
||||
login: new VikunjaApp(home: LoginPage(), key: UniqueKey(),)));
|
||||
runApp(VikunjaGlobal(
|
||||
child: new VikunjaApp(
|
||||
home: HomePage(),
|
||||
key: UniqueKey(),
|
||||
),
|
||||
login: new VikunjaApp(
|
||||
home: LoginPage(),
|
||||
key: UniqueKey(),
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
class VikunjaApp extends StatelessWidget {
|
||||
final Widget home;
|
||||
|
||||
const VikunjaApp({Key? key, required this.home}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return new MaterialApp(
|
||||
title: 'Vikunja',
|
||||
theme: buildVikunjaTheme(),
|
||||
darkTheme: buildVikunjaDarkTheme(),
|
||||
scaffoldMessengerKey: VikunjaGlobal.of(context).snackbarKey, // <= this
|
||||
scaffoldMessengerKey: VikunjaGlobal.of(context).snackbarKey,
|
||||
// <= this
|
||||
home: this.home,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,24 +2,51 @@
|
|||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'as notifs;
|
||||
import 'package:rxdart/subjects.dart' as rxSub;
|
||||
import 'package:vikunja_app/service/services.dart';
|
||||
|
||||
class NotificationClass{
|
||||
class NotificationClass {
|
||||
final int? id;
|
||||
final String? title;
|
||||
final String? body;
|
||||
final String? payload;
|
||||
late String currentTimeZone;
|
||||
notifs.NotificationAppLaunchDetails? notifLaunch;
|
||||
|
||||
notifs.FlutterLocalNotificationsPlugin get notificationsPlugin => new notifs.FlutterLocalNotificationsPlugin();
|
||||
|
||||
|
||||
var androidSpecificsDueDate = notifs.AndroidNotificationDetails(
|
||||
"Vikunja1",
|
||||
"Due Date Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
var androidSpecificsReminders = notifs.AndroidNotificationDetails(
|
||||
"Vikunja2",
|
||||
"Reminder Notifications",
|
||||
channelDescription: "description",
|
||||
icon: 'vikunja_notification_logo',
|
||||
importance: notifs.Importance.high
|
||||
);
|
||||
late notifs.IOSNotificationDetails iOSSpecifics;
|
||||
late notifs.NotificationDetails platformChannelSpecificsDueDate;
|
||||
late notifs.NotificationDetails platformChannelSpecificsReminders;
|
||||
|
||||
NotificationClass({this.id, this.body, this.payload, this.title});
|
||||
|
||||
final rxSub.BehaviorSubject<NotificationClass> didReceiveLocalNotificationSubject =
|
||||
final rxSub.BehaviorSubject<
|
||||
NotificationClass> didReceiveLocalNotificationSubject =
|
||||
rxSub.BehaviorSubject<NotificationClass>();
|
||||
final rxSub.BehaviorSubject<String> selectNotificationSubject =
|
||||
rxSub.BehaviorSubject<String>();
|
||||
|
||||
Future<void> initNotifications(notifs.FlutterLocalNotificationsPlugin notifsPlugin) async {
|
||||
Future<void> _initNotifications() async {
|
||||
var initializationSettingsAndroid =
|
||||
notifs.AndroidInitializationSettings('vikunja_logo');
|
||||
var initializationSettingsIOS = notifs.IOSInitializationSettings(
|
||||
|
@ -29,11 +56,12 @@ class NotificationClass{
|
|||
onDidReceiveLocalNotification:
|
||||
(int? id, String? title, String? body, String? payload) async {
|
||||
didReceiveLocalNotificationSubject
|
||||
.add(NotificationClass(id: id, title: title, body: body, payload: payload));
|
||||
.add(NotificationClass(
|
||||
id: id, title: title, body: body, payload: payload));
|
||||
});
|
||||
var initializationSettings = notifs.InitializationSettings(
|
||||
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
|
||||
await notifsPlugin.initialize(initializationSettings,
|
||||
await notificationsPlugin.initialize(initializationSettings,
|
||||
onSelectNotification: (String? payload) async {
|
||||
if (payload != null) {
|
||||
print('notification payload: ' + payload);
|
||||
|
@ -42,32 +70,88 @@ class NotificationClass{
|
|||
});
|
||||
print("Notifications initialised successfully");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> scheduleNotification(String title, String description,
|
||||
notifs.FlutterLocalNotificationsPlugin notifsPlugin,
|
||||
DateTime scheduledTime, String currentTimeZone, notifs.NotificationDetails platformChannelSpecifics, {int? id}) async {
|
||||
if(id == null)
|
||||
id = Random().nextInt(1000000);
|
||||
// TODO: move to setup
|
||||
tz.TZDateTime time = tz.TZDateTime.from(scheduledTime,tz.getLocation(currentTimeZone));
|
||||
if(time.difference(tz.TZDateTime.now(tz.getLocation(currentTimeZone))) < Duration.zero)
|
||||
return;
|
||||
await notifsPlugin.zonedSchedule(id, title, description,
|
||||
time, platformChannelSpecifics, androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: notifs.UILocalNotificationDateInterpretation.wallClockTime); // This literally schedules the notification
|
||||
}
|
||||
Future<void> notificationInitializer() async {
|
||||
iOSSpecifics = notifs.IOSNotificationDetails();
|
||||
platformChannelSpecificsDueDate = notifs.NotificationDetails(
|
||||
android: androidSpecificsDueDate, iOS: iOSSpecifics);
|
||||
platformChannelSpecificsReminders = notifs.NotificationDetails(
|
||||
android: androidSpecificsReminders, iOS: iOSSpecifics);
|
||||
currentTimeZone = await FlutterTimezone.getLocalTimezone();
|
||||
notifLaunch = await notificationsPlugin.getNotificationAppLaunchDetails();
|
||||
await _initNotifications();
|
||||
requestIOSPermissions();
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
void sendTestNotification(notifs.FlutterLocalNotificationsPlugin notifsPlugin, notifs.NotificationDetails platformChannelSpecifics) {
|
||||
notifsPlugin.show(Random().nextInt(10000000), "Test Notification", "This is a test notification", platformChannelSpecifics);
|
||||
}
|
||||
Future<void> scheduleNotification(String title, String description,
|
||||
notifs.FlutterLocalNotificationsPlugin notifsPlugin,
|
||||
DateTime scheduledTime, String currentTimeZone,
|
||||
notifs.NotificationDetails platformChannelSpecifics, {int? id}) async {
|
||||
if (id == null)
|
||||
id = Random().nextInt(1000000);
|
||||
// TODO: move to setup
|
||||
tz.TZDateTime time = tz.TZDateTime.from(
|
||||
scheduledTime, tz.getLocation(currentTimeZone));
|
||||
if (time.difference(tz.TZDateTime.now(tz.getLocation(currentTimeZone))) <
|
||||
Duration.zero)
|
||||
return;
|
||||
await notifsPlugin.zonedSchedule(id, title, description,
|
||||
time, platformChannelSpecifics, androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation: notifs
|
||||
.UILocalNotificationDateInterpretation
|
||||
.wallClockTime); // This literally schedules the notification
|
||||
}
|
||||
|
||||
void sendTestNotification() {
|
||||
notificationsPlugin.show(Random().nextInt(10000000), "Test Notification",
|
||||
"This is a test notification", platformChannelSpecificsReminders);
|
||||
}
|
||||
|
||||
|
||||
void requestIOSPermissions(
|
||||
notifs.FlutterLocalNotificationsPlugin notifsPlugin) {
|
||||
notifsPlugin.resolvePlatformSpecificImplementation<notifs.IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
void requestIOSPermissions() {
|
||||
notificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
notifs.IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future<void> scheduleDueNotifications(TaskService taskService) async {
|
||||
final tasks = await taskService.getAll();
|
||||
if (tasks == null) {
|
||||
print("did not receive tasks on notification update");
|
||||
return;
|
||||
}
|
||||
await notificationsPlugin.cancelAll();
|
||||
for (final task in tasks) {
|
||||
for (final reminder in task.reminderDates) {
|
||||
scheduleNotification(
|
||||
"Reminder",
|
||||
"This is your reminder for '" + task.title + "'",
|
||||
notificationsPlugin,
|
||||
reminder,
|
||||
await FlutterTimezone.getLocalTimezone(),
|
||||
platformChannelSpecificsReminders,
|
||||
id: (reminder.millisecondsSinceEpoch / 1000).floor(),
|
||||
);
|
||||
}
|
||||
if (task.hasDueDate) {
|
||||
scheduleNotification(
|
||||
"Due Reminder",
|
||||
"The task '" + task.title + "' is due.",
|
||||
notificationsPlugin,
|
||||
task.dueDate!,
|
||||
currentTimeZone,
|
||||
platformChannelSpecificsDueDate,
|
||||
id: task.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
print("notifications scheduled successfully");
|
||||
}
|
||||
|
||||
}
|
|
@ -173,7 +173,7 @@ class LandingPageState extends State<LandingPage>
|
|||
_list = [];
|
||||
landingPageStatus = PageStatus.loading;
|
||||
// FIXME: loads and reschedules tasks each time list is updated
|
||||
VikunjaGlobal.of(context).scheduleDueNotifications();
|
||||
VikunjaGlobal.of(context).notifications.scheduleDueNotifications(VikunjaGlobal.of(context).taskService);
|
||||
return VikunjaGlobal.of(context)
|
||||
.taskService
|
||||
.getByOptions(VikunjaGlobal.of(context).taskServiceOptions)
|
||||
|
|
|
@ -127,10 +127,7 @@ class SettingsPageState extends State<SettingsPage> {
|
|||
: ListTile(title: Text("...")),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
sendTestNotification(
|
||||
VikunjaGlobal.of(context).notificationsPlugin,
|
||||
VikunjaGlobal.of(context)
|
||||
.platformChannelSpecificsReminders);
|
||||
VikunjaGlobal.of(context).notifications.sendTestNotification();
|
||||
},
|
||||
child: Text("Send test notification")),
|
||||
TextButton(
|
||||
|
|
Loading…
Reference in New Issue
Block a user