Skip to content

Commit e0ea3b9

Browse files
Fix background notifications failing in release builds (#378)
* Add FVM configuration for Flutter version management * Update background notification icon * Update local notification icon to use dedicated drawable Changes the local notification icon from `ic_bg_service_small` to `ic_notification` and adds metadata to preserve the dedicated notification icon in AndroidManifest.xml * Update notification data extractor to handle fiatSent action * Format code with consistent early return style
1 parent cc881cf commit e0ea3b9

File tree

4 files changed

+61
-32
lines changed

4 files changed

+61
-32
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
<meta-data
5353
android:name="com.google.firebase.messaging.default_notification_icon"
5454
android:resource="@drawable/ic_bg_service_small" />
55+
<!-- Preserve ic_notification for local notifications -->
56+
<meta-data
57+
android:name="com.app.notification_icon"
58+
android:resource="@drawable/ic_notification" />
5559

5660
</application>
5761

lib/background/background.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ Future<void> serviceMain(ServiceInstance service) async {
6969

7070
subscription.listen((event) async {
7171
try {
72-
if (await eventStore.hasItem(event.id!)) return;
72+
if (await eventStore.hasItem(event.id!)) {
73+
return;
74+
}
7375
await notification_service.retryNotification(event);
7476
} catch (e) {
7577
Logger().e('Error processing event', error: e);

lib/features/notifications/services/background_notification_service.dart

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ Future<void> showLocalNotification(NostrEvent event) async {
5353
try {
5454
final mostroMessage = await _decryptAndProcessEvent(event);
5555
if (mostroMessage == null) return;
56-
5756

5857
final sessions = await _loadSessionsFromDatabase();
5958
final matchingSession = sessions.cast<Session?>().firstWhere(
6059
(session) => session?.orderId == mostroMessage.id,
6160
orElse: () => null,
6261
);
63-
62+
6463
final notificationData = await NotificationDataExtractor.extractFromMostroMessage(mostroMessage, null, session: matchingSession);
65-
if (notificationData == null || notificationData.isTemporary) return;
64+
65+
if (notificationData == null || notificationData.isTemporary) {
66+
return;
67+
}
6668

6769
final notificationText = await _getLocalizedNotificationText(notificationData.action, notificationData.values);
6870
final expandedText = _getExpandedText(notificationData.values);
@@ -79,7 +81,7 @@ Future<void> showLocalNotification(NostrEvent event) async {
7981
enableVibration: true,
8082
ticker: notificationText.title,
8183
icon: '@drawable/ic_notification',
82-
styleInformation: expandedText != null
84+
styleInformation: expandedText != null
8385
? BigTextStyleInformation(expandedText, contentTitle: notificationText.title)
8486
: null,
8587
category: AndroidNotificationCategory.message,
@@ -111,25 +113,34 @@ Future<void> showLocalNotification(NostrEvent event) async {
111113

112114
Future<MostroMessage?> _decryptAndProcessEvent(NostrEvent event) async {
113115
try {
114-
if (event.kind != 4 && event.kind != 1059) return null;
116+
if (event.kind != 4 && event.kind != 1059) {
117+
return null;
118+
}
115119

116120
final sessions = await _loadSessionsFromDatabase();
121+
117122
final matchingSession = sessions.cast<Session?>().firstWhere(
118123
(s) => s?.tradeKey.public == event.recipient,
119124
orElse: () => null,
120125
);
121126

122-
if (matchingSession == null) return null;
127+
if (matchingSession == null) {
128+
return null;
129+
}
123130

124131
final decryptedEvent = await event.unWrap(matchingSession.tradeKey.private);
125-
if (decryptedEvent.content == null) return null;
132+
if (decryptedEvent.content == null) {
133+
return null;
134+
}
126135

127136
final result = jsonDecode(decryptedEvent.content!);
128-
if (result is! List || result.isEmpty) return null;
137+
if (result is! List || result.isEmpty) {
138+
return null;
139+
}
129140

130141
final mostroMessage = MostroMessage.fromJson(result[0]);
131142
mostroMessage.timestamp = event.createdAt?.millisecondsSinceEpoch;
132-
143+
133144
return mostroMessage;
134145
} catch (e) {
135146
Logger().e('Decrypt error: $e');
@@ -258,25 +269,25 @@ String? _getExpandedText(Map<String, dynamic> values) {
258269
}
259270

260271

261-
Future<void> retryNotification(NostrEvent event, {int maxAttempts = 3}) async {
262-
int attempt = 0;
263-
bool success = false;
264-
265-
while (!success && attempt < maxAttempts) {
266-
try {
267-
await showLocalNotification(event);
268-
success = true;
269-
} catch (e) {
270-
attempt++;
271-
if (attempt >= maxAttempts) {
272-
Logger().e('Failed to show notification after $maxAttempts attempts: $e');
273-
break;
274-
}
275-
276-
// Exponential backoff: 1s, 2s, 4s, etc.
277-
final backoffSeconds = pow(2, attempt - 1).toInt();
278-
Logger().e('Notification attempt $attempt failed: $e. Retrying in ${backoffSeconds}s');
279-
await Future.delayed(Duration(seconds: backoffSeconds));
280-
}
281-
}
272+
Future<void> retryNotification(NostrEvent event, {int maxAttempts = 3}) async {
273+
int attempt = 0;
274+
bool success = false;
275+
276+
while (!success && attempt < maxAttempts) {
277+
try {
278+
await showLocalNotification(event);
279+
success = true;
280+
} catch (e) {
281+
attempt++;
282+
if (attempt >= maxAttempts) {
283+
Logger().e('Failed to show notification after $maxAttempts attempts: $e');
284+
break;
285+
}
286+
287+
// Exponential backoff: 1s, 2s, 4s, etc.
288+
final backoffSeconds = pow(2, attempt - 1).toInt();
289+
Logger().e('Notification attempt $attempt failed: $e. Retrying in ${backoffSeconds}s');
290+
await Future.delayed(Duration(seconds: backoffSeconds));
291+
}
292+
}
282293
}

lib/features/notifications/utils/notification_data_extractor.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,22 @@ class NotificationDataExtractor {
108108
}
109109
break;
110110

111+
case Action.fiatSent:
112+
// Notification for seller when buyer marks fiat as sent
113+
// This requires seller to release the funds
114+
final order = event.getPayload<Order>();
115+
if (order != null) {
116+
values = {
117+
'fiat_code': order.fiatCode,
118+
'fiat_amount': order.fiatAmount,
119+
};
120+
}
121+
break;
122+
111123
case Action.fiatSentOk:
112124
// Only sellers should receive fiat confirmed notifications
113125
if (session?.role != Role.seller) return null;
114-
126+
115127
final peer = event.getPayload<Peer>();
116128
if (peer?.publicKey != null) {
117129
final buyerNym = ref != null

0 commit comments

Comments
 (0)