diff --git a/dogfooding/lib/main.dart b/dogfooding/lib/main.dart index 3c10c4ef9..bf8bd192c 100644 --- a/dogfooding/lib/main.dart +++ b/dogfooding/lib/main.dart @@ -13,6 +13,7 @@ import 'package:uni_links/uni_links.dart'; import 'firebase_options.dart'; import 'repos/app_repository.dart'; import 'repos/auth_repo.dart'; +import 'repos/token_service.dart'; import 'repos/user_repository.dart'; import 'src/routes/app_routes.dart'; import 'src/routes/routes.dart'; @@ -21,13 +22,31 @@ import 'src/utils/providers.dart'; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - await Firebase.initializeApp(); - await AppRepository.initStreamVideo(); + // As this runs in a separate isolate, we need to initialize and connect the + // user to StreamVideo again. + final appRepo = AppRepository(); + await appRepo.beginSession(); + final authRepo = AuthRepository( + tokenService: TokenService(), + streamVideo: appRepo.videoClient, + streamChat: appRepo.chatClient, + googleSignIn: GoogleSignIn(hostedDomain: 'getstream.io'), + ); + + final credentials = await UserRepository.instance.getUserCredentials(); + if (credentials == null) { + // The user is not logged in, so we don't need to handle the message. + return; + } + + await authRepo.loginWithUserInfo(credentials.user); + + // Once the setup is done, we can handle the message. await _handleRemoteMessage(message); } -Future _handleRemoteMessage(RemoteMessage message) async { - await StreamVideo.instance.handlePushNotification(message.data); +Future _handleRemoteMessage(RemoteMessage message) { + return StreamVideo.instance.handlePushNotification(message.data); } Future main() async { diff --git a/dogfooding/lib/repos/app_repository.dart b/dogfooding/lib/repos/app_repository.dart index 6b1ca8923..6e9b67862 100644 --- a/dogfooding/lib/repos/app_repository.dart +++ b/dogfooding/lib/repos/app_repository.dart @@ -14,12 +14,29 @@ import 'user_repository.dart'; class AppRepository { AppRepository(); - late final StreamVideo _streamVideoClient; - late final StreamChatClient _streamChatClient; + StreamVideo? _streamVideoClient; + StreamChatClient? _streamChatClient; - StreamVideo get videoClient => _streamVideoClient; + StreamVideo get videoClient { + final client = _streamVideoClient; + if (client == null) { + throw Exception( + 'Please initialise Stream Video by calling AppRepository.beginSession()', + ); + } + + return client; + } - StreamChatClient get chatClient => _streamChatClient; + StreamChatClient get chatClient { + final client = _streamChatClient; + if (client == null) { + throw Exception( + 'Please initialise Stream Chat by calling AppRepository.beginSession()', + ); + } + return client; + } static Future initStreamVideo() async { if (!StreamVideo.isInitialized()) { @@ -44,10 +61,10 @@ class AppRepository { Future beginSession() async { _streamVideoClient = await initStreamVideo(); unawaited(_setupLogger()); - _streamChatClient = _initChat(); + _streamChatClient = initStreamChat(); } - StreamChatClient _initChat() { + StreamChatClient initStreamChat() { return StreamChatClient( Env.apiKey, logLevel: Level.INFO, @@ -83,7 +100,7 @@ class AppRepository { Future createChatChannel({ required String channelId, }) async { - final channel = _streamChatClient.channel( + final channel = chatClient.channel( kMessageChannelType, id: channelId, ); @@ -93,8 +110,10 @@ class AppRepository { } Future endSession() async { - await _streamVideoClient.disconnectUser(); - await _streamChatClient.disconnectUser(); + await _streamVideoClient?.disconnectUser(); + _streamVideoClient = null; + await _streamChatClient?.disconnectUser(); + _streamChatClient = null; await UserRepository.instance.clear(); } } diff --git a/dogfooding/lib/repos/auth_repo.dart b/dogfooding/lib/repos/auth_repo.dart index 767d47d4d..1687d8a3b 100644 --- a/dogfooding/lib/repos/auth_repo.dart +++ b/dogfooding/lib/repos/auth_repo.dart @@ -8,6 +8,7 @@ import 'package:stream_video_flutter/stream_video_flutter.dart'; import '../env/env.dart'; import '../src/model/user_credentials.dart'; +import 'token_service.dart'; import 'user_repository.dart'; class AuthRepository { @@ -24,7 +25,6 @@ class AuthRepository { final GoogleSignIn googleSignIn; final _logger = taggedLogger(tag: 'SV:LoginViewState'); - // late final _googleSignIn = GoogleSignIn(hostedDomain: 'getstream.io'); UserToken? _userToken; @@ -51,7 +51,7 @@ class AuthRepository { id: data.user.id, image: data.user.image, name: data.user.name ?? '', - teams: data.user.teams ?? [], + teams: data.user.teams, role: data.user.role, ); diff --git a/packages/stream_video/lib/src/token/token_service.dart b/dogfooding/lib/repos/token_service.dart similarity index 100% rename from packages/stream_video/lib/src/token/token_service.dart rename to dogfooding/lib/repos/token_service.dart diff --git a/packages/stream_video/lib/src/persistence/shared_prefs_helper.dart b/packages/stream_video/lib/src/persistence/shared_prefs_helper.dart deleted file mode 100644 index 905039b5a..000000000 --- a/packages/stream_video/lib/src/persistence/shared_prefs_helper.dart +++ /dev/null @@ -1,48 +0,0 @@ - -import 'package:shared_preferences/shared_preferences.dart'; - -import '../models/models.dart'; - -const streamVideoPrefixKey = 'stream_video_'; - -const keyUserId = '${streamVideoPrefixKey}user_id'; -const keyUserName = '${streamVideoPrefixKey}user_name'; -const keyUserRole = '${streamVideoPrefixKey}user_role'; -const keyUserImage = '${streamVideoPrefixKey}user_image'; -const keyToken = '${streamVideoPrefixKey}token'; - -class SharedPrefsHelper { - SharedPrefsHelper({bool init = true}) { - if(init) { - initPrefs(); - } - } - - late SharedPreferences _preferences; - - Future initPrefs() async { - _preferences = await SharedPreferences.getInstance(); - } - - Future saveUserCredentials(UserInfo user) async { - await _preferences.setString(keyUserId, user.id); - await _preferences.setString(keyUserName, user.name); - await _preferences.setString(keyUserRole, user.role); - await _preferences.setString(keyUserImage, user.image ?? ''); - } - - UserInfo getSavedUser() { - final userId = _preferences.getString(keyUserId) ?? ''; - final userName = _preferences.getString(keyUserName) ?? ''; - final userRole = _preferences.getString(keyUserRole) ?? ''; - final userImage = _preferences.getString(keyUserImage) ?? ''; - return UserInfo(id: userId, role: userRole, name: userName, image: userImage); - } - - Future deleteSavedUser() async { - await _preferences.remove(keyUserId); - await _preferences.remove(keyUserName); - await _preferences.remove(keyUserRole); - await _preferences.remove(keyUserImage); - } -} diff --git a/packages/stream_video/lib/src/stream_video.dart b/packages/stream_video/lib/src/stream_video.dart index 5a8de96e2..6028fa05e 100644 --- a/packages/stream_video/lib/src/stream_video.dart +++ b/packages/stream_video/lib/src/stream_video.dart @@ -142,7 +142,6 @@ class StreamVideo { final _tokenManager = TokenManager(); final _subscriptions = Subscriptions(); - final _sharedPrefsHelper = SharedPrefsHelper(); late final CoordinatorClient _client; PushNotificationManager? pushNotificationManager; @@ -182,7 +181,6 @@ class StreamVideo { Future> connectUserWithProvider( UserInfo user, { required TokenProvider tokenProvider, - bool saveUser = true, }) async { _logger.i(() => '[connectUser] user.id : ${user.id}'); if (currentUser != null) { @@ -205,9 +203,6 @@ class StreamVideo { if (result is Failure) { return result; } - if (saveUser) { - await _sharedPrefsHelper.saveUserCredentials(user); - } _subscriptions.add(_idEvents, _client.events.listen(_onEvent)); _subscriptions.add(_idAppState, lifecycle.appState.listen(_onAppState)); await pushNotificationManager?.onUserLoggedIn(); @@ -284,7 +279,6 @@ class StreamVideo { } try { await _client.disconnectUser(); - await _sharedPrefsHelper.deleteSavedUser(); _subscriptions.cancelAll(); _tokenManager.reset(); @@ -391,16 +385,17 @@ class StreamVideo { return result; } - Future handlePushNotification(Map payload) { - return pushNotificationManager?.handlePushNotification(payload) ?? - Future.value(false); + Future handlePushNotification(Map payload) async { + final manager = pushNotificationManager; + if (manager == null) return false; + + return manager.handlePushNotification(payload); } - Future consumeIncomingCall() { + Future consumeIncomingCall() async { return pushNotificationManager?.consumeIncomingCall().then((data) { - return data?.let((it) => _makeCallFromCreated(data: it)); - }) ?? - Future.value(); + return data?.let((it) => _makeCallFromCreated(data: it)); + }); } } diff --git a/packages/stream_video/lib/stream_video.dart b/packages/stream_video/lib/stream_video.dart index 8b627e009..838239e49 100644 --- a/packages/stream_video/lib/stream_video.dart +++ b/packages/stream_video/lib/stream_video.dart @@ -14,7 +14,6 @@ export 'src/logger/impl/tagged_logger.dart'; export 'src/logger/stream_log.dart'; export 'src/logger/stream_logger.dart'; export 'src/models/models.dart'; -export 'src/persistence/shared_prefs_helper.dart'; export 'src/platform_detector/platform_detector.dart'; export 'src/push_notification/push_notification_manager.dart'; export 'src/sfu/data/models/sfu_connection_quality.dart'; @@ -22,7 +21,6 @@ export 'src/sfu/data/models/sfu_track_type.dart'; export 'src/sorting/call_participant_sorting_presets.dart'; export 'src/stream_video.dart'; export 'src/token/token.dart'; -export 'src/token/token_service.dart'; export 'src/types/other.dart'; export 'src/utils/result.dart'; export 'src/utils/string.dart'; diff --git a/packages/stream_video/pubspec.yaml b/packages/stream_video/pubspec.yaml index 0c7bbb1f8..c3721f718 100644 --- a/packages/stream_video/pubspec.yaml +++ b/packages/stream_video/pubspec.yaml @@ -29,7 +29,6 @@ dependencies: sdp_transform: ^0.3.2 synchronized: ^3.1.0 state_notifier: ^0.7.2+1 - shared_preferences: ^2.2.0 fixnum: ^1.1.0 dart_webrtc: ^1.0.17 webrtc_interface: ^1.0.13 diff --git a/packages/stream_video_push_notification/lib/src/stream_video_push_notification_event_channel.dart b/packages/stream_video_push_notification/lib/src/stream_video_push_notification_event_channel.dart index 41111e951..f920c5344 100644 --- a/packages/stream_video_push_notification/lib/src/stream_video_push_notification_event_channel.dart +++ b/packages/stream_video_push_notification/lib/src/stream_video_push_notification_event_channel.dart @@ -1,6 +1,8 @@ import 'package:flutter/services.dart'; import 'package:rxdart/rxdart.dart'; +// TODO: Verify if this is needed anymore? +// We already have FlutterCallkitIncoming.onEvent; (Event.actionCallIncoming) class StreamVideoPushNotificationEventChannel { const StreamVideoPushNotificationEventChannel( {EventChannel eventChannel = diff --git a/packages/stream_video_push_notification/lib/src/stream_video_push_notification_method_channel.dart b/packages/stream_video_push_notification/lib/src/stream_video_push_notification_method_channel.dart index f146aeab9..45b979c76 100644 --- a/packages/stream_video_push_notification/lib/src/stream_video_push_notification_method_channel.dart +++ b/packages/stream_video_push_notification/lib/src/stream_video_push_notification_method_channel.dart @@ -9,6 +9,8 @@ class StreamVideoPushNotificationMethodChannel { /// The method channel used to interact with the native platform. final MethodChannel _methodChannel; + // TODO: Verify if this is needed anymore? + // We already have FlutterCallkitIncoming.getDevicePushTokenVoIP(); /// Obtain the Device Push Token VoIp. Future getDevicePushTokenVoIP() async { return await _methodChannel.invokeMethod('getDevicePushTokenVoIP'); diff --git a/packages/stream_video_push_notification/lib/stream_video_push_notification.dart b/packages/stream_video_push_notification/lib/stream_video_push_notification.dart index b4fdf64bc..f48fde2db 100644 --- a/packages/stream_video_push_notification/lib/stream_video_push_notification.dart +++ b/packages/stream_video_push_notification/lib/stream_video_push_notification.dart @@ -6,7 +6,6 @@ import 'package:collection/collection.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stream_video/stream_video.dart'; import 'src/call_notification_wrapper.dart'; @@ -16,7 +15,6 @@ import 'src/stream_video_push_notification_method_channel.dart'; class StreamVideoPushNotificationManager implements PushNotificationManager { StreamVideoPushNotificationManager._create({ required CoordinatorClient client, - required SharedPreferences sharedPreferences, required CallNotificationWrapper callNotification, required StreamVideoPushNotificationMethodChannel methodChannel, required StreamVideoPushNotificationEventChannel eventChannel, @@ -24,7 +22,6 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { String? apnsProviderName, }) : _client = client, _callNotification = callNotification, - _sharedPreferences = sharedPreferences, _methodChannel = methodChannel, _eventChannel = eventChannel, _firebaseProviderName = firebaseProviderName, @@ -39,7 +36,6 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { final _logger = taggedLogger(tag: 'SV:PNManager'); final CoordinatorClient _client; final CallNotificationWrapper _callNotification; - final SharedPreferences _sharedPreferences; final StreamVideoPushNotificationMethodChannel _methodChannel; final StreamVideoPushNotificationEventChannel _eventChannel; final String? _firebaseProviderName; @@ -55,7 +51,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { } Future _registerAndroidDevice() async { - if(_firebaseProviderName == null) { + if (_firebaseProviderName == null) { _logger.w(() => '[registerAndroidDevice] No Firebase provider name set'); return; } @@ -76,7 +72,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { } Future _registerIOSDevice() async { - if(_apnsProviderName == null) { + if (_apnsProviderName == null) { _logger.w(() => '[registerIOSDevice] No APNS provider name set'); return; } @@ -105,10 +101,9 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { final streamCallCid = StreamCallCid(cid: cid); final call = await _from(streamCallCid); if (call != null) { - await _callNotification.showCallNotification( - streamCallCid: streamCallCid, + await showCallIncoming( + cid: streamCallCid, callers: call.metadata.users.values.map((e) => e.name).join(', '), - isVideoCall: true, avatarUrl: call.metadata.users.values.firstOrNull?.image, onCallAccepted: _acceptCall, onCallRejected: _rejectCall, @@ -127,7 +122,6 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { } Future _acceptCall(StreamCallCid streamCallCid) async { - await _sharedPreferences.setString('incomingCallCid', streamCallCid.value); await _client.acceptCall(cid: streamCallCid); } @@ -153,29 +147,8 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { } Future _from(StreamCallCid streamCallCid) async { - SharedPrefsHelper prefs = SharedPrefsHelper(init: false); - await prefs.initPrefs(); - var user = prefs.getSavedUser(); - - await StreamVideo.instance.connectUserWithProvider( - user, - tokenProvider: TokenProvider.dynamic( - (userId) { - return TokenService() - .loadToken(apiKey: StreamVideo.instance.apiKey, userId: userId); - }, - ), - saveUser: false, - ); - var callData = (await _client.getOrCreateCall(callCid: streamCallCid)) - .getDataOrNull() - ?.data; - - // TODO(Deven): Explore why this does not work - // await StreamVideo.instance.disconnectUser(); - // await StreamVideo.reset(disconnectUser: true); - - return callData; + final result = await _client.getOrCreateCall(callCid: streamCallCid); + return result.getDataOrNull()?.data; } @override @@ -185,11 +158,13 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { required void Function(StreamCallCid streamCallCid) onCallAccepted, required void Function(StreamCallCid streamCallCid) onCallRejected, bool isVideoCall = true, + String? avatarUrl, }) { return _callNotification.showCallNotification( streamCallCid: cid, callers: callers, isVideoCall: isVideoCall, + avatarUrl: avatarUrl, onCallAccepted: onCallAccepted, onCallRejected: onCallRejected, ); @@ -197,26 +172,18 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { @override Future consumeIncomingCall() async { - var incomingCid = ''; + final calls = await FlutterCallkitIncoming.activeCalls(); - var calls = await FlutterCallkitIncoming.activeCalls(); + // return if calls and data is not available. + if (calls is! List) return null; + final call = calls.lastOrNull; + if(call is! Map) return null; - if (Platform.isIOS) { - if (calls is List && calls.isNotEmpty) { - incomingCid = calls[0]['extra']['incomingCallCid']; - } - } else if (Platform.isAndroid) { - // TODO(Deven): Use the same approach from iOS for incoming Android calls - // We need to wait for a second to be sure sharedPreferences has been updated - await _sharedPreferences.reload(); - incomingCid = _sharedPreferences.getString('incomingCallCid') ?? ''; - await _sharedPreferences.remove('incomingCallCid'); - } + final incomingCid = call['extra']?['incomingCallCid']; - if (incomingCid != '') { - return _from(StreamCallCid(cid: incomingCid)); - } - return Future.value(); + if (incomingCid == null) return null; + + return _from(StreamCallCid(cid: incomingCid)); } @override @@ -227,21 +194,18 @@ class StreamVideoPushNotificationManager implements PushNotificationManager { static PushNotificationManagerFactory factory({ String? firebaseProviderName, String? apnsProviderName, - SharedPreferences? sharedPreferences, - CallNotificationWrapper? callNotification, - StreamVideoPushNotificationMethodChannel? methodChannel, - StreamVideoPushNotificationEventChannel? eventChannel, + CallNotificationWrapper callNotification = const CallNotificationWrapper(), + StreamVideoPushNotificationMethodChannel methodChannel = + const StreamVideoPushNotificationMethodChannel(), + StreamVideoPushNotificationEventChannel eventChannel = + const StreamVideoPushNotificationEventChannel(), }) { return (client) async { return StreamVideoPushNotificationManager._create( client: client, - sharedPreferences: - sharedPreferences ?? await SharedPreferences.getInstance(), - callNotification: callNotification ?? const CallNotificationWrapper(), - methodChannel: - methodChannel ?? const StreamVideoPushNotificationMethodChannel(), - eventChannel: - eventChannel ?? const StreamVideoPushNotificationEventChannel(), + callNotification: callNotification, + methodChannel: methodChannel, + eventChannel: eventChannel, apnsProviderName: apnsProviderName, firebaseProviderName: firebaseProviderName, ); diff --git a/packages/stream_video_push_notification/pubspec.yaml b/packages/stream_video_push_notification/pubspec.yaml index e841edf2f..83ffb6191 100644 --- a/packages/stream_video_push_notification/pubspec.yaml +++ b/packages/stream_video_push_notification/pubspec.yaml @@ -12,7 +12,6 @@ environment: dependencies: flutter: sdk: flutter - shared_preferences: ^2.2.0 firebase_messaging: ^14.6.5 stream_video: ^0.0.2 flutter_callkit_incoming: ^2.0.0+1 diff --git a/packages/stream_video_push_notification/test/mocks.dart b/packages/stream_video_push_notification/test/mocks.dart index af49f1b3e..bd3531f2e 100644 --- a/packages/stream_video_push_notification/test/mocks.dart +++ b/packages/stream_video_push_notification/test/mocks.dart @@ -1,6 +1,5 @@ import 'package:flutter/services.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stream_video/stream_video.dart'; import 'package:stream_video_push_notification/src/call_notification_wrapper.dart'; @@ -11,6 +10,4 @@ class CoordinatorClientMock extends Mock implements CoordinatorClient {} class CallNotificationWrapperMock extends Mock implements CallNotificationWrapper {} -class SharedPreferencesMock extends Mock implements SharedPreferences {} - class EventChannelMock extends Mock implements EventChannel {} diff --git a/packages/stream_video_push_notification/test/stream_video_push_notification_test.dart b/packages/stream_video_push_notification/test/stream_video_push_notification_test.dart index 82aa91d29..eb98a1c3f 100644 --- a/packages/stream_video_push_notification/test/stream_video_push_notification_test.dart +++ b/packages/stream_video_push_notification/test/stream_video_push_notification_test.dart @@ -11,7 +11,6 @@ import 'mocks.dart'; Future main() async { final client = CoordinatorClientMock(); final callNotificationWrapper = CallNotificationWrapperMock(); - final sharedPreferences = SharedPreferencesMock(); final eventChannelMock = EventChannelMock(); final streamVideoEventChannel = StreamVideoPushNotificationEventChannel(eventChannel: eventChannelMock); @@ -82,14 +81,8 @@ Future main() async { onCallAccepted: any(named: 'onCallAccepted'), onCallRejected: any(named: 'onCallRejected'), )).thenAnswer((invocation) => Future.value()); - when(() => sharedPreferences.setString(any(), any())) - .thenAnswer((_) => Future.value(true)); - when(sharedPreferences.reload).thenAnswer((_) => Future.value()); - when(() => sharedPreferences.remove('incomingCallCid')) - .thenAnswer((_) => Future.value(true)); final factory = StreamVideoPushNotificationManager.factory( - sharedPreferences: sharedPreferences, callNotification: callNotificationWrapper, eventChannel: streamVideoEventChannel, ); @@ -146,8 +139,6 @@ Future main() async { await sut.handlePushNotification(data); - verify(() => sharedPreferences.setString('incomingCallCid', 'call:123')) - .called(1); verify(() => client.getOrCreateCall( callCid: streamCallCid, )).called(1); @@ -187,8 +178,6 @@ Future main() async { }); test('When a call was accepted, it should be able to be consumed', () async { - when(() => sharedPreferences.getString('incomingCallCid')) - .thenReturn('call:123'); final result = await sut.consumeIncomingCall(); expect(result, callCreatedData); @@ -201,7 +190,6 @@ Future main() async { test("When there aren't any accepted call, it shouldn't consume any call", () async { - when(() => sharedPreferences.getString('incomingCallCid')).thenReturn(null); final result = await sut.consumeIncomingCall(); expect(result, null);