102 lines
3.2 KiB
Dart
102 lines
3.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:developer';
|
|
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../api/marianumcloud/nextcloud_ocs.dart';
|
|
import '../model/account_data.dart';
|
|
import 'nid_store.dart';
|
|
|
|
/// Notification action identifiers shared between the renderer (which attaches
|
|
/// the actions) and the response handlers (which dispatch them).
|
|
const String kTalkReplyActionId = 'TALK_REPLY';
|
|
const String kTalkMarkReadActionId = 'TALK_MARK_READ';
|
|
|
|
/// Handles Talk notification actions (inline reply, mark-as-read). Runs in the
|
|
/// background isolate spawned by flutter_local_notifications, so it may not
|
|
/// share any app state — it reads credentials straight from secure storage via
|
|
/// the [AccountData] singleton after awaiting population.
|
|
class PushActions {
|
|
/// Background entry point for notification actions. Must be a top-level or
|
|
/// static function annotated with `vm:entry-point` so AOT keeps it alive.
|
|
@pragma('vm:entry-point')
|
|
static Future<void> handleBackgroundResponse(
|
|
NotificationResponse response,
|
|
) async {
|
|
final chatToken = _chatTokenFrom(response.payload);
|
|
if (chatToken == null) return;
|
|
switch (response.actionId) {
|
|
case kTalkReplyActionId:
|
|
final text = response.input?.trim();
|
|
if (text != null && text.isNotEmpty) {
|
|
await sendReply(chatToken, text);
|
|
}
|
|
await markRead(chatToken);
|
|
break;
|
|
case kTalkMarkReadActionId:
|
|
await markRead(chatToken);
|
|
await _cancelForToken(response);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static Future<void> sendReply(String chatToken, String message) async {
|
|
await _ocsPost(
|
|
'apps/spreed/api/v1/chat/$chatToken',
|
|
body: {'message': message},
|
|
);
|
|
}
|
|
|
|
static Future<void> markRead(String chatToken) async {
|
|
await _ocsPost('apps/spreed/api/v1/chat/$chatToken/read');
|
|
}
|
|
|
|
static Future<void> _ocsPost(String path, {Map<String, String>? body}) async {
|
|
try {
|
|
await AccountData().waitForPopulation();
|
|
final response = await http.post(
|
|
NextcloudOcs.uri(path),
|
|
headers: NextcloudOcs.headers(),
|
|
body: body,
|
|
);
|
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
log('Push action $path -> HTTP ${response.statusCode}');
|
|
}
|
|
} on Object catch (e) {
|
|
log('Push action $path failed: $e');
|
|
}
|
|
}
|
|
|
|
static Future<void> _cancelForToken(NotificationResponse response) async {
|
|
final nid = _nidFrom(response.payload);
|
|
if (nid == null) return;
|
|
try {
|
|
await NidStore().delete(nid);
|
|
} on Object catch (e) {
|
|
log('Push action nid cleanup failed: $e');
|
|
}
|
|
}
|
|
|
|
static String? _chatTokenFrom(String? payload) =>
|
|
_payloadField(payload, 'chatToken');
|
|
|
|
static int? _nidFrom(String? payload) {
|
|
final raw = _payloadField(payload, 'nid');
|
|
return raw == null ? null : int.tryParse(raw);
|
|
}
|
|
|
|
static String? _payloadField(String? payload, String key) {
|
|
if (payload == null || payload.isEmpty) return null;
|
|
try {
|
|
final map = jsonDecode(payload) as Map<String, dynamic>;
|
|
final value = map[key];
|
|
return value?.toString();
|
|
} on Object {
|
|
return null;
|
|
}
|
|
}
|
|
}
|