improved reliability and error handling for background notification actions by migrating to a top-level entry point for stable AOT callback resolution; implemented failure notifications to preserve undelivered reply text and surface technical errors; resolved a race condition that could resurrect dismissed notifications during silent re-renders; updated the Android manifest with the required broadcast receiver for notification actions; and refactored internal OCS methods to return detailed success/failure records for improved logging and field debugging.
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
|
||||
import '../notification/notification_service.dart';
|
||||
@@ -146,6 +147,17 @@ class PushRenderer {
|
||||
bool alert = true,
|
||||
}) async {
|
||||
if (messages.isEmpty) return;
|
||||
// A silent render only ever UPDATES an existing card (delete-push shrunk
|
||||
// the thread, late avatar arrived, failed reply needs its spinner
|
||||
// stopped). If the card is verifiably gone meanwhile (reply/mark-read
|
||||
// cleanup cancelled it, or the user swiped it away), re-posting would
|
||||
// resurrect it — and Android would re-attach a pending inline reply on
|
||||
// top. Probe failure (null) still renders: stopping a possible reply
|
||||
// spinner outweighs a rare resurrection.
|
||||
if (!alert && await _isChatNotificationActive(chatToken) == false) {
|
||||
debugPrint('PushRenderer: skip silent re-render, card gone ($chatToken)');
|
||||
return;
|
||||
}
|
||||
final tag = chatNotificationTag(chatToken);
|
||||
final id = stableChatNotificationId(chatToken);
|
||||
final latest = messages.last;
|
||||
@@ -261,12 +273,18 @@ class PushRenderer {
|
||||
);
|
||||
}
|
||||
|
||||
// Both actions keep cancelNotification: true (the default): the plugin's
|
||||
// Java receiver then removes the card NATIVELY the moment the action fires.
|
||||
// The reply action must not rely on our Dart-side cancel instead — MIUI/
|
||||
// HyperOS ignores an app-issued cancel while an inline reply is pending, so
|
||||
// the card would stay behind showing the reply as an attached "Ich" row.
|
||||
// If sending subsequently fails, the error card (with the typed text)
|
||||
// replaces the lost thread card.
|
||||
static const _talkActions = [
|
||||
AndroidNotificationAction(
|
||||
kTalkReplyActionId,
|
||||
'Antworten',
|
||||
showsUserInterface: false,
|
||||
cancelNotification: false,
|
||||
inputs: [AndroidNotificationActionInput(label: 'Nachricht')],
|
||||
),
|
||||
AndroidNotificationAction(
|
||||
@@ -276,6 +294,34 @@ class PushRenderer {
|
||||
),
|
||||
];
|
||||
|
||||
/// Renders a failure card for a Talk notification action (reply/mark-read).
|
||||
/// Separate tag per chat, so it neither replaces the thread notification
|
||||
/// nor stacks across repeated failures. Carries no payload — tapping it
|
||||
/// just opens the app.
|
||||
Future<void> renderTalkActionError({
|
||||
required String chatToken,
|
||||
required String title,
|
||||
required String body,
|
||||
}) async {
|
||||
await _plugin.show(
|
||||
id: stableChatNotificationId(chatToken),
|
||||
title: title,
|
||||
body: body,
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
talkChannelId,
|
||||
talkChannelName,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
color: _accentColor,
|
||||
tag: 'talk_error_$chatToken',
|
||||
styleInformation: BigTextStyleInformation(body),
|
||||
),
|
||||
iOS: const DarwinNotificationDetails(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _renderGeneric(PushSubject subject) async {
|
||||
final nid = subject.nid ?? _fallbackId(subject.subject);
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
|
||||
Reference in New Issue
Block a user