From b89820618c5f83fee465787aae4284a256f7f43f Mon Sep 17 00:00:00 2001 From: DatDang Date: Tue, 4 Jun 2024 10:02:19 +0700 Subject: [PATCH] TF-2877 Create notification data source & repo --- .../linagora/android/tmail/MainActivity.kt | 8 ++ .../android/tmail/NotificationGroup.kt | 48 +++++++ ios/Podfile | 2 +- ios/Podfile.lock | 8 +- .../datasource/notification_datasource.dart | 7 + .../notification_datasource_impl.dart | 42 ++++++ .../notification_repository_impl.dart | 19 +++ .../repository/notification_repository.dart | 7 + .../notification_permission_service.dart | 28 ++++ lib/main/permissions/permission_service.dart | 17 +++ pubspec.lock | 8 ++ pubspec.yaml | 2 + .../notification_datasource_impl_test.dart | 101 ++++++++++++++ .../notification_repository_impl_test.dart | 48 +++++++ .../notification_permission_service_test.dart | 123 ++++++++++++++++++ .../permissions/permission_service_test.dart | 84 ++++++++++++ 16 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/kotlin/com/linagora/android/tmail/NotificationGroup.kt create mode 100644 lib/features/manage_account/data/datasource/notification_datasource.dart create mode 100644 lib/features/manage_account/data/datasource_impl/notification_datasource_impl.dart create mode 100644 lib/features/manage_account/data/repository/notification_repository_impl.dart create mode 100644 lib/features/manage_account/domain/repository/notification_repository.dart create mode 100644 lib/main/permissions/notification_permission_service.dart create mode 100644 lib/main/permissions/permission_service.dart create mode 100644 test/features/manage_account/data/datasource_impl/notification_datasource_impl_test.dart create mode 100644 test/features/manage_account/data/repository/notification_repository_impl_test.dart create mode 100644 test/main/permissions/notification_permission_service_test.dart create mode 100644 test/main/permissions/permission_service_test.dart diff --git a/android/app/src/main/kotlin/com/linagora/android/tmail/MainActivity.kt b/android/app/src/main/kotlin/com/linagora/android/tmail/MainActivity.kt index f95b122d0..98789d5fc 100644 --- a/android/app/src/main/kotlin/com/linagora/android/tmail/MainActivity.kt +++ b/android/app/src/main/kotlin/com/linagora/android/tmail/MainActivity.kt @@ -1,11 +1,17 @@ package com.linagora.android.tmail +import android.app.NotificationManager +import android.content.Context import android.os.Bundle import android.os.PersistableBundle import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.os.Build +import android.text.TextUtils +import androidx.core.app.NotificationManagerCompat +import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterFragmentActivity() { @@ -20,5 +26,7 @@ class MainActivity: FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) + + NotificationGroup().register(flutterEngine, applicationContext) } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/linagora/android/tmail/NotificationGroup.kt b/android/app/src/main/kotlin/com/linagora/android/tmail/NotificationGroup.kt new file mode 100644 index 000000000..235bbf42e --- /dev/null +++ b/android/app/src/main/kotlin/com/linagora/android/tmail/NotificationGroup.kt @@ -0,0 +1,48 @@ +package com.linagora.android.tmail + +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import android.text.TextUtils +import androidx.core.app.NotificationManagerCompat +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel + +class NotificationGroup { + private val channel = "com.linagora.android.teammail.notification.group.permission" + + fun register(flutterEngine: FlutterEngine, applicationContext: Context) { + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler { call, result -> + try { + if (call.method == "getNotificationGroupPermission") { + val groupId = call.arguments + if (groupId is String) { + val isEnabled = getNotificationChannelGroupStatus(applicationContext, groupId) + result.success(isEnabled) + } else { + result.error("Unavailable", "No such group", "") + } + } else { + result.notImplemented() + } + } catch (exception: Throwable) { + result.error("UnknownError", exception.message, "") + } + } + } + + private fun getNotificationChannelGroupStatus(context: Context, groupId: String?): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (!TextUtils.isEmpty(groupId)) { + val manager = context.getSystemService(FlutterFragmentActivity.NOTIFICATION_SERVICE) as NotificationManager + val group = manager.getNotificationChannelGroup(groupId) + return !group.isBlocked + } + + return false + } else { + NotificationManagerCompat.from(context).areNotificationsEnabled() + } + } +} \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index a3bff4196..d1c268764 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -71,7 +71,7 @@ post_install do |installer| 'PERMISSION_LOCATION=0', ## dart: PermissionGroup.notification - 'PERMISSION_NOTIFICATIONS=0', + 'PERMISSION_NOTIFICATIONS=1', ## dart: PermissionGroup.mediaLibrary 'PERMISSION_MEDIA_LIBRARY=0', diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4a76c4da3..c3330b71a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - app_settings (5.1.1): + - Flutter - AppAuth (1.7.4): - AppAuth/Core (= 1.7.4) - AppAuth/ExternalUserAgent (= 1.7.4) @@ -185,6 +187,7 @@ PODS: - Flutter DEPENDENCIES: + - app_settings (from `.symlinks/plugins/app_settings/ios`) - better_open_file (from `.symlinks/plugins/better_open_file/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - contacts_service (from `.symlinks/plugins/contacts_service/ios`) @@ -237,6 +240,8 @@ SPEC REPOS: - SwiftyGif EXTERNAL SOURCES: + app_settings: + :path: ".symlinks/plugins/app_settings/ios" better_open_file: :path: ".symlinks/plugins/better_open_file/ios" connectivity_plus: @@ -295,6 +300,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/workmanager/ios" SPEC CHECKSUMS: + app_settings: 017320c6a680cdc94c799949d95b84cb69389ebc AppAuth: 182c5b88630569df5acb672720534756c29b3358 better_open_file: 03cf320415d4d3f46b6e00adc4a567d76c1a399d connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e @@ -343,6 +349,6 @@ SPEC CHECKSUMS: url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 -PODFILE CHECKSUM: 91d9116a6c482e0d0b230a68097981fae7e4d07a +PODFILE CHECKSUM: ded4724b53568389542fa0f7d7a64c05ffc03971 COCOAPODS: 1.14.3 diff --git a/lib/features/manage_account/data/datasource/notification_datasource.dart b/lib/features/manage_account/data/datasource/notification_datasource.dart new file mode 100644 index 000000000..d2ff2f115 --- /dev/null +++ b/lib/features/manage_account/data/datasource/notification_datasource.dart @@ -0,0 +1,7 @@ +import 'package:jmap_dart_client/jmap/core/user_name.dart'; + +abstract class NotificationDataSource { + Future getNotificationSetting(UserName userName); + + Future toggleNotificationSetting(); +} \ No newline at end of file diff --git a/lib/features/manage_account/data/datasource_impl/notification_datasource_impl.dart b/lib/features/manage_account/data/datasource_impl/notification_datasource_impl.dart new file mode 100644 index 000000000..55bac9f16 --- /dev/null +++ b/lib/features/manage_account/data/datasource_impl/notification_datasource_impl.dart @@ -0,0 +1,42 @@ +import 'package:app_settings/app_settings.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource/notification_datasource.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; +import 'package:tmail_ui_user/main/permissions/notification_permission_service.dart'; + +class NotificationDataSourceImpl implements NotificationDataSource { + final NotificationPermissionService _permissionService; + final ExceptionThrower _exceptionThrower; + + NotificationDataSourceImpl( + this._permissionService, + this._exceptionThrower); + + @override + Future getNotificationSetting(UserName userName) { + return Future.sync(() async { + if (PlatformInfo.isIOS) { + return await _checkIosNotificationPermissionEnabled(); + } else { + return await _checkAndroidNotificationPermissionEnabled(userName); + } + }).catchError(_exceptionThrower.throwException); + } + + @override + Future toggleNotificationSetting() async { + return Future.sync(() async { + return await AppSettings.openAppSettings(type: AppSettingsType.notification); + }).catchError(_exceptionThrower.throwException); + } + + Future _checkIosNotificationPermissionEnabled() { + return _permissionService.isGranted(Permission.notification); + } + + Future _checkAndroidNotificationPermissionEnabled(UserName userName) { + return _permissionService.checkNotificationPermissionEnabled(userName.value); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/data/repository/notification_repository_impl.dart b/lib/features/manage_account/data/repository/notification_repository_impl.dart new file mode 100644 index 000000000..70b45c7cd --- /dev/null +++ b/lib/features/manage_account/data/repository/notification_repository_impl.dart @@ -0,0 +1,19 @@ +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource/notification_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/notification_repository.dart'; + +class NotificationRepositoryImpl implements NotificationRepository { + final NotificationDataSource _notificationDataSource; + + NotificationRepositoryImpl(this._notificationDataSource); + + @override + Future getNotificationSetting(UserName userName) { + return _notificationDataSource.getNotificationSetting(userName); + } + + @override + Future toggleNotificationSetting() { + return _notificationDataSource.toggleNotificationSetting(); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/repository/notification_repository.dart b/lib/features/manage_account/domain/repository/notification_repository.dart new file mode 100644 index 000000000..c1672f75f --- /dev/null +++ b/lib/features/manage_account/domain/repository/notification_repository.dart @@ -0,0 +1,7 @@ +import 'package:jmap_dart_client/jmap/core/user_name.dart'; + +abstract class NotificationRepository { + Future getNotificationSetting(UserName userName); + + Future toggleNotificationSetting(); +} \ No newline at end of file diff --git a/lib/main/permissions/notification_permission_service.dart b/lib/main/permissions/notification_permission_service.dart new file mode 100644 index 000000000..57db2e502 --- /dev/null +++ b/lib/main/permissions/notification_permission_service.dart @@ -0,0 +1,28 @@ +import 'package:permission_handler/permission_handler.dart'; +import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; +import 'package:tmail_ui_user/main/permissions/permission_service.dart'; + +class NotificationPermissionService extends PermissionService { + + Future checkGroupPermissionByGroupId(String groupId) async { + if (groupId.isEmpty) { + return false; + } + + const methodChannel = MethodChannel('com.linagora.android.teammail.notification.group.permission'); + const methodName = 'getNotificationGroupPermission'; + final invokeResult = await methodChannel.invokeMethod( + methodName, + groupId); + return invokeResult ?? false; + } + + Future checkNotificationPermissionEnabled(String groupId) async { + final permissionGranted = await isGranted(Permission.notification); + if (!permissionGranted) { + return false; + } + + return await checkGroupPermissionByGroupId(groupId); + } +} \ No newline at end of file diff --git a/lib/main/permissions/permission_service.dart b/lib/main/permissions/permission_service.dart new file mode 100644 index 000000000..833faa5b0 --- /dev/null +++ b/lib/main/permissions/permission_service.dart @@ -0,0 +1,17 @@ +import 'package:permission_handler/permission_handler.dart'; + +class PermissionService { + Future isGranted(Permission permission) async { + final current = await permission.status; + switch (current) { + case PermissionStatus.granted: + return true; + case PermissionStatus.denied: + final requestResult = await permission.request(); + final permissionGranted = requestResult == PermissionStatus.granted; + return permissionGranted; + default: + return false; + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 4bf57cea4..c2272824e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + app_settings: + dependency: "direct main" + description: + name: app_settings + sha256: "09bc7fe0313a507087bec1a3baf555f0576e816a760cbb31813a88890a09d9e5" + url: "https://pub.dev" + source: hosted + version: "5.1.1" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dd8335bff..b3628eb82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -244,6 +244,8 @@ dependencies: file_picker: 5.3.1 + app_settings: 5.1.1 + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/features/manage_account/data/datasource_impl/notification_datasource_impl_test.dart b/test/features/manage_account/data/datasource_impl/notification_datasource_impl_test.dart new file mode 100644 index 000000000..fe48f68e5 --- /dev/null +++ b/test/features/manage_account/data/datasource_impl/notification_datasource_impl_test.dart @@ -0,0 +1,101 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.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/data/datasource_impl/notification_datasource_impl.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; +import 'package:tmail_ui_user/main/permissions/notification_permission_service.dart'; + +import 'notification_datasource_impl_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec()]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final permissionService = MockNotificationPermissionService(); + final exceptionThrower = MockExceptionThrower(); + final notificationDatasourceImpl = NotificationDataSourceImpl( + permissionService, + exceptionThrower); + + final userName = UserName('value'); + + void setMockNotificationPermission({ + VoidCallback? openAppSettingsCallback, + }) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('com.spencerccf.app_settings/methods'), + (message) async { + if (message.method == 'openSettings') { + openAppSettingsCallback?.call(); + return true; + } + + return null; + }); + } + + group('notification datasource test:', () { + group('getAppNotificationSetting test:', () { + test( + 'should return true ' + 'when user grants notification permission', + () async { + // arrange + debugDefaultTargetPlatformOverride = TargetPlatform.android; + when(permissionService.checkNotificationPermissionEnabled(any)).thenAnswer((_) async => true); + + // act + final result = await notificationDatasourceImpl.getNotificationSetting(userName); + + // assert + verify(permissionService.checkNotificationPermissionEnabled(userName.value)); + expect(result, true); + debugDefaultTargetPlatformOverride = null; + }); + + test( + 'should return false ' + 'when user denies notification permission', + () async { + // arrange + debugDefaultTargetPlatformOverride = TargetPlatform.android; + when(permissionService.checkNotificationPermissionEnabled(any)).thenAnswer((_) async => false); + + // act + final result = await notificationDatasourceImpl.getNotificationSetting(userName); + + // assert + verify(permissionService.checkNotificationPermissionEnabled(userName.value)); + expect(result, false); + debugDefaultTargetPlatformOverride = null; + }); + }); + + group('toggleAppNotificationSetting test:', () { + test( + 'should call openAppSettings ' + 'when user toggles notification setting on', + () async { + // arrange + debugDefaultTargetPlatformOverride = TargetPlatform.android; + bool openAppSettingsCalled = false; + setMockNotificationPermission(openAppSettingsCallback: () { + openAppSettingsCalled = true; + }); + + // act + await notificationDatasourceImpl.toggleNotificationSetting(); + + // assert + expect(openAppSettingsCalled, true); + debugDefaultTargetPlatformOverride = null; + }); + }); + }); +} \ No newline at end of file diff --git a/test/features/manage_account/data/repository/notification_repository_impl_test.dart b/test/features/manage_account/data/repository/notification_repository_impl_test.dart new file mode 100644 index 000000000..18f8b4d10 --- /dev/null +++ b/test/features/manage_account/data/repository/notification_repository_impl_test.dart @@ -0,0 +1,48 @@ +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/data/datasource/notification_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/data/repository/notification_repository_impl.dart'; + +import 'notification_repository_impl_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final notificationDataSource = MockNotificationDataSource(); + final notificationRepositoryImpl = NotificationRepositoryImpl(notificationDataSource); + + final userName = UserName('value'); + + group('notification repository impl test:', () { + test( + 'should call getNotificationSetting ' + 'when getNotificationSetting in datasource is called', + () { + // arrange + when(notificationDataSource.getNotificationSetting(any)) + .thenAnswer((_) async => true); + + // act + notificationRepositoryImpl.getNotificationSetting(userName); + + // assert + verify(notificationDataSource.getNotificationSetting(userName)).called(1); + }); + + test( + 'should call toggleNotificationSetting ' + 'when toggleNotificationSetting in datasource is called', + () { + // arrange + when(notificationDataSource.toggleNotificationSetting()) + .thenAnswer((_) async => true); + + // act + notificationRepositoryImpl.toggleNotificationSetting(); + + // assert + verify(notificationDataSource.toggleNotificationSetting()).called(1); + }); + }); +} \ No newline at end of file diff --git a/test/main/permissions/notification_permission_service_test.dart b/test/main/permissions/notification_permission_service_test.dart new file mode 100644 index 000000000..16996fd87 --- /dev/null +++ b/test/main/permissions/notification_permission_service_test.dart @@ -0,0 +1,123 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; +import 'package:tmail_ui_user/main/permissions/notification_permission_service.dart'; + +class TestNotificationPermissionService extends NotificationPermissionService { + bool callCheckGroupPermission = false; + + @override + Future checkGroupPermissionByGroupId(String groupId) { + callCheckGroupPermission = true; + return super.checkGroupPermissionByGroupId(groupId); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final notificationPermissionService = TestNotificationPermissionService(); + + void setMockNotificationPermission({ + required PermissionStatus currentStatus, + PermissionStatus? requestedStatus, + }) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('flutter.baseflow.com/permissions/methods'), + (message) async { + if (message.method == 'checkPermissionStatus') { + return currentStatus.index; + } else if (message.method == 'requestPermissions') { + assert( + requestedStatus != null, + 'mock requested status must not be null ' + 'when requesting permission'); + + return {Permission.notification.value: requestedStatus!.index}; + } + + return null; + }); + } + + void setMockNotificationGroupStatus({ + required bool groupIsBlocked, + }) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('com.linagora.android.teammail.notification.group.permission'), + (message) async { + if (message.method == 'getNotificationGroupPermission') { + return groupIsBlocked; + } + + return null; + }); + } + + group('notification permission service test', () { + test( + 'should return false ' + 'when checkGroupPermission is called ' + 'and group id is empty', + () async { + // arrange + setMockNotificationGroupStatus(groupIsBlocked: true); + + // act + final result = await notificationPermissionService.checkGroupPermissionByGroupId(''); + + // assert + expect(notificationPermissionService.callCheckGroupPermission, true); + expect(result, false); + }); + + test( + 'should return the status of notification group ' + 'when checkGroupPermission is called ' + 'and group id is not empty', + () async { + // arrange + setMockNotificationGroupStatus(groupIsBlocked: true); + + // act + final result = await notificationPermissionService.checkGroupPermissionByGroupId('test'); + + // assert + expect(notificationPermissionService.callCheckGroupPermission, true); + expect(result, true); + }); + + test( + 'should return false ' + 'when checkNotificationPermissionEnabled is called ' + 'and permission is not granted', + () async { + // arrange + setMockNotificationPermission(currentStatus: PermissionStatus.permanentlyDenied); + + // act + final result = await notificationPermissionService.checkNotificationPermissionEnabled(''); + + // assert + expect(result, false); + }); + + test( + 'should return the status of notification group ' + 'when checkNotificationPermissionEnabled is called ' + 'and permission is not granted', + () async { + // arrange + setMockNotificationPermission(currentStatus: PermissionStatus.granted); + setMockNotificationGroupStatus(groupIsBlocked: true); + + // act + final result = await notificationPermissionService.checkGroupPermissionByGroupId('test'); + + // assert + expect(result, true); + }); + }); +} \ No newline at end of file diff --git a/test/main/permissions/permission_service_test.dart b/test/main/permissions/permission_service_test.dart new file mode 100644 index 000000000..2e2c85546 --- /dev/null +++ b/test/main/permissions/permission_service_test.dart @@ -0,0 +1,84 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:tmail_ui_user/main/permissions/permission_service.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final permissionService = PermissionService(); + + void setMockNotificationPermission({ + required PermissionStatus currentStatus, + PermissionStatus? requestedStatus, + }) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('flutter.baseflow.com/permissions/methods'), + (message) async { + if (message.method == 'checkPermissionStatus') { + return currentStatus.index; + } else if (message.method == 'requestPermissions') { + assert( + requestedStatus != null, + 'mock requested status must not be null ' + 'when requesting permission'); + + return {Permission.notification.value: requestedStatus!.index}; + } + + return null; + }); + } + + group('permission service test:', () { + group('isGranted test:', () { + test( + 'should return true ' + 'when permission is granted before', + () async { + // arrange + setMockNotificationPermission(currentStatus: PermissionStatus.granted); + + // act + final result = await permissionService.isGranted(Permission.notification); + + // assert + expect(result, true); + }); + + test( + 'should return true ' + 'when permission is not granted before ' + 'and granted when request', + () async { + // arrange + setMockNotificationPermission( + currentStatus: PermissionStatus.denied, + requestedStatus: PermissionStatus.granted); + + // act + final result = await permissionService.isGranted(Permission.notification); + + // assert + expect(result, true); + }); + + test( + 'should return false ' + 'when permission is not granted before ' + 'and denied when request', + () async { + // arrange + setMockNotificationPermission( + currentStatus: PermissionStatus.denied, + requestedStatus: PermissionStatus.denied); + + // act + final result = await permissionService.isGranted(Permission.notification); + + // assert + expect(result, false); + }); + }); + }); +} \ No newline at end of file