From a3ac048c87cd21664936e49c681b2ad61f8108b8 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Thu, 12 Oct 2023 16:31:10 -0700 Subject: [PATCH] wip binding for flutter_local_notifications --- lib/model/binding.dart | 7 +++ lib/notif.dart | 6 +-- test/model/binding.dart | 105 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/lib/model/binding.dart b/lib/model/binding.dart index 6b3473af79e..0a7fbb674cb 100644 --- a/lib/model/binding.dart +++ b/lib/model/binding.dart @@ -2,6 +2,7 @@ import 'package:device_info_plus/device_info_plus.dart' as device_info_plus; import 'package:firebase_core/firebase_core.dart' as firebase_core; import 'package:firebase_messaging/firebase_messaging.dart' as firebase_messaging; import 'package:flutter/foundation.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:url_launcher/url_launcher.dart' as url_launcher; import '../firebase_options.dart'; @@ -98,6 +99,9 @@ abstract class ZulipBinding { /// Wraps [firebase_messaging.FirebaseMessaging.onMessage]. Stream get firebaseMessagingOnMessage; + + /// Wraps the [FlutterLocalNotificationsPlugin] singleton constructor. + FlutterLocalNotificationsPlugin get notif; } /// Like [device_info_plus.BaseDeviceInfo], but without things we don't use. @@ -180,4 +184,7 @@ class LiveZulipBinding extends ZulipBinding { Stream get firebaseMessagingOnMessage { return firebase_messaging.FirebaseMessaging.onMessage; } + + @override + FlutterLocalNotificationsPlugin get notif => FlutterLocalNotificationsPlugin(); } diff --git a/lib/notif.dart b/lib/notif.dart index bd5beebfd6a..10003df370f 100644 --- a/lib/notif.dart +++ b/lib/notif.dart @@ -46,7 +46,7 @@ class NotificationService { // (in order to avoid calling for permissions) ZulipBinding.instance.firebaseMessagingOnMessage.listen(_onRemoteMessage); - FlutterLocalNotificationsPlugin().initialize( + ZulipBinding.instance.notif.initialize( const InitializationSettings( android: AndroidInitializationSettings('zulip_notification'), ), @@ -126,7 +126,7 @@ class NotificationChannelManager { static final _kVibrationPattern = Int64List.fromList([0, 125, 100, 450]); static void _ensureChannel() async { // TODO "ensure" - final plugin = FlutterLocalNotificationsPlugin(); + final plugin = ZulipBinding.instance.notif; await plugin.resolvePlatformSpecificImplementation() ?.createNotificationChannel(AndroidNotificationChannel( _kChannelId, @@ -157,7 +157,7 @@ class NotificationDisplayManager { FcmMessageDmRecipient() => data.senderFullName, }; - FlutterLocalNotificationsPlugin().show( + ZulipBinding.instance.notif.show( _kNotificationId, title, data.content, // TODO diff --git a/test/model/binding.dart b/test/model/binding.dart index 5939808652c..d8a5ea3e49a 100644 --- a/test/model/binding.dart +++ b/test/model/binding.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart'; import 'package:test/fake.dart'; import 'package:url_launcher/url_launcher.dart' as url_launcher; import 'package:zulip/model/binding.dart'; @@ -63,6 +65,7 @@ class TestZulipBinding extends ZulipBinding { _resetLaunchUrl(); _resetDeviceInfo(); _resetFirebase(); + _resetNotif(); } /// The current global store offered to a [GlobalStoreWidget]. @@ -188,6 +191,17 @@ class TestZulipBinding extends ZulipBinding { @override Stream get firebaseMessagingOnMessage => firebaseMessaging.onMessage.stream; + + void _resetNotif() { + _notifPlugin = null; + } + + FakeFlutterLocalNotificationsPlugin? _notifPlugin; + + @override + FakeFlutterLocalNotificationsPlugin get notif { + return (_notifPlugin ??= FakeFlutterLocalNotificationsPlugin()); + } } class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { @@ -228,3 +242,94 @@ class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { StreamController onMessage = StreamController.broadcast(); } + +class FakeFlutterLocalNotificationsPlugin extends Fake implements FlutterLocalNotificationsPlugin { + InitializationSettings? initializationSettings; + DidReceiveNotificationResponseCallback? onDidReceiveNotificationResponse; + DidReceiveBackgroundNotificationResponseCallback? onDidReceiveBackgroundNotificationResponse; + + @override + Future initialize( + InitializationSettings initializationSettings, { + DidReceiveNotificationResponseCallback? onDidReceiveNotificationResponse, + DidReceiveBackgroundNotificationResponseCallback? onDidReceiveBackgroundNotificationResponse, + }) async { + assert(this.initializationSettings == null); + this.initializationSettings = initializationSettings; + this.onDidReceiveNotificationResponse = onDidReceiveNotificationResponse; + this.onDidReceiveBackgroundNotificationResponse = onDidReceiveBackgroundNotificationResponse; + return true; + } + + FlutterLocalNotificationsPlatform? _platform; + + @override + T? resolvePlatformSpecificImplementation() { + // This follows the logic of the base class's implementation, + // but supplies our fakes for the per-platform classes. + assert(initializationSettings != null); + assert(T != FlutterLocalNotificationsPlatform); + if (kIsWeb) return null; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + assert(_platform == null || _platform is FakeAndroidFlutterLocalNotificationsPlugin); + if (T != AndroidFlutterLocalNotificationsPlugin) return null; + return (_platform ??= FakeAndroidFlutterLocalNotificationsPlugin()) as T?; + + case TargetPlatform.iOS: + assert(_platform == null || _platform is FakeIOSFlutterLocalNotificationsPlugin); + if (T != IOSFlutterLocalNotificationsPlugin) return null; + return (_platform ??= FakeIOSFlutterLocalNotificationsPlugin()) as T?; + + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + case TargetPlatform.fuchsia: + return null; + } + } + + /// Consume the log of calls made to [show]. + /// + /// This returns a list of the arguments to all calls made + /// to [show] since the last call to this method. + List takeShowCalls() { + final result = _showCalls; + _showCalls = []; + return result; + } + List _showCalls = []; + + @override + Future show(int id, String? title, String? body, + NotificationDetails? notificationDetails, {String? payload}) async { + assert(initializationSettings != null); + _showCalls.add((id, title, body, notificationDetails, payload: payload)); + } +} + +typedef FlutterLocalNotificationsPluginShowCall = ( + int id, String? title, String? body, + NotificationDetails? notificationDetails, {String? payload} +); + +class FakeAndroidFlutterLocalNotificationsPlugin extends Fake implements AndroidFlutterLocalNotificationsPlugin { + /// Consume the log of calls made to [createNotificationChannel]. + /// + /// This returns a list of the arguments to all calls made + /// to [createNotificationChannel] since the last call to this method. + List takeCreatedChannels() { + final result = _createdChannels; + _createdChannels = []; + return result; + } + List _createdChannels = []; + + @override + Future createNotificationChannel(AndroidNotificationChannel notificationChannel) async { + _createdChannels.add(notificationChannel); + } +} + +class FakeIOSFlutterLocalNotificationsPlugin extends Fake implements IOSFlutterLocalNotificationsPlugin { +}