diff --git a/lib/features/manage_account/domain/state/get_notification_setting_state.dart b/lib/features/manage_account/domain/state/get_notification_setting_state.dart new file mode 100644 index 000000000..af059a731 --- /dev/null +++ b/lib/features/manage_account/domain/state/get_notification_setting_state.dart @@ -0,0 +1,14 @@ +import 'package:tmail_ui_user/features/manage_account/domain/state/notification_setting_state.dart'; + +class GettingNotificationSetting extends NotificationSettingHandling {} + +class GetNotificationSettingSuccess extends NotificationSettingSuccess { + GetNotificationSettingSuccess({required this.isEnabled}); + + final bool isEnabled; + + @override + List get props => [isEnabled]; +} + +class GetNotificationSettingFailure extends NotificationSettingFailure {} \ No newline at end of file diff --git a/lib/features/manage_account/domain/state/notification_setting_state.dart b/lib/features/manage_account/domain/state/notification_setting_state.dart new file mode 100644 index 000000000..2d6d56a8d --- /dev/null +++ b/lib/features/manage_account/domain/state/notification_setting_state.dart @@ -0,0 +1,8 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class NotificationSettingHandling extends LoadingState {} + +class NotificationSettingSuccess extends UIState {} + +class NotificationSettingFailure extends FeatureFailure {} \ No newline at end of file diff --git a/lib/features/manage_account/domain/state/toggle_notification_setting_state.dart b/lib/features/manage_account/domain/state/toggle_notification_setting_state.dart new file mode 100644 index 000000000..72f69aaa6 --- /dev/null +++ b/lib/features/manage_account/domain/state/toggle_notification_setting_state.dart @@ -0,0 +1,7 @@ +import 'package:tmail_ui_user/features/manage_account/domain/state/notification_setting_state.dart'; + +class TogglingNotificationSetting extends NotificationSettingHandling {} + +class ToggleNotificationSettingSuccess extends NotificationSettingSuccess {} + +class ToggleNotificationSettingFailure extends NotificationSettingFailure {} \ No newline at end of file diff --git a/lib/features/manage_account/domain/usecases/get_notification_setting_interactor.dart b/lib/features/manage_account/domain/usecases/get_notification_setting_interactor.dart new file mode 100644 index 000000000..6b583ef35 --- /dev/null +++ b/lib/features/manage_account/domain/usecases/get_notification_setting_interactor.dart @@ -0,0 +1,22 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/notification_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_notification_setting_state.dart'; + +class GetNotificationSettingInteractor { + final NotificationRepository _notificationRepository; + + GetNotificationSettingInteractor(this._notificationRepository); + + Stream> execute(UserName userName) async* { + try { + yield Right(GettingNotificationSetting()); + final isEnabled = await _notificationRepository.getNotificationSetting(userName); + yield Right(GetNotificationSettingSuccess(isEnabled: isEnabled)); + } catch (_) { + yield Left(GetNotificationSettingFailure()); + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart b/lib/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart new file mode 100644 index 000000000..2412246e4 --- /dev/null +++ b/lib/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart @@ -0,0 +1,22 @@ +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/manage_account/domain/repository/notification_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/toggle_notification_setting_state.dart'; + +class ToggleNotificationSettingInteractor { + + final NotificationRepository _notificationRepository; + + ToggleNotificationSettingInteractor(this._notificationRepository); + + Stream> execute() async* { + try { + yield Right(TogglingNotificationSetting()); + await _notificationRepository.toggleNotificationSetting(); + yield Right(ToggleNotificationSettingSuccess()); + } catch (_) { + yield Left(ToggleNotificationSettingFailure()); + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/notification/bindings/notification_binding.dart b/lib/features/manage_account/presentation/notification/bindings/notification_binding.dart new file mode 100644 index 000000000..6c102a859 --- /dev/null +++ b/lib/features/manage_account/presentation/notification/bindings/notification_binding.dart @@ -0,0 +1,71 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource/notification_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/notification_datasource_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/data/repository/notification_repository_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/notification_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/notification/notification_controller.dart'; +import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; +import 'package:tmail_ui_user/main/permissions/notification_permission_service.dart'; + +class NotificationBinding extends BaseBindings { + @override + void bindingsController() { + Get.put(NotificationController( + Get.find(), + Get.find())); + } + + @override + void bindingsDataSource() { + Get.lazyPut( + () => Get.find()); + } + + @override + void bindingsDataSourceImpl() { + Get.lazyPut(() => NotificationDataSourceImpl( + Get.find(), + Get.find() + )); + } + + @override + void dependencies() { + Get.lazyPut(() => NotificationPermissionService()); + super.dependencies(); + } + + @override + void bindingsInteractor() { + Get.lazyPut(() => GetNotificationSettingInteractor( + Get.find())); + Get.lazyPut(() => ToggleNotificationSettingInteractor( + Get.find())); + } + + @override + void bindingsRepository() { + Get.lazyPut( + () => Get.find()); + } + + @override + void bindingsRepositoryImpl() { + Get.lazyPut(() => NotificationRepositoryImpl( + Get.find())); + } + + void close() { + Get.delete(); + Get.delete(); + Get.delete(); + Get.delete(); + Get.delete(); + Get.delete(); + Get.delete(); + Get.delete(); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/notification/notification_controller.dart b/lib/features/manage_account/presentation/notification/notification_controller.dart new file mode 100644 index 000000000..4212c7c42 --- /dev/null +++ b/lib/features/manage_account/presentation/notification/notification_controller.dart @@ -0,0 +1,59 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_notification_setting_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings/settings_controller.dart'; + +class NotificationController extends FullLifeCycleController with FullLifeCycleMixin { + final GetNotificationSettingInteractor _getNotificationSettingInteractor; + final ToggleNotificationSettingInteractor _toggleNotificationSettingInteractor; + + NotificationController( + this._getNotificationSettingInteractor, + this._toggleNotificationSettingInteractor); + + final notificationSettingEnabled = Rxn(); + final settingsController = Get.find(); + + @override + Future onInit() async { + super.onInit(); + await _getNotificationSetting(); + } + + Future _getNotificationSetting() async { + final userName = settingsController.manageAccountDashboardController.sessionCurrent?.username; + if (userName == null) { + notificationSettingEnabled.value = false; + return; + } + final getNotificationSettingState = await _getNotificationSettingInteractor + .execute(userName).last; + getNotificationSettingState.fold( + (failure) { + notificationSettingEnabled.value = false; + }, (success) { + notificationSettingEnabled.value = success is GetNotificationSettingSuccess + ? success.isEnabled + : false; + }); + } + + Future toggleNotificationSetting() async { + if (notificationSettingEnabled.value == null) return; + + await _toggleNotificationSettingInteractor.execute().last; + } + + @override + void onDetached() {} + + @override + void onInactive() {} + + @override + void onPaused() {} + + @override + Future onResumed() => _getNotificationSetting(); +} \ No newline at end of file diff --git a/test/features/manage_account/domain/usecases/get_notification_setting_interactor_test.dart b/test/features/manage_account/domain/usecases/get_notification_setting_interactor_test.dart new file mode 100644 index 000000000..409a592a5 --- /dev/null +++ b/test/features/manage_account/domain/usecases/get_notification_setting_interactor_test.dart @@ -0,0 +1,57 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/notification_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_notification_setting_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_notification_setting_interactor.dart'; + +import 'get_notification_setting_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final notificationRepository = MockNotificationRepository(); + final getNotificationSettingInteractor + = GetNotificationSettingInteractor(notificationRepository); + + final userName = UserName('value'); + + group('GetNotificationSettingInteractor test:', () { + test( + 'should emit expected state ' + 'when repo return value', + () { + // arrange + const notificationSettingEnabled = true; + when(notificationRepository.getNotificationSetting(any)) + .thenAnswer((_) async => notificationSettingEnabled); + + // assert + expect( + getNotificationSettingInteractor.execute(userName), + emitsInOrder([ + Right(GettingNotificationSetting()), + Right(GetNotificationSettingSuccess(isEnabled: notificationSettingEnabled)), + ]), + ); + }); + + test( + 'should emit expected state ' + 'when repo throws exception', + () { + // arrange + when(notificationRepository.getNotificationSetting(any)).thenThrow(Exception()); + + // assert + expect( + getNotificationSettingInteractor.execute(userName), + emitsInOrder([ + Right(GettingNotificationSetting()), + Left(GetNotificationSettingFailure()), + ]), + ); + }); + }); +} \ No newline at end of file diff --git a/test/features/manage_account/domain/usecases/toggle_notification_setting_interactor_test.dart b/test/features/manage_account/domain/usecases/toggle_notification_setting_interactor_test.dart new file mode 100644 index 000000000..b02fb40d0 --- /dev/null +++ b/test/features/manage_account/domain/usecases/toggle_notification_setting_interactor_test.dart @@ -0,0 +1,52 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/notification_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/toggle_notification_setting_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart'; + +import 'toggle_notification_setting_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final notificationRepository = MockNotificationRepository(); + final toggleNotificationSettingInteractor + = ToggleNotificationSettingInteractor(notificationRepository); + group('ToggleNotificationSettingInteractor test:', () { + test( + 'should emit expected state ' + 'when repo not throw exception', + () { + // arrange + when(notificationRepository.toggleNotificationSetting()) + .thenAnswer((_) async => {}); + + // assert + expect( + toggleNotificationSettingInteractor.execute(), + emitsInOrder([ + Right(TogglingNotificationSetting()), + Right(ToggleNotificationSettingSuccess()), + ]), + ); + }); + + test( + 'should emit expected state ' + 'when repo throws exception', + () { + // arrange + when(notificationRepository.toggleNotificationSetting()).thenThrow(Exception()); + + // assert + expect( + toggleNotificationSettingInteractor.execute(), + emitsInOrder([ + Right(TogglingNotificationSetting()), + Left(ToggleNotificationSettingFailure()), + ]), + ); + }); + }); +} \ No newline at end of file diff --git a/test/features/manage_account/presentation/notification/notification_controller_test.dart b/test/features/manage_account/presentation/notification/notification_controller_test.dart new file mode 100644 index 000000000..b1b35e573 --- /dev/null +++ b/test/features/manage_account/presentation/notification/notification_controller_test.dart @@ -0,0 +1,142 @@ +import 'package:dartz/dartz.dart' hide State; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_notification_setting_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/toggle_notification_setting_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/toggle_notification_setting_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings/settings_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/notification/notification_controller.dart'; + +import 'notification_controller_test.mocks.dart'; + +mockControllerCallback() => InternalFinalCallback(callback: () {}); +const fallbackGenerators = { + #onStart: mockControllerCallback, + #onDelete: mockControllerCallback, +}; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(fallbackGenerators: fallbackGenerators), + MockSpec(fallbackGenerators: fallbackGenerators), +]) +void main() { + final getNotificationSettingInteractor + = MockGetNotificationSettingInteractor(); + final toggleNotificationSettingInteractor + = MockToggleNotificationSettingInteractor(); + late NotificationController notificationController; + + final settingsController = MockSettingsController(); + + setUpAll(() { + Get.put(settingsController); + }); + + setUp(() { + notificationController = NotificationController( + getNotificationSettingInteractor, + toggleNotificationSettingInteractor); + }); + + group('notification controller test', () { + test( + 'should call execute on getNotificationSettingInteractor ' + 'when init', + () async { + // arrange + final userName = UserName('value'); + final manageAccountDashboardController = MockManageAccountDashBoardController(); + when(manageAccountDashboardController.sessionCurrent).thenReturn( + Session( + {}, {}, {}, + userName, + Uri(), Uri(), Uri(), Uri(), State('value')) + ); + when(settingsController.manageAccountDashboardController).thenReturn( + manageAccountDashboardController); + when(getNotificationSettingInteractor.execute(any)) + .thenAnswer((_) => Stream.value(Left(GetNotificationSettingFailure()))); + + // act + notificationController.onInit(); + + // assert + verify(getNotificationSettingInteractor.execute(userName)).called(1); + }); + + test( + 'should call execute on toggleNotificationSettingInteractor ' + 'when toggleNotificationSetting is triggered', + () { + // arrange + notificationController.notificationSettingEnabled.value = true; + when(toggleNotificationSettingInteractor.execute()) + .thenAnswer((_) => Stream.value(Left(ToggleNotificationSettingFailure()))); + + // act + notificationController.toggleNotificationSetting(); + + // assert + verify(toggleNotificationSettingInteractor.execute()).called(1); + }); + + test( + 'should change the value of notificationSettingEnabled ' + 'when GetNotificationSettingSuccess is emitted', + () async { + // arrange + const isEnabled = true; + final userName = UserName('value'); + final manageAccountDashboardController = MockManageAccountDashBoardController(); + when(manageAccountDashboardController.sessionCurrent).thenReturn( + Session( + {}, {}, {}, + userName, + Uri(), Uri(), Uri(), Uri(), State('value')) + ); + when(settingsController.manageAccountDashboardController).thenReturn( + manageAccountDashboardController); + when(getNotificationSettingInteractor.execute(any)) + .thenAnswer((_) => Stream.value(Right(GetNotificationSettingSuccess(isEnabled: isEnabled)))); + + // act + await notificationController.onInit(); + + // assert + expect(notificationController.notificationSettingEnabled.value, isEnabled); + }); + + test( + 'should change the value of notificationSettingEnabled to false ' + 'when GetNotificationSettingFailure is emitted', + () async { + // arrange + final userName = UserName('value'); + final manageAccountDashboardController = MockManageAccountDashBoardController(); + when(manageAccountDashboardController.sessionCurrent).thenReturn( + Session( + {}, {}, {}, + userName, + Uri(), Uri(), Uri(), Uri(), State('value')) + ); + when(settingsController.manageAccountDashboardController).thenReturn( + manageAccountDashboardController); + when(getNotificationSettingInteractor.execute(any)).thenAnswer((_) => Stream.value(Left(GetNotificationSettingFailure()))); + + // act + await notificationController.onInit(); + + // assert + expect(notificationController.notificationSettingEnabled.value, false); + }); + }); +} \ No newline at end of file