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 4cb64198d..b42241c33 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 @@ -65,4 +65,13 @@ class HiveFCMDatasourceImpl extends FCMDatasource { _exceptionThrower.throwException(error); }); } + + @override + Future storeDeviceId(String deviceId) { + return Future.sync(() async { + return await _firebaseCacheManager.storeDeviceId(deviceId); + }).catchError((error) { + _exceptionThrower.throwException(error); + }); + } } \ No newline at end of file diff --git a/lib/features/push_notification/data/local/fcm_cache_manager.dart b/lib/features/push_notification/data/local/fcm_cache_manager.dart index ebe2e48e5..5eb3eecc6 100644 --- a/lib/features/push_notification/data/local/fcm_cache_manager.dart +++ b/lib/features/push_notification/data/local/fcm_cache_manager.dart @@ -12,6 +12,8 @@ class FCMCacheManager { final FcmTokenCacheClient _fcmTokenCacheClient; final SharedPreferences _sharedPreferences; + static const String fcmDeviceIdKey = 'FCM_DEVICE_ID'; + FCMCacheManager(this._fcmTokenCacheClient, this._sharedPreferences); Future getFCMToken(String accountId) async { @@ -69,4 +71,8 @@ class FCMCacheManager { _sharedPreferences.remove(TypeName.emailDelivery.value) ]).then((listResult) => listResult.every((result) => result)); } + + Future storeDeviceId(String deviceId) { + return _sharedPreferences.setString(fcmDeviceIdKey, deviceId); + } } \ 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 5c29219cd..bbceccc2e 100644 --- a/lib/features/push_notification/data/repository/fcm_repository_impl.dart +++ b/lib/features/push_notification/data/repository/fcm_repository_impl.dart @@ -88,4 +88,9 @@ class FCMRepositoryImpl extends FCMRepository { Future deleteStateToRefresh(TypeName typeName) { return _fcmDatasource.deleteStateToRefresh(typeName); } + + @override + Future storeDeviceId(String deviceId) { + return _fcmDatasource.storeDeviceId(deviceId); + } } \ No newline at end of file diff --git a/lib/features/push_notification/domain/state/store_device_id_state.dart b/lib/features/push_notification/domain/state/store_device_id_state.dart new file mode 100644 index 000000000..35c4c8d7f --- /dev/null +++ b/lib/features/push_notification/domain/state/store_device_id_state.dart @@ -0,0 +1,22 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class StoreDeviceIdLoading extends UIState {} + +class StoreDeviceIdSuccess extends UIState { + + StoreDeviceIdSuccess(); + + @override + List get props => []; +} + +class StoreDeviceIdFailure extends FeatureFailure { + final dynamic exception; + + StoreDeviceIdFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/push_notification/domain/usecases/store_device_id_interactor.dart b/lib/features/push_notification/domain/usecases/store_device_id_interactor.dart new file mode 100644 index 000000000..cc53a4d99 --- /dev/null +++ b/lib/features/push_notification/domain/usecases/store_device_id_interactor.dart @@ -0,0 +1,21 @@ +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/store_device_id_state.dart'; + +class StoreDeviceIdInteractor { + final FCMRepository _fcmRepository; + + StoreDeviceIdInteractor(this._fcmRepository); + + Stream> execute(String deviceId) async* { + try { + yield Right(StoreDeviceIdLoading()); + await _fcmRepository.storeDeviceId(deviceId); + yield Right(StoreDeviceIdSuccess()); + } catch (e) { + yield Left(StoreDeviceIdFailure(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 454b2b835..37ee50fd9 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -12,6 +12,7 @@ import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_ema 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_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'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_email_delivery_state_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_email_state_to_refresh_interactor.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; @@ -53,6 +54,7 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => StoreEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => GetEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => DeleteEmailStateToRefreshInteractor(Get.find())); + Get.lazyPut(() => StoreDeviceIdInteractor(Get.find())); } @override diff --git a/lib/features/push_notification/presentation/controller/fcm_controller.dart b/lib/features/push_notification/presentation/controller/fcm_controller.dart index b41710fd3..914e39825 100644 --- a/lib/features/push_notification/presentation/controller/fcm_controller.dart +++ b/lib/features/push_notification/presentation/controller/fcm_controller.dart @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_a import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/action/fcm_action.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_token_handler.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/extensions/state_change_extension.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/listener/email_change_listener.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; @@ -54,6 +55,7 @@ class FcmController extends BaseController { void initialize({AccountId? accountId}) { _currentAccountId = accountId; + FcmTokenHandler.instance.initialize(); } void _listenFcmMessageStream() { @@ -65,6 +67,8 @@ class FcmController extends BaseController { FcmService.instance.backgroundMessageStream .throttleTime(const Duration(milliseconds: FcmService.durationMessageComing)) .listen(_handleBackgroundMessageAction); + + FcmService.instance.fcmTokenStream.listen(FcmTokenHandler.instance.handle); } void _handleForegroundMessageAction(RemoteMessage newRemoteMessage) { @@ -151,6 +155,7 @@ class FcmController extends BaseController { _dynamicUrlInterceptors = Get.find(); _authorizationInterceptors = Get.find(); _getSessionInteractor = Get.find(); + FcmTokenHandler.instance.initialize(); } catch (e) { logError('FcmController::_getBindings(): ${e.toString()}'); } diff --git a/lib/features/push_notification/presentation/controller/fcm_token_handler.dart b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart new file mode 100644 index 000000000..41d5f47aa --- /dev/null +++ b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart @@ -0,0 +1,65 @@ + +import 'package:core/presentation/state/failure.dart'; +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/usecases/store_device_id_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart'; + +class FcmTokenHandler { + + FcmTokenHandler._internal(); + + static final FcmTokenHandler _instance = FcmTokenHandler._internal(); + + static FcmTokenHandler get instance => _instance; + + StoreDeviceIdInteractor? _storeDeviceIdInteractor; + + void initialize() { + try { + _storeDeviceIdInteractor = Get.find(); + } catch (e) { + logError('FcmTokenHandler::initialize(): ${e.toString()}'); + } + } + + 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); + } + + void _storeDeviceIdAction(String deviceId) { + if (_storeDeviceIdInteractor != null) { + _consumeState(_storeDeviceIdInteractor!.execute(deviceId)); + } + } + + void _consumeState(Stream> newStateStream) { + newStateStream.listen( + _handleStateStream, + onError: (error, stackTrace) { + logError('FcmTokenHandler::consumeState():onError:error: $error'); + logError('FcmTokenHandler::consumeState():onError:stackTrace: $stackTrace'); + } + ); + } + + void _handleStateStream(Either newState) { + newState.fold(_handleFailureViewState, _handleSuccessViewState); + } + + void _handleFailureViewState(Failure failure) { + log('FcmTokenHandler::_handleFailureViewState(): $failure'); + } + + void _handleSuccessViewState(Success success) { + log('FcmTokenHandler::_handleSuccessViewState(): $success'); + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/services/fcm_service.dart b/lib/features/push_notification/presentation/services/fcm_service.dart index 487b09927..04f91811c 100644 --- a/lib/features/push_notification/presentation/services/fcm_service.dart +++ b/lib/features/push_notification/presentation/services/fcm_service.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:core/utils/app_logger.dart'; -import 'package:fcm/model/firebase_token.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_controller.dart'; @@ -19,8 +18,6 @@ class FcmService { final StreamController fcmTokenStreamController = StreamController.broadcast(); Stream get fcmTokenStream => fcmTokenStreamController.stream; - FirebaseToken? currentToken; - FcmService._internal(); static final FcmService _instance = FcmService._internal(); diff --git a/lib/features/push_notification/presentation/utils/fcm_utils.dart b/lib/features/push_notification/presentation/utils/fcm_utils.dart index ecdf47622..45cf3bc7b 100644 --- a/lib/features/push_notification/presentation/utils/fcm_utils.dart +++ b/lib/features/push_notification/presentation/utils/fcm_utils.dart @@ -14,6 +14,8 @@ class FcmUtils { static FcmUtils get instance => _instance; + static const String hashCodeKey = 'TeamMail'; + StateChange? convertFirebaseDataMessageToStateChange(Map dataMessage) { log('FcmUtils::convertFirebaseDataMessageToStateChange():dataMessage: $dataMessage'); Map mapData; @@ -73,4 +75,12 @@ class FcmUtils { bool isEmpty(dynamic object) { return object == null || (object is String && object.isEmpty); } + + String hashTokenToDeviceId(String token) { + final deviceId = '$hashCodeKey-$token'; + log('FcmUtils::hashCodeTokenToDeviceId():deviceId: $deviceId'); + final deviceIdHashed = deviceId.hashCode.toString(); + log('FcmUtils::hashCodeTokenToDeviceId():deviceIdHashCoded: $deviceIdHashed'); + return deviceIdHashed; + } } \ No newline at end of file