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 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 sendReply(String chatToken, String message) async { await _ocsPost( 'apps/spreed/api/v1/chat/$chatToken', body: {'message': message}, ); } static Future markRead(String chatToken) async { await _ocsPost('apps/spreed/api/v1/chat/$chatToken/read'); } static Future _ocsPost(String path, {Map? 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 _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; final value = map[key]; return value?.toString(); } on Object { return null; } } }