1
0
mirror of https://github.com/go-vikunja/app synced 2024-06-02 18:49:47 +00:00

implemented scheduled local notifications for reminders and tasks

This commit is contained in:
benimautner 2022-04-20 00:12:50 +02:00
parent db70990a3c
commit a6fab24598
7 changed files with 146 additions and 2 deletions

View File

@ -31,6 +31,13 @@ class TaskAPIService extends APIService implements TaskService {
.post('/tasks/${task.id}', body: task.toJSON())
.then((map) => Task.fromJson(map));
}
@override
Future<List<Task>> getAll() {
return client
.get('/tasks/all')
.then((value) => value.map<Task>((taskJson) => Task.fromJson(taskJson)).toList());
}
@override
Future<List<Task>> getByOptions(TaskServiceOptions options) {

View File

@ -1,13 +1,20 @@
//import 'dart:math';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:vikunja_app/api/client.dart';
import 'package:vikunja_app/api/list_implementation.dart';
import 'package:vikunja_app/api/namespace_implementation.dart';
import 'package:vikunja_app/api/task_implementation.dart';
import 'package:vikunja_app/api/user_implementation.dart';
import 'package:vikunja_app/managers/notifications.dart';
import 'package:vikunja_app/managers/user.dart';
import 'package:vikunja_app/models/user.dart';
import 'package:vikunja_app/service/services.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
class VikunjaGlobal extends StatefulWidget {
final Widget child;
@ -45,12 +52,21 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
ListService get listService => new ListAPIService(client, _storage);
FlutterLocalNotificationsPlugin get notificationsPlugin => new FlutterLocalNotificationsPlugin();
TaskServiceOptions get taskServiceOptions => new TaskServiceOptions();
NotificationClass get notifications => new NotificationClass();
NotificationAppLaunchDetails notifLaunch;
@override
void initState() {
super.initState();
_loadCurrentUser();
tz.initializeTimeZones();
notificationInitializer();
}
void changeUser(User newUser, {String token, String base}) async {
@ -78,6 +94,27 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
});
}
void notificationInitializer() async {
notifLaunch = await notificationsPlugin.getNotificationAppLaunchDetails();
await notifications.initNotifications(notificationsPlugin);
requestIOSPermissions(notificationsPlugin);
}
void scheduleDueNotifications() {
notificationsPlugin.cancelAll().then((value) {
taskService.getAll().then((value) =>
value.forEach((task) {
if(task.reminders != null)
task.reminders.forEach((reminder) {
scheduleNotification("This is your reminder for '" + task.title + "'", task.description, notificationsPlugin, reminder);
});
scheduleNotification("The task '" + task.title + "' is due.", task.description, notificationsPlugin, task.due);
})
);
});
}
void logoutUser(BuildContext context) {
_storage.deleteAll().then((_) {
@ -138,6 +175,9 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
if (_loading) {
return new Center(child: new CircularProgressIndicator());
}
if(client != null) {
scheduleDueNotifications();
}
return new _VikunjaGlobalInherited(
data: this,
child: client == null ? widget.login : widget.child,

View File

@ -0,0 +1,81 @@
// https://medium.com/@fuzzymemory/adding-scheduled-notifications-in-your-flutter-application-19be1f82ade8
import 'dart:math';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:timezone/data/latest_all.dart' as tz;
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;
class NotificationClass{
final int id;
final String title;
final String body;
final String payload;
NotificationClass({this.id, this.body, this.payload, this.title});
final rxSub.BehaviorSubject<NotificationClass> didReceiveLocalNotificationSubject =
rxSub.BehaviorSubject<NotificationClass>();
final rxSub.BehaviorSubject<String> selectNotificationSubject =
rxSub.BehaviorSubject<String>();
Future<void> initNotifications(notifs.FlutterLocalNotificationsPlugin notifsPlugin) async {
var initializationSettingsAndroid =
notifs.AndroidInitializationSettings('vikunja_logo');
var initializationSettingsIOS = notifs.IOSInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification:
(int id, String title, String body, String payload) async {
didReceiveLocalNotificationSubject
.add(NotificationClass(id: id, title: title, body: body, payload: payload));
});
var initializationSettings = notifs.InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
await notifsPlugin.initialize(initializationSettings,
onSelectNotification: (String payload) async {
if (payload != null) {
print('notification payload: ' + payload);
}
selectNotificationSubject.add(payload);
});
print("Notifications initialised successfully");
}
}
Future<void> scheduleNotification(String title, String description,
notifs.FlutterLocalNotificationsPlugin notifsPlugin,
DateTime scheduledTime, {String id}) async {
if(scheduledTime.difference(DateTime.now()) < Duration.zero)
return;
if(id == null)
id = DateTime.now().toString();
var androidSpecifics = notifs.AndroidNotificationDetails(
"Vikunja",
"Due Date Reminder",
channelDescription: "description",
icon: 'vikunja_logo',
importance: notifs.Importance.high
);
var iOSSpecifics = notifs.IOSNotificationDetails();
var platformChannelSpecifics = notifs.NotificationDetails(
android: androidSpecifics, iOS: iOSSpecifics);
final String currentTimeZone = await FlutterNativeTimezone.getLocalTimezone();
tz.TZDateTime time = tz.TZDateTime.from(scheduledTime,tz.getLocation(currentTimeZone));
//time.add(Duration(hours: -2));
await notifsPlugin.zonedSchedule(Random().nextInt(100000000), title, description,
time, platformChannelSpecifics, androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: notifs.UILocalNotificationDateInterpretation.wallClockTime); // This literally schedules the notification
}
void requestIOSPermissions(
notifs.FlutterLocalNotificationsPlugin notifsPlugin) {
notifsPlugin.resolvePlatformSpecificImplementation<notifs.IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}

View File

@ -4,6 +4,8 @@ import 'package:vikunja_app/global.dart';
import '../components/AddDialog.dart';
import '../components/TaskTile.dart';
import '../models/task.dart';
import '../managers/notifications.dart';
import '../main.dart';
class LandingPage extends StatefulWidget {
@override
@ -25,6 +27,7 @@ class LandingPageState extends State<LandingPage> {
Widget build(BuildContext context) {
if(_list == null)
_loadList(context);
VikunjaGlobal.of(context).scheduleDueNotifications();
return new Scaffold(
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,

View File

@ -178,6 +178,12 @@ class MockedTaskService implements TaskService {
// TODO: implement getByOptions
throw UnimplementedError();
}
@override
Future<List<Task>> getAll() {
// TODO: implement getAll
throw UnimplementedError();
}
}
class MockedUserService implements UserService {

View File

@ -92,6 +92,7 @@ abstract class TaskService {
Future<Task> update(Task task);
Future delete(int taskId);
Future<Task> add(int listId, Task task);
Future<List<Task>> getAll();
Future<List<Task>> getByOptions(TaskServiceOptions options);
}

View File

@ -3,9 +3,15 @@ String durationToHumanReadable(Duration dur) {
return dur.inDays.toString() + " days";
if(dur.inDays.abs() == 1)
return dur.inDays.toString() + " day";
if(dur.inHours.abs() > 1)
return dur.inHours.toString() + " hours";
if(dur.inHours.abs() == 1)
return dur.inHours.toString() + "1 hour";
return "under 1 hour";
return dur.inHours.toString() + " hour";
if(dur.inMinutes.abs() > 1)
return dur.inMinutes.toString() + " minutes";
if(dur.inMinutes.abs() == 1)
return dur.inMinutes.toString() + " minute";
return "under 1 minute";
}