diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 00000000..e8b41515 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.35.7" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 10cd2174..20dd50a4 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,6 @@ chrome/.packages .pdm bfg-1.14.0.jar lib/generated/ + +# FVM Version Cache +.fvm/ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6ccf0946..49f162e7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -52,6 +52,10 @@ + + diff --git a/lib/background/background.dart b/lib/background/background.dart index fef485dd..f9f6552e 100644 --- a/lib/background/background.dart +++ b/lib/background/background.dart @@ -69,7 +69,9 @@ Future serviceMain(ServiceInstance service) async { subscription.listen((event) async { try { - if (await eventStore.hasItem(event.id!)) return; + if (await eventStore.hasItem(event.id!)) { + return; + } await notification_service.retryNotification(event); } catch (e) { Logger().e('Error processing event', error: e); diff --git a/lib/features/notifications/services/background_notification_service.dart b/lib/features/notifications/services/background_notification_service.dart index 0cc2316f..a7879404 100644 --- a/lib/features/notifications/services/background_notification_service.dart +++ b/lib/features/notifications/services/background_notification_service.dart @@ -53,16 +53,18 @@ Future showLocalNotification(NostrEvent event) async { try { final mostroMessage = await _decryptAndProcessEvent(event); if (mostroMessage == null) return; - final sessions = await _loadSessionsFromDatabase(); final matchingSession = sessions.cast().firstWhere( (session) => session?.orderId == mostroMessage.id, orElse: () => null, ); - + final notificationData = await NotificationDataExtractor.extractFromMostroMessage(mostroMessage, null, session: matchingSession); - if (notificationData == null || notificationData.isTemporary) return; + + if (notificationData == null || notificationData.isTemporary) { + return; + } final notificationText = await _getLocalizedNotificationText(notificationData.action, notificationData.values); final expandedText = _getExpandedText(notificationData.values); @@ -79,7 +81,7 @@ Future showLocalNotification(NostrEvent event) async { enableVibration: true, ticker: notificationText.title, icon: '@drawable/ic_notification', - styleInformation: expandedText != null + styleInformation: expandedText != null ? BigTextStyleInformation(expandedText, contentTitle: notificationText.title) : null, category: AndroidNotificationCategory.message, @@ -111,25 +113,34 @@ Future showLocalNotification(NostrEvent event) async { Future _decryptAndProcessEvent(NostrEvent event) async { try { - if (event.kind != 4 && event.kind != 1059) return null; + if (event.kind != 4 && event.kind != 1059) { + return null; + } final sessions = await _loadSessionsFromDatabase(); + final matchingSession = sessions.cast().firstWhere( (s) => s?.tradeKey.public == event.recipient, orElse: () => null, ); - if (matchingSession == null) return null; + if (matchingSession == null) { + return null; + } final decryptedEvent = await event.unWrap(matchingSession.tradeKey.private); - if (decryptedEvent.content == null) return null; + if (decryptedEvent.content == null) { + return null; + } final result = jsonDecode(decryptedEvent.content!); - if (result is! List || result.isEmpty) return null; + if (result is! List || result.isEmpty) { + return null; + } final mostroMessage = MostroMessage.fromJson(result[0]); mostroMessage.timestamp = event.createdAt?.millisecondsSinceEpoch; - + return mostroMessage; } catch (e) { Logger().e('Decrypt error: $e'); @@ -258,25 +269,25 @@ String? _getExpandedText(Map values) { } -Future retryNotification(NostrEvent event, {int maxAttempts = 3}) async { - int attempt = 0; - bool success = false; - - while (!success && attempt < maxAttempts) { - try { - await showLocalNotification(event); - success = true; - } catch (e) { - attempt++; - if (attempt >= maxAttempts) { - Logger().e('Failed to show notification after $maxAttempts attempts: $e'); - break; - } - - // Exponential backoff: 1s, 2s, 4s, etc. - final backoffSeconds = pow(2, attempt - 1).toInt(); - Logger().e('Notification attempt $attempt failed: $e. Retrying in ${backoffSeconds}s'); - await Future.delayed(Duration(seconds: backoffSeconds)); - } - } +Future retryNotification(NostrEvent event, {int maxAttempts = 3}) async { + int attempt = 0; + bool success = false; + + while (!success && attempt < maxAttempts) { + try { + await showLocalNotification(event); + success = true; + } catch (e) { + attempt++; + if (attempt >= maxAttempts) { + Logger().e('Failed to show notification after $maxAttempts attempts: $e'); + break; + } + + // Exponential backoff: 1s, 2s, 4s, etc. + final backoffSeconds = pow(2, attempt - 1).toInt(); + Logger().e('Notification attempt $attempt failed: $e. Retrying in ${backoffSeconds}s'); + await Future.delayed(Duration(seconds: backoffSeconds)); + } + } } \ No newline at end of file diff --git a/lib/features/notifications/utils/notification_data_extractor.dart b/lib/features/notifications/utils/notification_data_extractor.dart index 02b277cd..35e04964 100644 --- a/lib/features/notifications/utils/notification_data_extractor.dart +++ b/lib/features/notifications/utils/notification_data_extractor.dart @@ -108,10 +108,22 @@ class NotificationDataExtractor { } break; + case Action.fiatSent: + // Notification for seller when buyer marks fiat as sent + // This requires seller to release the funds + final order = event.getPayload(); + if (order != null) { + values = { + 'fiat_code': order.fiatCode, + 'fiat_amount': order.fiatAmount, + }; + } + break; + case Action.fiatSentOk: // Only sellers should receive fiat confirmed notifications if (session?.role != Role.seller) return null; - + final peer = event.getPayload(); if (peer?.publicKey != null) { final buyerNym = ref != null