feat: Enhance APNS token retrieval with retries and add cancel functionality to revert changes in notification and privacy settings.
This commit is contained in:
@@ -123,11 +123,18 @@ class PushNotificationService {
|
|||||||
|
|
||||||
Future<void> _getAndRegisterToken() async {
|
Future<void> _getAndRegisterToken() async {
|
||||||
try {
|
try {
|
||||||
// On iOS, wait for APNS token before requesting FCM token
|
// On iOS, wait for APNS token before requesting FCM token.
|
||||||
|
// Apple may not deliver it immediately — retry a few times.
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
final apnsToken = await _messaging!.getAPNSToken();
|
String? apnsToken;
|
||||||
|
for (int attempt = 0; attempt < 5; attempt++) {
|
||||||
|
apnsToken = await _messaging!.getAPNSToken();
|
||||||
|
if (apnsToken != null) break;
|
||||||
|
debugPrint('[FCM] APNS token not available yet, retrying in ${attempt + 1}s...');
|
||||||
|
await Future.delayed(Duration(seconds: attempt + 1));
|
||||||
|
}
|
||||||
if (apnsToken == null) {
|
if (apnsToken == null) {
|
||||||
debugPrint('[FCM] APNS token not available yet (iOS simulator?), skipping FCM token registration');
|
debugPrint('[FCM] APNS token not available after retries (simulator?), skipping FCM token registration');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:real_estate_mobile/core/constants/app_colors.dart';
|
import 'package:real_estate_mobile/core/constants/app_colors.dart';
|
||||||
import 'package:real_estate_mobile/features/notifications/data/models/notification_model.dart';
|
import 'package:real_estate_mobile/features/notifications/data/models/notification_model.dart';
|
||||||
import 'package:real_estate_mobile/features/notifications/presentation/providers/notification_provider.dart';
|
import 'package:real_estate_mobile/features/notifications/presentation/providers/notification_provider.dart';
|
||||||
@@ -248,6 +249,7 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||||||
.read(notificationListProvider.notifier)
|
.read(notificationListProvider.notifier)
|
||||||
.markAsRead(notification.id);
|
.markAsRead(notification.id);
|
||||||
}
|
}
|
||||||
|
_navigateToNotification(notification);
|
||||||
},
|
},
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -359,6 +361,36 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _navigateToNotification(AppNotification notification) {
|
||||||
|
final data = notification.data;
|
||||||
|
final type = data?['type'] ?? notification.type;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'message':
|
||||||
|
final conversationId = data?['conversationId'];
|
||||||
|
if (conversationId != null) {
|
||||||
|
context.push('/messages/chat/$conversationId');
|
||||||
|
} else {
|
||||||
|
context.go('/messages');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'connection_request':
|
||||||
|
case 'connection':
|
||||||
|
context.go('/agent/network');
|
||||||
|
break;
|
||||||
|
case 'connection_response':
|
||||||
|
final status = data?['status'];
|
||||||
|
if (status == 'ACCEPTED') {
|
||||||
|
context.go('/messages');
|
||||||
|
} else {
|
||||||
|
context.go('/home');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IconData _iconForType(String type) {
|
IconData _iconForType(String type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'connection':
|
case 'connection':
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
bool _isLoadingNotifications = false;
|
bool _isLoadingNotifications = false;
|
||||||
bool _isSavingNotifications = false;
|
bool _isSavingNotifications = false;
|
||||||
|
|
||||||
|
// Snapshot of original values for cancel/reset
|
||||||
|
Map<String, Map<String, bool>> _originalPrefs = {};
|
||||||
|
String _originalFrequency = 'instant';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -55,6 +59,12 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
_digestFrequency = data['digestFrequency'] as String? ?? 'instant';
|
_digestFrequency = data['digestFrequency'] as String? ?? 'instant';
|
||||||
_isLoadingNotifications = false;
|
_isLoadingNotifications = false;
|
||||||
_notificationsLoaded = true;
|
_notificationsLoaded = true;
|
||||||
|
// Save snapshot for cancel/reset
|
||||||
|
_originalPrefs = {
|
||||||
|
for (final e in _notificationPrefs.entries)
|
||||||
|
e.key: Map<String, bool>.from(e.value),
|
||||||
|
};
|
||||||
|
_originalFrequency = _digestFrequency;
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -144,8 +154,12 @@ class _NotificationsTabState extends ConsumerState<NotificationsTab> {
|
|||||||
ProfileActionButtons(
|
ProfileActionButtons(
|
||||||
onSave: _saveNotificationSettings,
|
onSave: _saveNotificationSettings,
|
||||||
onCancel: () {
|
onCancel: () {
|
||||||
_notificationsLoaded = false;
|
setState(() {
|
||||||
_loadNotificationPreferences();
|
for (final key in _originalPrefs.keys) {
|
||||||
|
_notificationPrefs[key] = Map<String, bool>.from(_originalPrefs[key]!);
|
||||||
|
}
|
||||||
|
_digestFrequency = _originalFrequency;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isSaving: _isSavingNotifications,
|
isSaving: _isSavingNotifications,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ class _PrivacyTabState extends ConsumerState<PrivacyTab> {
|
|||||||
bool _isSavingPrivacy = false;
|
bool _isSavingPrivacy = false;
|
||||||
bool _privacyLoaded = false;
|
bool _privacyLoaded = false;
|
||||||
|
|
||||||
|
// Snapshot for cancel/reset
|
||||||
|
String _origProfileVisibility = 'public';
|
||||||
|
String _origContactInfo = 'connections';
|
||||||
|
String _origActivityStatus = 'public';
|
||||||
|
bool _origShareAnalytics = false;
|
||||||
|
bool _origPersonalizedAds = false;
|
||||||
|
bool _origThirdPartySharing = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -50,6 +58,13 @@ class _PrivacyTabState extends ConsumerState<PrivacyTab> {
|
|||||||
dataSettings['thirdPartySharing'] as bool? ?? false;
|
dataSettings['thirdPartySharing'] as bool? ?? false;
|
||||||
_isLoadingPrivacy = false;
|
_isLoadingPrivacy = false;
|
||||||
_privacyLoaded = true;
|
_privacyLoaded = true;
|
||||||
|
// Save snapshot for cancel/reset
|
||||||
|
_origProfileVisibility = _profileVisibility;
|
||||||
|
_origContactInfo = _contactInfo;
|
||||||
|
_origActivityStatus = _activityStatus;
|
||||||
|
_origShareAnalytics = _shareAnalytics;
|
||||||
|
_origPersonalizedAds = _personalizedAds;
|
||||||
|
_origThirdPartySharing = _thirdPartySharing;
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
setState(() => _isLoadingPrivacy = false);
|
setState(() => _isLoadingPrivacy = false);
|
||||||
@@ -150,8 +165,14 @@ class _PrivacyTabState extends ConsumerState<PrivacyTab> {
|
|||||||
ProfileActionButtons(
|
ProfileActionButtons(
|
||||||
onSave: _savePrivacyPreferences,
|
onSave: _savePrivacyPreferences,
|
||||||
onCancel: () {
|
onCancel: () {
|
||||||
_privacyLoaded = false;
|
setState(() {
|
||||||
_loadPrivacyPreferences();
|
_profileVisibility = _origProfileVisibility;
|
||||||
|
_contactInfo = _origContactInfo;
|
||||||
|
_activityStatus = _origActivityStatus;
|
||||||
|
_shareAnalytics = _origShareAnalytics;
|
||||||
|
_personalizedAds = _origPersonalizedAds;
|
||||||
|
_thirdPartySharing = _origThirdPartySharing;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isSaving: _isSavingPrivacy),
|
isSaving: _isSavingPrivacy),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|||||||
Reference in New Issue
Block a user