diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 285b1c52..7d476fb6 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -30,7 +30,7 @@ android:name=".ForegroundService" android:exported="false" /> - @@ -40,6 +40,13 @@ + + + + + result) { + AlarmUtils.openAlarmPermissionSettings(activity); + result.onComplete(null, null); + } + @KeepForSdk public void createChannel(Bundle channelMap, MethodCallResult result) { ChannelModel channelModel = ChannelModel.fromBundle(channelMap); @@ -392,6 +399,17 @@ public void getNotificationSettings(MethodCallResult result) { } else { notificationSettingsBundle.putInt("authorizationStatus", 0); } + + boolean canScheduleExactAlarms = AlarmUtils.canScheduleExactAlarms(); + Bundle androidSettingsBundle = new Bundle(); + + if (canScheduleExactAlarms) { + androidSettingsBundle.putInt("alarm", 1); + } else { + androidSettingsBundle.putInt("alarm", 0); + } + + notificationSettingsBundle.putBundle("android", androidSettingsBundle); result.onComplete(null, notificationSettingsBundle); } diff --git a/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java b/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java index 635cdcb5..6227c7c7 100644 --- a/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +++ b/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java @@ -30,6 +30,7 @@ import app.notifee.core.database.WorkDataRepository; import app.notifee.core.model.NotificationModel; import app.notifee.core.model.TimestampTriggerModel; +import app.notifee.core.utility.AlarmUtils; import app.notifee.core.utility.ObjectUtils; import com.google.android.gms.tasks.Continuation; import com.google.android.gms.tasks.Task; @@ -118,10 +119,6 @@ static void displayScheduledNotification(Bundle alarmManagerNotification) { }); } - private static AlarmManager getAlarmManager() { - return (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); - } - public static PendingIntent getAlarmManagerIntentForNotification(String notificationId) { try { Context context = getApplicationContext(); @@ -145,13 +142,14 @@ static void scheduleTimestampTriggerNotification( PendingIntent pendingIntent = getAlarmManagerIntentForNotification(notificationModel.getId()); - AlarmManager alarmManager = getAlarmManager(); + AlarmManager alarmManager = AlarmUtils.getAlarmManager(); // Verify we can call setExact APIs to avoid a crash, but it requires an Android S+ symbol if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (!alarmManager.canScheduleExactAlarms()) { System.err.println( - "Missing SCHEDULE_EXACT_ALARM permission. Trigger not scheduled. Issue #239"); + "Missing SCHEDULE_EXACT_ALARM permission. Trigger not scheduled. See:" + + " https://notifee.app/react-native/docs/triggers#android-12-limitations"); return; } } @@ -172,7 +170,7 @@ Task> getScheduledNotifications() { public static void cancelNotification(String notificationId) { PendingIntent pendingIntent = getAlarmManagerIntentForNotification(notificationId); - AlarmManager alarmManager = getAlarmManager(); + AlarmManager alarmManager = AlarmUtils.getAlarmManager(); if (pendingIntent != null) { alarmManager.cancel(pendingIntent); } diff --git a/android/src/main/java/app/notifee/core/utility/AlarmUtils.java b/android/src/main/java/app/notifee/core/utility/AlarmUtils.java new file mode 100644 index 00000000..dadd2ef4 --- /dev/null +++ b/android/src/main/java/app/notifee/core/utility/AlarmUtils.java @@ -0,0 +1,52 @@ +package app.notifee.core.utility; + +import static app.notifee.core.ContextHolder.getApplicationContext; + +import android.app.Activity; +import android.app.AlarmManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import app.notifee.core.ContextHolder; +import app.notifee.core.Logger; + +public class AlarmUtils { + private static final String TAG = "AlarmUtils"; + + public static AlarmManager getAlarmManager() { + return (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); + } + + /** + * Attempts to open the device's alarm special access settings + * + * @param activity + */ + public static void openAlarmPermissionSettings(Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + return; + } + + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); + Context context = ContextHolder.getApplicationContext(); + String packageName = context.getPackageName(); + intent.setData(Uri.parse("package:" + packageName)); + + IntentUtils.startActivityOnUiThread(activity, intent); + } catch (Exception e) { + Logger.e(TAG, "An error occurred whilst trying to open alarm permission settings", e); + } + } + + public static boolean canScheduleExactAlarms() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + return getAlarmManager().canScheduleExactAlarms(); + } + return true; + } +} diff --git a/docs/navigator.json b/docs/navigator.json index 9b4a54d9..a5056b90 100644 --- a/docs/navigator.json +++ b/docs/navigator.json @@ -46,6 +46,7 @@ ["/react-native/docs/android/foreground-service", "Foreground Service"], ["/react-native/docs/android/grouping-and-sorting", "Grouping & Sorting"], ["/react-native/docs/android/interaction", "Interaction"], + ["/react-native/docs/android/permissions", "Permissions"], ["/react-native/docs/android/progress-indicators", "Progress Indicators"], ["/react-native/docs/android/styles", "Styles"], ["/react-native/docs/android/timers", "Timers"], diff --git a/docs/react-native/docs/triggers.md b/docs/react-native/docs/triggers.md index 45d9372a..4a7d67af 100644 --- a/docs/react-native/docs/triggers.md +++ b/docs/react-native/docs/triggers.md @@ -69,15 +69,15 @@ Let's update our trigger we created previously to occur weekly. import notifee, { TimestampTrigger, TriggerType } from '@notifee/react-native'; async function onCreateTriggerNotification() { - const date = new Date(Date.now()); - date.setHours(11); - date.setMinutes(10); + const date = new Date(Date.now()); + date.setHours(11); + date.setMinutes(10); - const trigger: TimestampTrigger = { - type: TriggerType.TIMESTAMP, - timestamp: date.getTime(), - repeatFrequency: RepeatFrequency.WEEKLY - }; + const trigger: TimestampTrigger = { + type: TriggerType.TIMESTAMP, + timestamp: date.getTime(), + repeatFrequency: RepeatFrequency.WEEKLY, + }; await notifee.createTriggerNotification( { @@ -132,7 +132,7 @@ On Android, you have the option to create your trigger notification with Android ```js const trigger: TimestampTrigger = { //... - alarmManager: true + alarmManager: true, }; ``` @@ -143,16 +143,34 @@ const trigger: TimestampTrigger = { //... alarmManager: { allowWhileIdle: true, - } + }, }; ``` Please note, for iOS, a repeating trigger does not work the same as Android - the initial trigger cannot be delayed: + - `HOURLY`: the starting date and hour will be ignored, and only the minutes and seconds will be taken into the account. If the timestamp is set to trigger in 3 hours and repeat every 5th minute of the hour, the alert will not fire in 3 hours, but will instead fire immediately on the next 5th minute of the hour. - `DAILY`: the starting day will be ignored, and only the time will be taken into account. If it is January 1 at 10 AM and you schedule a daily recurring notification for January 2 at 11 AM, it will fire on January 1 at 11 AM and every day thereafter. - `WEEKLY`: the starting week will be ignored, and only the day and time will be taken into account. -> For more details, please see the discussion [here](https://github.com/notifee/react-native-notifee/issues/241). +> For more details, please see the discussion [here](https://github.com/notifee/react-native-notifee/issues/241). + +### Android 12 Limitations + +Starting from Android 12, timestamp triggers cannot be created unless user specfically allow the [exact alarm permission](https://developer.android.com/reference/android/Manifest.permission#SCHEDULE_EXACT_ALARM). Before you create a timestamp trigger, check whether `SCHEDULE_EXACT_ALARM` permission is allowed by making a call to `getNotificationSettings`. If ` alarm` is `DISABLED`, you should educate the user on this permission and ask to enable scheduling alarms. You can then use `openAlarmPermissionSettings` function to display the Alarms & Reminder settings of your app. + +```js +const settings = Notifee.getNotificationSettings(); +if (settings.android.alarm == AndroidNotificationSetting.ENABLED) { + //Create timestamp trigger +} else { + // Show some user information to educate them on what exact alarm permission is, + // and why it is necessary for your app functionality, then send them to system preferences: + await Notifee.openAlarmPermissionSettings(); +} +``` + +Please note that if the user revokes the permission via system preferences, all of the timestamp triggers will be deleted by the system. However, if you check for the permission, notice it is missing, educate the user and they grant permission again, notifee will automatically reschedule the triggers when the user allows the alarm permission again with no need for additional code. ## Interval Trigger diff --git a/packages/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java b/packages/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java index e8d8473b..8096f1b5 100644 --- a/packages/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java +++ b/packages/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java @@ -156,6 +156,13 @@ public void displayNotification(ReadableMap notificationMap, Promise promise) { (e, aVoid) -> NotifeeReactUtils.promiseResolver(promise, e)); } + @ReactMethod + public void openAlarmPermissionSettings(Promise promise) { + Notifee.getInstance() + .openAlarmPermissionSettings( + getCurrentActivity(), (e, avoid) -> NotifeeReactUtils.promiseResolver(promise, e)); + } + @ReactMethod public void createTriggerNotification( ReadableMap notificationMap, ReadableMap triggerMap, Promise promise) { diff --git a/packages/react-native/src/NotifeeApiModule.ts b/packages/react-native/src/NotifeeApiModule.ts index 3c26747f..c539f816 100644 --- a/packages/react-native/src/NotifeeApiModule.ts +++ b/packages/react-native/src/NotifeeApiModule.ts @@ -7,6 +7,7 @@ import { Module } from './types/Module'; import { AndroidChannel, AndroidChannelGroup, + AndroidNotificationSetting, NativeAndroidChannel, NativeAndroidChannelGroup, } from './types/NotificationAndroid'; @@ -350,6 +351,13 @@ export default class NotifeeApiModule extends NotifeeNativeModule implements Mod }); }; + public openAlarmPermissionSettings = (): Promise => { + if (isIOS) { + return Promise.resolve(); + } + return this.native.openAlarmPermissionSettings(); + }; + public createTriggerNotification = ( notification: Notification, trigger: Trigger, @@ -461,24 +469,30 @@ export default class NotifeeApiModule extends NotifeeNativeModule implements Mod if (isAndroid) { return this.native .getNotificationSettings() - .then(({ authorizationStatus }: Pick) => { - return { + .then( + ({ authorizationStatus, - ios: { - alert: 1, - badge: 1, - criticalAlert: 1, - showPreviews: 1, - sound: 1, - carPlay: 1, - lockScreen: 1, - announcement: 1, - notificationCenter: 1, - inAppNotificationSettings: 1, + android, + }: Pick) => { + return { authorizationStatus, - }, - }; - }); + android, + ios: { + alert: 1, + badge: 1, + criticalAlert: 1, + showPreviews: 1, + sound: 1, + carPlay: 1, + lockScreen: 1, + announcement: 1, + notificationCenter: 1, + inAppNotificationSettings: 1, + authorizationStatus, + }, + }; + }, + ); } let options: IOSNotificationPermissions; @@ -488,7 +502,22 @@ export default class NotifeeApiModule extends NotifeeNativeModule implements Mod throw new Error(`notifee.requestPermission(*) ${e.message}`); } - return this.native.requestPermission(options); + return this.native + .requestPermission(options) + .then( + ({ + authorizationStatus, + ios, + }: Pick) => { + return { + authorizationStatus, + ios, + android: { + alarm: AndroidNotificationSetting.ENABLED, + }, + }; + }, + ); }; public registerForegroundService(runner: (notification: Notification) => Promise): void { @@ -540,27 +569,48 @@ export default class NotifeeApiModule extends NotifeeNativeModule implements Mod if (isAndroid) { return this.native .getNotificationSettings() - .then(({ authorizationStatus }: Pick) => { - return { + .then( + ({ authorizationStatus, - ios: { - alert: 1, - badge: 1, - criticalAlert: 1, - showPreviews: 1, - sound: 1, - carPlay: 1, - lockScreen: 1, - announcement: 1, - notificationCenter: 1, - inAppNotificationSettings: 1, + android, + }: Pick) => { + return { authorizationStatus, - }, - }; - }); + android, + ios: { + alert: 1, + badge: 1, + criticalAlert: 1, + showPreviews: 1, + sound: 1, + carPlay: 1, + lockScreen: 1, + announcement: 1, + notificationCenter: 1, + inAppNotificationSettings: 1, + authorizationStatus, + }, + }; + }, + ); } - return this.native.getNotificationSettings(); + return this.native + .getNotificationSettings() + .then( + ({ + authorizationStatus, + ios, + }: Pick) => { + return { + authorizationStatus, + ios, + android: { + alarm: AndroidNotificationSetting.ENABLED, + }, + }; + }, + ); }; public getBadgeCount = (): Promise => { diff --git a/packages/react-native/src/__mocks__/NotifeeNativeModule.ts b/packages/react-native/src/__mocks__/NotifeeNativeModule.ts index b2d78ae3..2fd6d03e 100644 --- a/packages/react-native/src/__mocks__/NotifeeNativeModule.ts +++ b/packages/react-native/src/__mocks__/NotifeeNativeModule.ts @@ -25,6 +25,7 @@ export const mockNotifeeNativeModule = { deleteChannel: jest.fn(), deleteChannelGroup: jest.fn(), displayNotification: jest.fn(), + openAlarmPermissionSettings: jest.fn(), createTriggerNotification: jest.fn(), getChannel: jest.fn(), getChannels: jest.fn(), diff --git a/packages/react-native/src/types/Module.ts b/packages/react-native/src/types/Module.ts index c22d8ddc..acfe709e 100644 --- a/packages/react-native/src/types/Module.ts +++ b/packages/react-native/src/types/Module.ts @@ -184,6 +184,18 @@ export interface Module { */ displayNotification(notification: Notification): Promise; + /** + * API used to open the Android Alarm special access settings for the application. + * + * On Android >= 12 / API >= 31, the alarm special access settings screen is displayed, otherwise, + * this is a no-op & instantly resolves. + * + * View the [Trigger](/react-native/docs/android/triggers#android-12-limitations) documentation for more information. + * + * @platform android + */ + openAlarmPermissionSettings(): Promise; + /** * API used to create a trigger notification. * diff --git a/packages/react-native/src/types/Notification.ts b/packages/react-native/src/types/Notification.ts index 59904793..3f374dfa 100644 --- a/packages/react-native/src/types/Notification.ts +++ b/packages/react-native/src/types/Notification.ts @@ -9,7 +9,7 @@ import { NotificationAndroid, AndroidLaunchActivityFlag, } from './NotificationAndroid'; -import { Trigger } from '..'; +import { AndroidNotificationSettings, Trigger } from '..'; /** * Interface for building a local notification for both Android & iOS devices. @@ -486,4 +486,9 @@ export interface NotificationSettings { * On Android, this will be populated with default values */ ios: IOSNotificationSettings; + /** + * Overall notification settings for the application in android. + * On iOS, this will be populated with default values + */ + android: AndroidNotificationSettings; } diff --git a/packages/react-native/src/types/NotificationAndroid.ts b/packages/react-native/src/types/NotificationAndroid.ts index a58fa229..0fb6cbdd 100644 --- a/packages/react-native/src/types/NotificationAndroid.ts +++ b/packages/react-native/src/types/NotificationAndroid.ts @@ -416,6 +416,46 @@ export interface NotificationAndroid { sound?: string; } +/** + * An interface representing the current android only notification-related settings for your app. + * + * This interface is returned from [`requestPermission`](/react-native/reference/requestpermission) + * and [`getNotificationSettings`](/react-native/reference/getnotificationsettings). + * + * View the [Permissions](/react-native/docs/android/permissions) documentation to learn more. + * + * @platform android + */ + +export enum AndroidNotificationSetting { + /** + * This setting is not supported on this device. Usually this means that the Android version required + * for this setting has not been met. + */ + NOT_SUPPORTED = -1, + + /** + * This setting is currently disabled by the user. + */ + DISABLED = 0, + + /** + * This setting is currently enabled. + */ + ENABLED = 1, +} + +export interface AndroidNotificationSettings { + /** + * Enum describing if you can create triggers + * + * For Android < 12 / API < 31, this will default to true + * + * View the [Trigger](/react-native/docs/android/triggers#android-12-limitations) documentation for more information. + */ + alarm: AndroidNotificationSetting; +} + /** * The interface used to describe a notification quick action for Android. * diff --git a/tests_react_native/__tests__/NotifeeApiModule.test.ts b/tests_react_native/__tests__/NotifeeApiModule.test.ts index b38200b2..3f07755e 100644 --- a/tests_react_native/__tests__/NotifeeApiModule.test.ts +++ b/tests_react_native/__tests__/NotifeeApiModule.test.ts @@ -6,7 +6,10 @@ import { /* @ts-ignore */ mockNotifeeNativeModule, } from '@notifee/react-native/src/NotifeeNativeModule'; -import { AndroidChannel } from '@notifee/react-native/src/types/NotificationAndroid'; +import { + AndroidChannel, + AndroidNotificationSetting, +} from '@notifee/react-native/src/types/NotificationAndroid'; import { setPlatform } from './testSetup'; import { TriggerNotification, TriggerType } from '@notifee/react-native/src'; @@ -240,13 +243,19 @@ describe('Notifee Api Module', () => { setPlatform('android'); }); - test('return authorized with IOSNotificationSettings set to default values', async () => { + test('return android settings with IOSNotificationSettings set to default values', async () => { mockNotifeeNativeModule.getNotificationSettings.mockResolvedValue({ authorizationStatus: AuthorizationStatus.AUTHORIZED, + android: { + alarm: AndroidNotificationSetting.DISABLED, + }, }); const settings = await apiModule.getNotificationSettings(); expect(settings).toEqual({ authorizationStatus: AuthorizationStatus.AUTHORIZED, + android: { + alarm: 0, + }, ios: { alert: 1, badge: 1, @@ -262,14 +271,36 @@ describe('Notifee Api Module', () => { }, }); }); + }); + + describe('on iOS', () => { + beforeEach(() => { + setPlatform('iOS'); + }); - test('return denied with IOSNotificationSettings set to default values', async () => { + test('return iOS settings with AndroidNotificationSettings set to default values', async () => { mockNotifeeNativeModule.getNotificationSettings.mockResolvedValue({ - authorizationStatus: AuthorizationStatus.DENIED, + authorizationStatus: AuthorizationStatus.AUTHORIZED, + ios: { + alert: 1, + badge: 1, + criticalAlert: 1, + showPreviews: 1, + sound: 1, + carPlay: 1, + lockScreen: 1, + announcement: 1, + notificationCenter: 1, + inAppNotificationSettings: 1, + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }, }); const settings = await apiModule.getNotificationSettings(); expect(settings).toEqual({ - authorizationStatus: AuthorizationStatus.DENIED, + authorizationStatus: AuthorizationStatus.AUTHORIZED, + android: { + alarm: AndroidNotificationSetting.ENABLED, + }, ios: { alert: 1, badge: 1, @@ -281,7 +312,7 @@ describe('Notifee Api Module', () => { announcement: 1, notificationCenter: 1, inAppNotificationSettings: 1, - authorizationStatus: AuthorizationStatus.DENIED, + authorizationStatus: AuthorizationStatus.AUTHORIZED, }, }); }); diff --git a/tests_react_native/android/app/src/androidTest/java/com/invertase/testing/GetNotificationSettingsTest.java b/tests_react_native/android/app/src/androidTest/java/com/invertase/testing/GetNotificationSettingsTest.java deleted file mode 100644 index 221d0141..00000000 --- a/tests_react_native/android/app/src/androidTest/java/com/invertase/testing/GetNotificationSettingsTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.notifee.testing; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import androidx.test.InstrumentationRegistry; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.UiObjectNotFoundException; -import androidx.test.uiautomator.UiSelector; -import androidx.test.uiautomator.Until; -import org.junit.Assert; -import org.junit.Test; - -public class GetNotificationSettingsTest { - - private final long TIMEOUT = 5000; - - @Test - public void testNotificationSettingsCorrectResponse() - throws UiObjectNotFoundException, InterruptedException { - final UiDevice device = UiDevice.getInstance(getInstrumentation()); - device.pressHome(); - - // Launch Notifee-dev's app settings - Context context = InstrumentationRegistry.getContext(); - Intent appSettingsIntent = - new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - appSettingsIntent.setData(Uri.parse("package:com.notifee.testing")); - context.startActivity(appSettingsIntent); - - // Disable Notification - try { - device.findObject(new UiSelector().text("App notifications")).clickAndWaitForNewWindow(); - } catch (UiObjectNotFoundException exception) { - // Skip if cannot found - } - try { - device.findObject(new UiSelector().text("On")).click(); - } catch (UiObjectNotFoundException exception) { - // Skip if already off - } - device.waitForIdle(); - - // Launch App - Intent appIntent = context.getPackageManager().getLaunchIntentForPackage("com.notifee.testing"); - context.startActivity(appIntent); - - // Check Notification Settings - device.wait(Until.hasObject(By.text("GET NOTIFICATION SETTINGS")), TIMEOUT); - device.findObject(new UiSelector().text("GET NOTIFICATION SETTINGS")).click(); - device.wait(Until.hasObject(By.text("Notification Settings")), TIMEOUT); - - device.wait( - Until.hasObject( - By.text( - "{\"authorizationStatus\":0,\"ios\":{\"alert\":1,\"badge\":1,\"criticalAlert\":1,\"showPreviews\":1,\"sound\":1,\"carPlay\":1,\"lockScreen\":1,\"announcement\":1,\"notificationCenter\":1,\"inAppNotificationSettings\":1,\"authorizationStatus\":0}}")), - TIMEOUT); - - Assert.assertTrue( - device - .findObject( - new UiSelector() - .text( - "{\"authorizationStatus\":0,\"ios\":{\"alert\":1,\"badge\":1,\"criticalAlert\":1,\"showPreviews\":1,\"sound\":1,\"carPlay\":1,\"lockScreen\":1,\"announcement\":1,\"notificationCenter\":1,\"inAppNotificationSettings\":1,\"authorizationStatus\":0}}")) - .exists()); - - device.findObject(new UiSelector().text("CANCEL")).click(); - - // Repeat the step for enabled state - context.startActivity(appSettingsIntent); - device.findObject(new UiSelector().text("Off")).click(); - - context.startActivity(appIntent); - - device.wait(Until.hasObject(By.text("GET NOTIFICATION SETTINGS")), TIMEOUT); - device.findObject(new UiSelector().text("GET NOTIFICATION SETTINGS")).click(); - device.wait(Until.hasObject(By.text("Notification Settings")), TIMEOUT); - Assert.assertTrue( - device - .findObject( - new UiSelector() - .text( - "{\"authorizationStatus\":1,\"ios\":{\"alert\":1,\"badge\":1,\"criticalAlert\":1,\"showPreviews\":1,\"sound\":1,\"carPlay\":1,\"lockScreen\":1,\"announcement\":1,\"notificationCenter\":1,\"inAppNotificationSettings\":1,\"authorizationStatus\":1}}")) - .exists()); - device.waitForIdle(); - device.findObject(new UiSelector().text("CANCEL")).click(); - } -} diff --git a/tests_react_native/example/app.tsx b/tests_react_native/example/app.tsx index bbf8d3ce..07d4b01c 100755 --- a/tests_react_native/example/app.tsx +++ b/tests_react_native/example/app.tsx @@ -237,6 +237,12 @@ function Root(): any { console.log(await Notifee.openPowerManagerSettings()); }} /> +