Implement robust notification deep linking
- Create SecureChatLoaderScreen for linking to conversations by ID - Add /secure-chat/:id route to AppRoutes - Update NotificationService to use AppRoutes.router for all navigation - Fix Follow and Post navigation routes in NotificationService - Decouple notification handling from manual Navigator pushes
This commit is contained in:
parent
69358b016f
commit
c635da552d
|
|
@ -19,6 +19,7 @@ import '../screens/profile/blocked_users_screen.dart';
|
||||||
import '../screens/auth/auth_gate.dart';
|
import '../screens/auth/auth_gate.dart';
|
||||||
import '../screens/discover/discover_screen.dart';
|
import '../screens/discover/discover_screen.dart';
|
||||||
import '../screens/secure_chat/secure_chat_full_screen.dart';
|
import '../screens/secure_chat/secure_chat_full_screen.dart';
|
||||||
|
import '../screens/secure_chat/secure_chat_loader_screen.dart';
|
||||||
import '../screens/post/threaded_conversation_screen.dart';
|
import '../screens/post/threaded_conversation_screen.dart';
|
||||||
|
|
||||||
/// App routing config (GoRouter).
|
/// App routing config (GoRouter).
|
||||||
|
|
@ -65,6 +66,15 @@ class AppRoutes {
|
||||||
path: secureChat,
|
path: secureChat,
|
||||||
parentNavigatorKey: rootNavigatorKey,
|
parentNavigatorKey: rootNavigatorKey,
|
||||||
builder: (_, __) => const SecureChatFullScreen(),
|
builder: (_, __) => const SecureChatFullScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: ':id',
|
||||||
|
parentNavigatorKey: rootNavigatorKey,
|
||||||
|
builder: (_, state) => SecureChatLoaderScreen(
|
||||||
|
conversationId: state.pathParameters['id'] ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '$postPrefix/:id',
|
path: '$postPrefix/:id',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../services/secure_chat_service.dart';
|
||||||
|
import '../../theme/app_theme.dart';
|
||||||
|
import 'secure_chat_screen.dart';
|
||||||
|
|
||||||
|
/// Loading wrapper to fetch conversation data before showing chat screen
|
||||||
|
class SecureChatLoaderScreen extends StatefulWidget {
|
||||||
|
final String conversationId;
|
||||||
|
|
||||||
|
const SecureChatLoaderScreen({
|
||||||
|
super.key,
|
||||||
|
required this.conversationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SecureChatLoaderScreen> createState() => _SecureChatLoaderScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SecureChatLoaderScreenState extends State<SecureChatLoaderScreen> {
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadConversation() async {
|
||||||
|
try {
|
||||||
|
final conversation = await SecureChatService.instance
|
||||||
|
.getConversationById(widget.conversationId);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (conversation != null) {
|
||||||
|
// Replace this loader with the actual chat screen
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => SecureChatScreen(conversation: conversation),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_error = 'Conversation not found';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_error = 'Failed to load conversation: $e';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_error != null) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Error')),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(_error!, style: TextStyle(color: AppTheme.error)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Go Back'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.scaffoldBg,
|
||||||
|
body: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,6 @@ import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../config/firebase_web_config.dart';
|
import '../config/firebase_web_config.dart';
|
||||||
import '../routes/app_routes.dart';
|
import '../routes/app_routes.dart';
|
||||||
import '../services/secure_chat_service.dart';
|
import '../services/secure_chat_service.dart';
|
||||||
import '../screens/secure_chat/secure_chat_screen.dart';
|
|
||||||
import 'api_service.dart';
|
import 'api_service.dart';
|
||||||
|
|
||||||
/// NotificationPreferences model
|
/// NotificationPreferences model
|
||||||
|
|
@ -465,15 +464,13 @@ class NotificationService {
|
||||||
|
|
||||||
Future<void> _handleMessageOpen(RemoteMessage message) async {
|
Future<void> _handleMessageOpen(RemoteMessage message) async {
|
||||||
final data = message.data;
|
final data = message.data;
|
||||||
|
// Try to get type from data, fallback to notification title parsing if needed
|
||||||
final type = data['type'] as String?;
|
final type = data['type'] as String?;
|
||||||
|
|
||||||
debugPrint('[FCM] Handling message open - type: $type, data: $data');
|
debugPrint('[FCM] Handling message open - type: $type, data: $data');
|
||||||
|
|
||||||
final navigator = AppRoutes.rootNavigatorKey.currentState;
|
// Use the router directly for reliability
|
||||||
if (navigator == null) {
|
final router = AppRoutes.router;
|
||||||
debugPrint('[FCM] Navigator not available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'chat':
|
case 'chat':
|
||||||
|
|
@ -482,6 +479,8 @@ class NotificationService {
|
||||||
final conversationId = data['conversation_id'];
|
final conversationId = data['conversation_id'];
|
||||||
if (conversationId != null) {
|
if (conversationId != null) {
|
||||||
await _openConversation(conversationId.toString());
|
await _openConversation(conversationId.toString());
|
||||||
|
} else {
|
||||||
|
router.go(AppRoutes.secureChat);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -495,7 +494,7 @@ class NotificationService {
|
||||||
final postId = data['post_id'] ?? data['beacon_id'];
|
final postId = data['post_id'] ?? data['beacon_id'];
|
||||||
final target = data['target'];
|
final target = data['target'];
|
||||||
if (postId != null) {
|
if (postId != null) {
|
||||||
_navigateToPost(navigator, postId.toString(), target?.toString());
|
_navigateToPost(postId.toString(), target?.toString());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -505,9 +504,9 @@ class NotificationService {
|
||||||
case 'follow_accepted':
|
case 'follow_accepted':
|
||||||
final followerId = data['follower_id'];
|
final followerId = data['follower_id'];
|
||||||
if (followerId != null) {
|
if (followerId != null) {
|
||||||
navigator.context.push('${AppRoutes.userPrefix}/$followerId');
|
router.push('${AppRoutes.userPrefix}/$followerId');
|
||||||
} else {
|
} else {
|
||||||
navigator.context.go(AppRoutes.profile);
|
router.go(AppRoutes.profile);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -515,50 +514,60 @@ class NotificationService {
|
||||||
case 'beacon_report':
|
case 'beacon_report':
|
||||||
final beaconId = data['beacon_id'] ?? data['post_id'];
|
final beaconId = data['beacon_id'] ?? data['post_id'];
|
||||||
if (beaconId != null) {
|
if (beaconId != null) {
|
||||||
_navigateToPost(navigator, beaconId.toString(), 'beacon_map');
|
_navigateToPost(beaconId.toString(), 'beacon_map');
|
||||||
} else {
|
} else {
|
||||||
navigator.context.go(AppRoutes.beaconPrefix);
|
router.go(AppRoutes.beaconPrefix);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
debugPrint('[FCM] Unknown notification type: $type');
|
debugPrint('[FCM] Unknown notification type: $type');
|
||||||
|
// Retrieve generic target if available
|
||||||
|
final target = data['target'];
|
||||||
|
if (target != null) {
|
||||||
|
_handleGenericTarget(target.toString());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToPost(NavigatorState navigator, String postId, String? target) {
|
void _navigateToPost(String postId, String? target) {
|
||||||
|
final router = AppRoutes.router;
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case 'beacon_map':
|
case 'beacon_map':
|
||||||
navigator.context.go(AppRoutes.beaconPrefix);
|
router.go(AppRoutes.beaconPrefix);
|
||||||
break;
|
break;
|
||||||
case 'quip_feed':
|
case 'quip_feed':
|
||||||
navigator.context.go(AppRoutes.quips);
|
router.go(AppRoutes.quips);
|
||||||
break;
|
break;
|
||||||
case 'thread_view':
|
case 'thread_view':
|
||||||
case 'main_feed':
|
case 'main_feed':
|
||||||
default:
|
default:
|
||||||
navigator.context.push('${AppRoutes.postPrefix}/$postId');
|
router.push('${AppRoutes.postPrefix}/$postId');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openConversation(String conversationId) async {
|
void _handleGenericTarget(String target) {
|
||||||
final conversation =
|
final router = AppRoutes.router;
|
||||||
await SecureChatService.instance.getConversationById(conversationId);
|
switch (target) {
|
||||||
if (conversation == null) {
|
case 'secure_chat':
|
||||||
debugPrint('[FCM] Conversation not found: $conversationId');
|
router.go(AppRoutes.secureChat);
|
||||||
return;
|
break;
|
||||||
|
case 'profile':
|
||||||
|
router.go(AppRoutes.profile);
|
||||||
|
break;
|
||||||
|
case 'beacon_map':
|
||||||
|
router.go(AppRoutes.beaconPrefix);
|
||||||
|
break;
|
||||||
|
case 'quip_feed':
|
||||||
|
router.go(AppRoutes.quips);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final navigator = AppRoutes.rootNavigatorKey.currentState;
|
void _openConversation(String conversationId) {
|
||||||
if (navigator == null) return;
|
AppRoutes.router.push('${AppRoutes.secureChat}/$conversationId');
|
||||||
|
|
||||||
navigator.push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => SecureChatScreen(conversation: conversation),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue