mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-15 06:12:29 -06:00
feat: Improve notifications, add donation button, improved asn form field
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/notification_tap_response_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_downloaded_document_payload.dart';
|
||||
|
||||
class NotificationTapResponsePayloadConverter
|
||||
implements
|
||||
JsonConverter<NotificationTapResponsePayload, Map<String, dynamic>> {
|
||||
const NotificationTapResponsePayloadConverter();
|
||||
@override
|
||||
NotificationTapResponsePayload fromJson(Map<String, dynamic> json) {
|
||||
final type = NotificationResponseOpenAction.values.byName(json['type']);
|
||||
switch (type) {
|
||||
case NotificationResponseOpenAction.openDownloadedDocumentPath:
|
||||
return OpenDownloadedDocumentPayload.fromJson(
|
||||
json,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson(NotificationTapResponsePayload object) {
|
||||
return object.toJson();
|
||||
}
|
||||
}
|
||||
11
lib/features/notifications/models/notification_actions.dart
Normal file
11
lib/features/notifications/models/notification_actions.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
enum NotificationResponseButtonAction {
|
||||
openCreatedDocument,
|
||||
acknowledgeCreatedDocument;
|
||||
}
|
||||
|
||||
@JsonEnum()
|
||||
enum NotificationResponseOpenAction {
|
||||
openDownloadedDocumentPath;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
enum NotificationChannel {
|
||||
task("task_channel", "Paperless Tasks");
|
||||
task("task_channel", "Paperless tasks"),
|
||||
documentDownload("document_download_channel", "Document downloads");
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'create_document_success_payload.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateDocumentSuccessPayload {
|
||||
final int documentId;
|
||||
|
||||
CreateDocumentSuccessPayload(this.documentId);
|
||||
|
||||
factory CreateDocumentSuccessPayload.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateDocumentSuccessPayloadFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CreateDocumentSuccessPayloadToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
|
||||
abstract class NotificationTapResponsePayload {
|
||||
final NotificationResponseOpenAction type;
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
NotificationTapResponsePayload({required this.type});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/notification_tap_response_payload.dart';
|
||||
|
||||
part 'open_downloaded_document_payload.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class OpenDownloadedDocumentPayload extends NotificationTapResponsePayload {
|
||||
final String filePath;
|
||||
OpenDownloadedDocumentPayload({
|
||||
required this.filePath,
|
||||
super.type = NotificationResponseOpenAction.openDownloadedDocumentPath,
|
||||
});
|
||||
|
||||
factory OpenDownloadedDocumentPayload.fromJson(Map<String, dynamic> json) =>
|
||||
_$OpenDownloadedDocumentPayloadFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$OpenDownloadedDocumentPayloadToJson(this);
|
||||
}
|
||||
@@ -2,11 +2,16 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/models/notification_payloads/open_created_document_notification_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/notification_channels.dart';
|
||||
import 'package:paperless_mobile/features/notifications/converters/notification_tap_response_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_action/create_document_success_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_downloaded_document_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_channels.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class LocalNotificationService {
|
||||
final FlutterLocalNotificationsPlugin _plugin =
|
||||
@@ -16,7 +21,7 @@ class LocalNotificationService {
|
||||
|
||||
Future<void> initialize() async {
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('ic_stat_paperless_logo_green');
|
||||
AndroidInitializationSettings('paperless_logo_green');
|
||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
||||
DarwinInitializationSettings(
|
||||
requestSoundPermission: false,
|
||||
@@ -32,6 +37,8 @@ class LocalNotificationService {
|
||||
await _plugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||
onDidReceiveBackgroundNotificationResponse:
|
||||
onDidReceiveBackgroundNotificationResponse,
|
||||
);
|
||||
await _plugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
@@ -39,6 +46,51 @@ class LocalNotificationService {
|
||||
?.requestPermission();
|
||||
}
|
||||
|
||||
Future<void> notifyFileDownload({
|
||||
required DocumentModel document,
|
||||
required String filename,
|
||||
required String filePath,
|
||||
required bool finished,
|
||||
required String locale,
|
||||
}) async {
|
||||
final tr = await S.delegate.load(Locale(locale));
|
||||
|
||||
int id = document.id;
|
||||
await _plugin.show(
|
||||
id,
|
||||
filename,
|
||||
finished
|
||||
? tr.notificationDownloadComplete
|
||||
: tr.notificationDownloadingDocument,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
NotificationChannel.documentDownload.id + "_${document.id}",
|
||||
NotificationChannel.documentDownload.name,
|
||||
ongoing: !finished,
|
||||
indeterminate: true,
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showProgress: !finished,
|
||||
when: DateTime.now().millisecondsSinceEpoch,
|
||||
category: AndroidNotificationCategory.progress,
|
||||
icon: finished ? 'file_download_done' : 'downloading',
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
attachments: [
|
||||
DarwinNotificationAttachment(
|
||||
filePath,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(
|
||||
OpenDownloadedDocumentPayload(
|
||||
filePath: filePath,
|
||||
).toJson(),
|
||||
),
|
||||
); //TODO: INTL
|
||||
}
|
||||
|
||||
//TODO: INTL
|
||||
Future<void> notifyTaskChanged(Task task) {
|
||||
log("[LocalNotificationService] notifyTaskChanged: ${task.toString()}");
|
||||
@@ -49,20 +101,17 @@ class LocalNotificationService {
|
||||
late int timestampMillis;
|
||||
bool showProgress =
|
||||
status == TaskStatus.started || status == TaskStatus.pending;
|
||||
int progress = 0;
|
||||
dynamic payload;
|
||||
switch (status) {
|
||||
case TaskStatus.started:
|
||||
title = "Document received";
|
||||
body = task.taskFileName;
|
||||
timestampMillis = task.dateCreated.millisecondsSinceEpoch;
|
||||
progress = 10;
|
||||
break;
|
||||
case TaskStatus.pending:
|
||||
title = "Processing document...";
|
||||
body = task.taskFileName;
|
||||
timestampMillis = task.dateCreated.millisecondsSinceEpoch;
|
||||
progress = 70;
|
||||
break;
|
||||
case TaskStatus.failure:
|
||||
title = "Failed to process document";
|
||||
@@ -73,7 +122,7 @@ class LocalNotificationService {
|
||||
title = "Document successfully created";
|
||||
body = task.taskFileName;
|
||||
timestampMillis = task.dateDone!.millisecondsSinceEpoch;
|
||||
payload = CreateDocumentSuccessNotificationResponsePayload(
|
||||
payload = CreateDocumentSuccessPayload(
|
||||
task.relatedDocument!,
|
||||
);
|
||||
break;
|
||||
@@ -93,7 +142,7 @@ class LocalNotificationService {
|
||||
showProgress: showProgress,
|
||||
maxProgress: 100,
|
||||
when: timestampMillis,
|
||||
progress: progress,
|
||||
indeterminate: true,
|
||||
actions: status == TaskStatus.success
|
||||
? [
|
||||
//TODO: Implement once moved to new routing
|
||||
@@ -109,6 +158,7 @@ class LocalNotificationService {
|
||||
]
|
||||
: [],
|
||||
),
|
||||
//TODO: Add darwin support
|
||||
),
|
||||
payload: jsonEncode(payload),
|
||||
);
|
||||
@@ -119,38 +169,68 @@ class LocalNotificationService {
|
||||
String? title,
|
||||
String? body,
|
||||
String? payload,
|
||||
) {}
|
||||
|
||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
||||
debugPrint("Received Notification: ${response.payload}");
|
||||
if (response.notificationResponseType ==
|
||||
NotificationResponseType.selectedNotificationAction) {
|
||||
final action =
|
||||
NotificationResponseAction.values.byName(response.actionId!);
|
||||
_handleResponseAction(action, response);
|
||||
}
|
||||
// Non-actionable notification pressed, ignoring...
|
||||
) {
|
||||
debugPrint("onDidReceiveNotification!");
|
||||
}
|
||||
|
||||
void _handleResponseAction(
|
||||
NotificationResponseAction action,
|
||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
||||
debugPrint(
|
||||
"Received Notification ${response.id}: Action is ${response.actionId}): ${response.payload}",
|
||||
);
|
||||
switch (response.notificationResponseType) {
|
||||
case NotificationResponseType.selectedNotification:
|
||||
if (response.payload != null) {
|
||||
final payload =
|
||||
const NotificationTapResponsePayloadConverter().fromJson(
|
||||
jsonDecode(response.payload!),
|
||||
);
|
||||
_handleResponseTapAction(payload.type, response);
|
||||
}
|
||||
|
||||
break;
|
||||
case NotificationResponseType.selectedNotificationAction:
|
||||
final action =
|
||||
NotificationResponseButtonAction.values.byName(response.actionId!);
|
||||
_handleResponseButtonAction(action, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleResponseButtonAction(
|
||||
NotificationResponseButtonAction action,
|
||||
NotificationResponse response,
|
||||
) {
|
||||
switch (action) {
|
||||
case NotificationResponseAction.openCreatedDocument:
|
||||
final payload =
|
||||
CreateDocumentSuccessNotificationResponsePayload.fromJson(
|
||||
case NotificationResponseButtonAction.openCreatedDocument:
|
||||
final payload = CreateDocumentSuccessPayload.fromJson(
|
||||
jsonDecode(response.payload!),
|
||||
);
|
||||
log("Navigate to document ${payload.documentId}");
|
||||
break;
|
||||
case NotificationResponseAction.acknowledgeCreatedDocument:
|
||||
final payload =
|
||||
CreateDocumentSuccessNotificationResponsePayload.fromJson(
|
||||
case NotificationResponseButtonAction.acknowledgeCreatedDocument:
|
||||
final payload = CreateDocumentSuccessPayload.fromJson(
|
||||
jsonDecode(response.payload!),
|
||||
);
|
||||
log("Acknowledge document ${payload.documentId}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleResponseTapAction(
|
||||
NotificationResponseOpenAction type,
|
||||
NotificationResponse response,
|
||||
) {
|
||||
switch (type) {
|
||||
case NotificationResponseOpenAction.openDownloadedDocumentPath:
|
||||
final payload = OpenDownloadedDocumentPayload.fromJson(
|
||||
jsonDecode(response.payload!));
|
||||
OpenFilex.open(payload.filePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onDidReceiveBackgroundNotificationResponse(NotificationResponse response) {
|
||||
//TODO: When periodic background inbox check is implemented, notification tap is handled here
|
||||
debugPrint(response.toString());
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'open_created_document_notification_payload.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateDocumentSuccessNotificationResponsePayload {
|
||||
final int documentId;
|
||||
|
||||
CreateDocumentSuccessNotificationResponsePayload(this.documentId);
|
||||
|
||||
factory CreateDocumentSuccessNotificationResponsePayload.fromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$CreateDocumentSuccessNotificationResponsePayloadFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() =>
|
||||
_$CreateDocumentSuccessNotificationResponsePayloadToJson(this);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
enum NotificationResponseAction {
|
||||
openCreatedDocument,
|
||||
acknowledgeCreatedDocument;
|
||||
}
|
||||
Reference in New Issue
Block a user