From 4de25a778cf005e84587879383b9c7d41fe3fa8d Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 29 Nov 2022 15:34:06 +0700 Subject: [PATCH] TF-439 Implement get firebase subscription --- .../datasource_impl/fcm_datasource_impl.dart | 59 +++++++++++++++++++ .../hive_fcm_datasource_impl.dart | 6 ++ .../data/network/fcm_api.dart | 40 +++++++++++++ .../data/repository/fcm_repository_impl.dart | 25 ++++---- .../domain/exceptions/fcm_exception.dart | 4 +- .../get_firebase_subscription_state.dart | 25 ++++++++ .../get_firebase_subscription_interactor.dart | 23 ++++++++ .../bindings/fcm_interactor_bindings.dart | 16 ++++- .../controller/fcm_token_handler.dart | 38 +++++++++--- .../presentation/services/fcm_receiver.dart | 4 +- .../presentation/services/fcm_service.dart | 7 ++- .../bindings/network/network_bindings.dart | 2 + lib/main/routes/route_navigation.dart | 10 +++- 13 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart create mode 100644 lib/features/push_notification/data/network/fcm_api.dart create mode 100644 lib/features/push_notification/domain/state/get_firebase_subscription_state.dart create mode 100644 lib/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart diff --git a/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart b/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart new file mode 100644 index 000000000..1ee990892 --- /dev/null +++ b/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart @@ -0,0 +1,59 @@ +import 'package:fcm/model/firebase_subscription.dart'; +import 'package:fcm/model/type_name.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:model/fcm/fcm_token_dto.dart'; +import 'package:tmail_ui_user/features/push_notification/data/datasource/fcm_datasource.dart'; +import 'package:tmail_ui_user/features/push_notification/data/network/fcm_api.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class FcmDatasourceImpl extends FCMDatasource { + + final FcmApi _fcmApi; + final ExceptionThrower _exceptionThrower; + + FcmDatasourceImpl(this._fcmApi, this._exceptionThrower); + + @override + Future getFirebaseSubscriptionByDeviceId(String deviceId) { + return Future.sync(() async { + return await _fcmApi.getFirebaseSubscriptionByDeviceId(deviceId); + }).catchError((error) { + _exceptionThrower.throwException(error); + }); + } + + @override + Future deleteFCMToken(String accountId) { + throw UnimplementedError(); + } + + @override + Future deleteStateToRefresh(TypeName typeName) { + throw UnimplementedError(); + } + + @override + Future getFCMToken(String accountId) { + throw UnimplementedError(); + } + + @override + Future getStateToRefresh(TypeName typeName) { + throw UnimplementedError(); + } + + @override + Future setFCMToken(FCMTokenDto fcmToken) { + throw UnimplementedError(); + } + + @override + Future storeDeviceId(String deviceId) { + throw UnimplementedError(); + } + + @override + Future storeStateToRefresh(TypeName typeName, jmap.State newState) { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart b/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart index b42241c33..25f1c8bd6 100644 --- a/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart +++ b/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart @@ -1,3 +1,4 @@ +import 'package:fcm/model/firebase_subscription.dart'; import 'package:fcm/model/type_name.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:model/fcm/fcm_token_dto.dart'; @@ -74,4 +75,9 @@ class HiveFCMDatasourceImpl extends FCMDatasource { _exceptionThrower.throwException(error); }); } + + @override + Future getFirebaseSubscriptionByDeviceId(String deviceId) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/push_notification/data/network/fcm_api.dart b/lib/features/push_notification/data/network/fcm_api.dart new file mode 100644 index 000000000..4e046760c --- /dev/null +++ b/lib/features/push_notification/data/network/fcm_api.dart @@ -0,0 +1,40 @@ + +import 'package:fcm/method/get/firebase_subscription_get_method.dart'; +import 'package:fcm/method/get/firebase_subscription_get_response.dart'; +import 'package:fcm/model/firebase_subscription.dart'; +import 'package:jmap_dart_client/http/http_client.dart'; +import 'package:jmap_dart_client/jmap/jmap_request.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/exceptions/fcm_exception.dart'; + +class FcmApi { + + final HttpClient _httpClient; + + FcmApi(this._httpClient); + + Future getFirebaseSubscriptionByDeviceId(String deviceId) async { + final processingInvocation = ProcessingInvocation(); + final requestBuilder = JmapRequestBuilder(_httpClient, processingInvocation); + + final firebaseSubscriptionGetMethod = FirebaseSubscriptionGetMethod(); + final firebaseSubscriptionGetInvocation = requestBuilder.invocation(firebaseSubscriptionGetMethod); + final response = await (requestBuilder + ..usings(firebaseSubscriptionGetMethod.requiredCapabilities)) + .build() + .execute(); + + final getResponse = response.parse( + firebaseSubscriptionGetInvocation.methodCallId, + FirebaseSubscriptionGetResponse.deserialize); + + final matchedFirebaseSubscription = getResponse?.list + .where((fcmSub) => fcmSub.deviceClientId?.value == deviceId) + .toList(); + + if (matchedFirebaseSubscription?.isNotEmpty == true) { + return matchedFirebaseSubscription!.first; + } else { + throw NotFoundFirebaseSubscriptionException(); + } + } +} \ No newline at end of file diff --git a/lib/features/push_notification/data/repository/fcm_repository_impl.dart b/lib/features/push_notification/data/repository/fcm_repository_impl.dart index bbceccc2e..c2862998d 100644 --- a/lib/features/push_notification/data/repository/fcm_repository_impl.dart +++ b/lib/features/push_notification/data/repository/fcm_repository_impl.dart @@ -1,4 +1,5 @@ -import 'package:core/utils/app_logger.dart'; +import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:fcm/model/firebase_subscription.dart'; import 'package:fcm/model/type_name.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -12,7 +13,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; class FCMRepositoryImpl extends FCMRepository { - final FCMDatasource _fcmDatasource; + final Map _fcmDatasource; final ThreadDataSource _threadDataSource; FCMRepositoryImpl( @@ -22,18 +23,17 @@ class FCMRepositoryImpl extends FCMRepository { @override Future getFCMToken(String accountId) { - return _fcmDatasource.getFCMToken(accountId); + return _fcmDatasource[DataSourceType.local]!.getFCMToken(accountId); } @override Future setFCMToken(FCMTokenDto fcmTokenDto) { - log('FCMRepositoryImpl::setFCMToken(): $fcmTokenDto'); - return _fcmDatasource.setFCMToken(fcmTokenDto); + return _fcmDatasource[DataSourceType.local]!.setFCMToken(fcmTokenDto); } @override Future deleteFCMToken(String accountId) { - return _fcmDatasource.deleteFCMToken(accountId); + return _fcmDatasource[DataSourceType.local]!.deleteFCMToken(accountId); } @override @@ -76,21 +76,26 @@ class FCMRepositoryImpl extends FCMRepository { @override Future storeStateToRefresh(TypeName typeName, jmap.State newState) { - return _fcmDatasource.storeStateToRefresh(typeName, newState); + return _fcmDatasource[DataSourceType.local]!.storeStateToRefresh(typeName, newState); } @override Future getStateToRefresh(TypeName typeName) { - return _fcmDatasource.getStateToRefresh(typeName); + return _fcmDatasource[DataSourceType.local]!.getStateToRefresh(typeName); } @override Future deleteStateToRefresh(TypeName typeName) { - return _fcmDatasource.deleteStateToRefresh(typeName); + return _fcmDatasource[DataSourceType.local]!.deleteStateToRefresh(typeName); } @override Future storeDeviceId(String deviceId) { - return _fcmDatasource.storeDeviceId(deviceId); + return _fcmDatasource[DataSourceType.local]!.storeDeviceId(deviceId); + } + + @override + Future getFirebaseSubscriptionByDeviceId(String deviceId) { + return _fcmDatasource[DataSourceType.network]!.getFirebaseSubscriptionByDeviceId(deviceId); } } \ No newline at end of file diff --git a/lib/features/push_notification/domain/exceptions/fcm_exception.dart b/lib/features/push_notification/domain/exceptions/fcm_exception.dart index 440da59c7..e141f5ca6 100644 --- a/lib/features/push_notification/domain/exceptions/fcm_exception.dart +++ b/lib/features/push_notification/domain/exceptions/fcm_exception.dart @@ -4,4 +4,6 @@ class NotSupportFCMException implements Exception {} class NotFoundStateToRefreshException implements Exception {} -class NotFoundEmailDeliveryStateException implements Exception {} \ No newline at end of file +class NotFoundEmailDeliveryStateException implements Exception {} + +class NotFoundFirebaseSubscriptionException implements Exception {} \ No newline at end of file diff --git a/lib/features/push_notification/domain/state/get_firebase_subscription_state.dart b/lib/features/push_notification/domain/state/get_firebase_subscription_state.dart new file mode 100644 index 000000000..1bc799cc4 --- /dev/null +++ b/lib/features/push_notification/domain/state/get_firebase_subscription_state.dart @@ -0,0 +1,25 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:fcm/model/firebase_subscription.dart'; + +class GetFirebaseSubscriptionLoading extends UIState {} + +class GetFirebaseSubscriptionSuccess extends UIState { + + final FirebaseSubscription firebaseSubscription; + + GetFirebaseSubscriptionSuccess(this.firebaseSubscription); + + @override + List get props => [firebaseSubscription]; +} + +class GetFirebaseSubscriptionFailure extends FeatureFailure { + final dynamic exception; + + GetFirebaseSubscriptionFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart b/lib/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart new file mode 100644 index 000000000..b9bb029de --- /dev/null +++ b/lib/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart @@ -0,0 +1,23 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/get_firebase_subscription_state.dart'; + +class GetFirebaseSubscriptionInteractor { + + final FCMRepository _fcmRepository; + + GetFirebaseSubscriptionInteractor(this._fcmRepository); + + Stream> execute(String deviceId) async* { + try { + yield Right(GetFirebaseSubscriptionLoading()); + final firebaseSubscription = await _fcmRepository.getFirebaseSubscriptionByDeviceId(deviceId); + yield Right(GetFirebaseSubscriptionSuccess(firebaseSubscription)); + } catch (e) { + yield Left(GetFirebaseSubscriptionFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart index 37ee50fd9..5d3b4f73a 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -1,8 +1,11 @@ +import 'package:core/data/model/source_type/data_source_type.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; import 'package:tmail_ui_user/features/push_notification/data/datasource/fcm_datasource.dart'; +import 'package:tmail_ui_user/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart'; import 'package:tmail_ui_user/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart'; import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/data/network/fcm_api.dart'; import 'package:tmail_ui_user/features/push_notification/data/repository/fcm_repository_impl.dart'; import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/delete_email_state_to_refresh_interactor.dart'; @@ -10,6 +13,7 @@ import 'package:tmail_ui_user/features/push_notification/domain/usecases/delete_ import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_changes_to_push_notification_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_email_state_to_refresh_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_fcm_token_cache_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_stored_email_delivery_state_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_fcm_token_cache_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_device_id_interactor.dart'; @@ -26,7 +30,7 @@ class FcmInteractorBindings extends InteractorsBindings { @override void bindingsDataSource() { - Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); } @@ -36,6 +40,10 @@ class FcmInteractorBindings extends InteractorsBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => FcmDatasourceImpl( + Get.find(), + Get.find(), + )); Get.lazyPut(() => ThreadDataSourceImpl( Get.find(), Get.find(), @@ -55,6 +63,7 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => GetEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => DeleteEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => StoreDeviceIdInteractor(Get.find())); + Get.lazyPut(() => GetFirebaseSubscriptionInteractor(Get.find())); } @override @@ -65,7 +74,10 @@ class FcmInteractorBindings extends InteractorsBindings { @override void bindingsRepositoryImpl() { Get.lazyPut(() => FCMRepositoryImpl( - Get.find(), + { + DataSourceType.local: Get.find(), + DataSourceType.network: Get.find(), + }, Get.find() )); } diff --git a/lib/features/push_notification/presentation/controller/fcm_token_handler.dart b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart index 41d5f47aa..8fea04735 100644 --- a/lib/features/push_notification/presentation/controller/fcm_token_handler.dart +++ b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart @@ -4,9 +4,11 @@ import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart'; import 'package:fcm/model/firebase_token.dart'; -import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/get_firebase_subscription_state.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_device_id_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class FcmTokenHandler { @@ -17,22 +19,35 @@ class FcmTokenHandler { static FcmTokenHandler get instance => _instance; StoreDeviceIdInteractor? _storeDeviceIdInteractor; + GetFirebaseSubscriptionInteractor? _getFirebaseSubscriptionInteractor; + + FirebaseToken? _fcmToken; + String? _deviceId; void initialize() { try { - _storeDeviceIdInteractor = Get.find(); + _storeDeviceIdInteractor = getBinding(); + _getFirebaseSubscriptionInteractor = getBinding(); } catch (e) { logError('FcmTokenHandler::initialize(): ${e.toString()}'); } } - void handle(String token) { + void handle(String? token) { log('FcmTokenHandler::handle():token: $token'); - final fcmToken = FirebaseToken(token); - final deviceId = FcmUtils.instance.hashTokenToDeviceId(token); - log('FcmTokenHandler::handle(): fcmToken: $fcmToken'); - log('FcmTokenHandler::handle(): deviceId: $deviceId'); - _storeDeviceIdAction(deviceId); + if (token != null) { + _fcmToken = FirebaseToken(token); + _deviceId = FcmUtils.instance.hashTokenToDeviceId(token); + log('FcmTokenHandler::handle(): fcmToken: $_fcmToken'); + log('FcmTokenHandler::handle(): deviceId: $_deviceId'); + _getFcmTokenFromBackend(_deviceId!); + } + } + + void _getFcmTokenFromBackend(String deviceId) { + if (_getFirebaseSubscriptionInteractor != null) { + _consumeState(_getFirebaseSubscriptionInteractor!.execute(deviceId)); + } } void _storeDeviceIdAction(String deviceId) { @@ -57,9 +72,16 @@ class FcmTokenHandler { void _handleFailureViewState(Failure failure) { log('FcmTokenHandler::_handleFailureViewState(): $failure'); + if (failure is GetFirebaseSubscriptionFailure) { + // Register new token + } } void _handleSuccessViewState(Success success) { log('FcmTokenHandler::_handleSuccessViewState(): $success'); + if (success is GetFirebaseSubscriptionSuccess) { + final token = success.firebaseSubscription.token; + log('FcmTokenHandler::_handleSuccessViewState():token: $token'); + } } } \ No newline at end of file diff --git a/lib/features/push_notification/presentation/services/fcm_receiver.dart b/lib/features/push_notification/presentation/services/fcm_receiver.dart index 60edb7b79..c4e9be936 100644 --- a/lib/features/push_notification/presentation/services/fcm_receiver.dart +++ b/lib/features/push_notification/presentation/services/fcm_receiver.dart @@ -28,9 +28,7 @@ class FcmReceiver { void getFcmToken() async { final token = await FirebaseMessaging.instance.getToken(); log('FcmReceiver::onFcmToken():token: $token'); - if (token?.isNotEmpty == true) { - FcmService.instance.handleRefreshToken(token!); - } + FcmService.instance.handleRefreshToken(token); } void onRefreshFcmToken() { diff --git a/lib/features/push_notification/presentation/services/fcm_service.dart b/lib/features/push_notification/presentation/services/fcm_service.dart index 04f91811c..b0baa4034 100644 --- a/lib/features/push_notification/presentation/services/fcm_service.dart +++ b/lib/features/push_notification/presentation/services/fcm_service.dart @@ -15,8 +15,8 @@ class FcmService { final StreamController backgroundMessageStreamController = StreamController.broadcast(); Stream get backgroundMessageStream => backgroundMessageStreamController.stream; - final StreamController fcmTokenStreamController = StreamController.broadcast(); - Stream get fcmTokenStream => fcmTokenStreamController.stream; + final StreamController fcmTokenStreamController = StreamController.broadcast(); + Stream get fcmTokenStream => fcmTokenStreamController.stream; FcmService._internal(); @@ -35,13 +35,14 @@ class FcmService { backgroundMessageStreamController.add(newRemoteMessage); } - void handleRefreshToken(String newToken) { + void handleRefreshToken(String? newToken) { fcmTokenStreamController.add(newToken); } void _closeStream() { foregroundMessageStreamController.close(); backgroundMessageStreamController.close(); + fcmTokenStreamController.close(); } void dispose() { diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 4d65efc5b..edbad5029 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -21,6 +21,7 @@ import 'package:tmail_ui_user/features/manage_account/data/network/forwarding_ap import 'package:tmail_ui_user/features/manage_account/data/network/identity_api.dart'; import 'package:tmail_ui_user/features/manage_account/data/network/rule_filter_api.dart'; import 'package:tmail_ui_user/features/manage_account/data/network/vacation_api.dart'; +import 'package:tmail_ui_user/features/push_notification/data/network/fcm_api.dart'; import 'package:tmail_ui_user/features/quotas/data/network/quotas_api.dart'; import 'package:tmail_ui_user/features/session/data/network/session_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; @@ -88,6 +89,7 @@ class NetworkBindings extends Bindings { Get.put(MdnAPI(Get.find())); Get.put(ForwardingAPI(Get.find())); Get.put(QuotasAPI(Get.find())); + Get.put(FcmApi(Get.find())); } void _bindingConnection() { diff --git a/lib/main/routes/route_navigation.dart b/lib/main/routes/route_navigation.dart index b875d3d6b..2bfee5a65 100644 --- a/lib/main/routes/route_navigation.dart +++ b/lib/main/routes/route_navigation.dart @@ -24,4 +24,12 @@ bool canBack(BuildContext context) { BuildContext? get currentContext => Get.context; -BuildContext? get currentOverlayContext => Get.overlayContext; \ No newline at end of file +BuildContext? get currentOverlayContext => Get.overlayContext; + +T? getBinding() { + if (Get.isRegistered()) { + return Get.find(); + } else { + return null; + } +} \ No newline at end of file