From 8c7e98ae5db9772f76255f034e252899bb3c9f45 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Tue, 9 Jun 2026 14:44:44 -0400 Subject: [PATCH] Show foreground push notifications on iOS iOS suppresses an incoming push banner while the app is foregrounded unless the app opts in. _ensureForegroundDisplay now calls setForegroundNotificationPresentationOptions on iOS so the OS presents the push directly, avoiding a double notification / delegate clash with flutter_local_notifications. Android keeps the onMessage local-notification path, which it still requires. Co-Authored-By: Claude Opus 4.8 --- TODO.md | 2 +- frontend/lib/utils/push_service.dart | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 7ab36ea..69f7673 100644 --- a/TODO.md +++ b/TODO.md @@ -12,7 +12,7 @@ - [x] Test in-house for 1 week - [x] Implement push notifications - [x] Release 0.2.0 -- [ ] Install in test server and test app on iPhone +- [x] Install in test server and test app on iPhone - [ ] Test in-house for 3 days - [ ] Release 0.2.1 (or as many versions as needed) - [ ] Publish website diff --git a/frontend/lib/utils/push_service.dart b/frontend/lib/utils/push_service.dart index 4624aab..66ebf97 100644 --- a/frontend/lib/utils/push_service.dart +++ b/frontend/lib/utils/push_service.dart @@ -99,17 +99,34 @@ final FlutterLocalNotificationsPlugin _localNotifications = // request comes from startup (push already enabled) or from enable() on toggle. bool _foregroundDisplayWired = false; -// Renders push notifications that arrive while the app is in the foreground (the -// OS displays backgrounded/terminated ones itself); taps just open the app, so no +// Ensures push notifications are shown while the app is in the foreground (the OS +// displays backgrounded/terminated ones itself); taps just open the app, so no // tap handler is wired. Idempotent; a no-op on platforms without push. +// +// The two platforms diverge in the foreground: iOS suppresses the incoming push +// banner unless we opt in via setForegroundNotificationPresentationOptions, after +// which the OS presents the original push itself (no re-show needed, which also +// avoids a double notification / UNUserNotificationCenter delegate clash with +// flutter_local_notifications). Android never displays foreground `notification` +// messages itself, so there we render them through a local-notification channel +// from onMessage. Future _ensureForegroundDisplay() async { if (!_pushSupported || _foregroundDisplayWired) return; _foregroundDisplayWired = true; + if (_isIOS) { + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + return; + } + await _localNotifications.initialize( const InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), - iOS: DarwinInitializationSettings(), ), ); await _localNotifications @@ -133,7 +150,6 @@ Future _ensureForegroundDisplay() async { importance: Importance.high, priority: Priority.high, ), - iOS: const DarwinNotificationDetails(), ), ); });