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) {
|
if (HomeWidgetRepository.data != null) {
|
||||||
await HomeWidgetRepository.decideWhereToGoNotif();
|
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:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:home_widget/home_widget.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:path_provider/path_provider.dart';
|
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 {
|
class NotificationService {
|
||||||
static final FlutterLocalNotificationsPlugin
|
static final FlutterLocalNotificationsPlugin
|
||||||
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
|
|
@ -26,6 +50,10 @@ class NotificationService {
|
||||||
await _flutterLocalNotificationsPlugin.initialize(
|
await _flutterLocalNotificationsPlugin.initialize(
|
||||||
initializationSettings,
|
initializationSettings,
|
||||||
onDidReceiveNotificationResponse: _onNotificationResponse,
|
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) {
|
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(
|
static Future<void> _onNotificationResponse(
|
||||||
NotificationResponse response) async {
|
NotificationResponse response) async {
|
||||||
if (kDebugMode) {
|
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/migration/app_migration.dart';
|
||||||
import 'package:didvan/services/network/request.dart';
|
import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.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:didvan/services/storage/storage.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -337,6 +338,12 @@ class _SplashState extends State<Splash> with SingleTickerProviderStateMixin {
|
||||||
void _navigateToNextScreen(String? token) async {
|
void _navigateToNextScreen(String? token) async {
|
||||||
if (!mounted) return;
|
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 == '/'
|
String extractedPath = initialURI?.path == '/'
|
||||||
? Routes.home
|
? Routes.home
|
||||||
: (initialURI?.path ?? Routes.home);
|
: (initialURI?.path ?? Routes.home);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue