wire background-tap → route via cross-isolate payload stash
This commit is contained in:
parent
339499501f
commit
527986fdb1
|
|
@ -238,6 +238,9 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
|
|||
if (HomeWidgetRepository.data != null) {
|
||||
await HomeWidgetRepository.decideWhereToGoNotif();
|
||||
}
|
||||
// Drain anything the background-isolate notification-tap handler
|
||||
// stashed while the app was killed/backgrounded.
|
||||
await NotificationService.consumePendingNotificationTap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,33 @@ import 'package:didvan/services/storage/storage.dart';
|
|||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// Storage key shared between the background notification-tap handler
|
||||
/// (running in a separate isolate) and the main isolate, which reads the
|
||||
/// payload back on resume and performs the actual navigation.
|
||||
const String pendingNotificationPayloadKey = 'pending_notif_payload';
|
||||
|
||||
/// Top-level handler invoked by flutter_local_notifications when the user
|
||||
/// taps a notification while the app is in the background or killed. Runs
|
||||
/// in its own isolate, so it cannot touch the Navigator directly. Instead
|
||||
/// it stashes the payload via HomeWidget storage (cross-isolate) so the
|
||||
/// main isolate can pick it up and route on resume.
|
||||
@pragma('vm:entry-point')
|
||||
void backgroundNotificationTapHandler(NotificationResponse response) {
|
||||
final payload = response.payload;
|
||||
if (payload == null || payload.isEmpty) return;
|
||||
try {
|
||||
HomeWidget.saveWidgetData(pendingNotificationPayloadKey, payload);
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('backgroundNotificationTapHandler: failed to stash payload: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationService {
|
||||
static final FlutterLocalNotificationsPlugin
|
||||
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
|
@ -26,6 +50,10 @@ class NotificationService {
|
|||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: _onNotificationResponse,
|
||||
// Background tap callback — runs in a separate isolate when the user
|
||||
// taps a notification while the app is in the background or killed.
|
||||
// It stashes the payload; the main isolate consumes it on resume.
|
||||
onDidReceiveBackgroundNotificationResponse: backgroundNotificationTapHandler,
|
||||
);
|
||||
|
||||
if (!kIsWeb) {
|
||||
|
|
@ -44,6 +72,51 @@ class NotificationService {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read a pending notification payload (if any) from cross-isolate
|
||||
/// storage and hand it off to [HomeWidgetRepository.data] without
|
||||
/// navigating. Use this during splash where you want the existing splash
|
||||
/// routing logic to make the navigation call.
|
||||
static Future<void> loadPendingNotificationPayload() async {
|
||||
try {
|
||||
final stashed = await HomeWidget.getWidgetData<String>(
|
||||
pendingNotificationPayloadKey,
|
||||
defaultValue: '',
|
||||
);
|
||||
if (stashed == null || stashed.isEmpty) return;
|
||||
await HomeWidget.saveWidgetData(pendingNotificationPayloadKey, '');
|
||||
HomeWidgetRepository.data =
|
||||
NotificationMessage.fromJson(jsonDecode(stashed));
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('loadPendingNotificationPayload failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pull a pending notification payload (stored by the background tap
|
||||
/// handler) out of cross-isolate storage and route to the right screen.
|
||||
/// Called from the main isolate on app resume / startup.
|
||||
static Future<void> consumePendingNotificationTap() async {
|
||||
try {
|
||||
final stashed = await HomeWidget.getWidgetData<String>(
|
||||
pendingNotificationPayloadKey,
|
||||
defaultValue: '',
|
||||
);
|
||||
if (stashed == null || stashed.isEmpty) return;
|
||||
|
||||
// Clear immediately so we don't re-route on every resume.
|
||||
await HomeWidget.saveWidgetData(pendingNotificationPayloadKey, '');
|
||||
|
||||
final data = NotificationMessage.fromJson(jsonDecode(stashed));
|
||||
HomeWidgetRepository.data = data;
|
||||
await HomeWidgetRepository.decideWhereToGoNotif();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('consumePendingNotificationTap failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _onNotificationResponse(
|
||||
NotificationResponse response) async {
|
||||
if (kDebugMode) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'package:didvan/services/auth/token_storage.dart';
|
|||
import 'package:didvan/services/migration/app_migration.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
import 'package:didvan/services/notification/notification_service.dart';
|
||||
import 'package:didvan/services/storage/storage.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -337,6 +338,12 @@ class _SplashState extends State<Splash> with SingleTickerProviderStateMixin {
|
|||
void _navigateToNextScreen(String? token) async {
|
||||
if (!mounted) return;
|
||||
|
||||
// If the user tapped a local notification while the app was killed,
|
||||
// the payload was stashed in cross-isolate storage by
|
||||
// backgroundNotificationTapHandler. Pull it into HomeWidgetRepository
|
||||
// so the routing block below picks it up.
|
||||
await NotificationService.loadPendingNotificationPayload();
|
||||
|
||||
String extractedPath = initialURI?.path == '/'
|
||||
? Routes.home
|
||||
: (initialURI?.path ?? Routes.home);
|
||||
|
|
|
|||
Loading…
Reference in New Issue