Skip to content

Commit 1aa1d0b

Browse files
authored
feat(local-notifications): allow requesting send notifications permission on Android 13+ devices (#472)
1 parent d5ee861 commit 1aa1d0b

File tree

8 files changed

+90
-60
lines changed

8 files changed

+90
-60
lines changed

apps/demo/src/plugin-demos/local-notifications.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99

1010
<Label row="0" colSpan="2" text="You need permission before being able to schedule local notifications. Either defer to when you schedule it, or do it when the app first loads." class="hint" textWrap="true" />
1111
<Button row="1" col="0" text="has permission?" tap="{{ doCheckHasPermission }}" class="button button-neutral"/>
12-
<iOS>
13-
<Button row="1" col="1" text="request perm." tap="{{ doRequestPermission }}" class="button button-neutral"/>
14-
</iOS>
12+
<Button row="1" col="1" text="request perm." tap="{{ doRequestPermission }}" class="button button-neutral"/>
13+
1514

1615
<Label row="2" colSpan="2" text="After scheduling a notification, it pops up after 10 sec. In some cases you'll need to close the app first (see 'forceShowWhenInForeground')." class="hint" textWrap="true" />
1716

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@angular/platform-browser": "^15.0.0",
2727
"@angular/platform-browser-dynamic": "^15.0.0",
2828
"@angular/router": "^15.0.0",
29+
"@nativescript-community/perms": "^2.3.0",
2930
"@nativescript/angular": "^15.0.0",
3031
"@nativescript/core": "~8.4.0",
3132
"@nativescript/plugin-tools": "5.0.3",

packages/local-notifications/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ LocalNotifications.requestPermission().then((granted) => {
289289
```
290290

291291
Requests for the user's permissions for the app to send her notifications.
292-
You only need to call this method on iOS. If the permission has already been granted, it returns `true`. Otherwise, it returns `false`.
292+
If the permission has already been granted, it returns `true`. Otherwise, it returns `false`.
293293

294294
---
295295
### hasPermission()

packages/local-notifications/index.android.ts

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Application, Device, Utils } from '@nativescript/core';
2+
import { check, request, Result } from '@nativescript-community/perms';
23
import { LocalNotificationsApi, LocalNotificationsCommon, ReceivedNotification, ScheduleInterval, ScheduleOptions } from './common';
34

45
declare const com, global: any;
@@ -68,27 +69,23 @@ export class LocalNotificationsImpl extends LocalNotificationsCommon implements
6869
com.telerik.localnotifications.Store.remove(context, id);
6970
}
7071

71-
hasPermission(): Promise<boolean> {
72-
return new Promise((resolve, reject) => {
73-
try {
74-
resolve(LocalNotificationsImpl.hasPermission());
75-
} catch (ex) {
76-
console.log('Error in LocalNotifications.hasPermission: ' + ex);
77-
reject(ex);
78-
}
79-
});
72+
async hasPermission(): Promise<boolean> {
73+
try {
74+
return await LocalNotificationsImpl.canSend();
75+
} catch (ex) {
76+
console.log('Error in LocalNotifications.hasPermission: ' + ex);
77+
throw ex;
78+
}
8079
}
8180

82-
requestPermission(): Promise<boolean> {
83-
return new Promise((resolve, reject) => {
84-
try {
85-
// AFAIK can't do it on this platform.. when 'false' is returned, the app could prompt the user to manually enable them in the Device Settings
86-
resolve(LocalNotificationsImpl.hasPermission());
87-
} catch (ex) {
88-
console.log('Error in LocalNotifications.requestPermission: ' + ex);
89-
reject(ex);
90-
}
91-
});
81+
async requestPermission(): Promise<boolean> {
82+
try {
83+
await LocalNotificationsImpl.ensurePreconditions();
84+
return true;
85+
} catch (ex) {
86+
console.log('Error in LocalNotifications.requestPermission: ' + ex);
87+
return false;
88+
}
9289
}
9390

9491
addOnMessageReceivedCallback(onReceived: (data: ReceivedNotification) => void): Promise<void> {
@@ -186,58 +183,89 @@ export class LocalNotificationsImpl extends LocalNotificationsCommon implements
186183
});
187184
}
188185

189-
schedule(scheduleOptions: ScheduleOptions[]): Promise<Array<number>> {
190-
return new Promise((resolve, reject) => {
191-
try {
192-
if (!LocalNotificationsImpl.hasPermission()) {
193-
reject('Permission not granted');
194-
return;
195-
}
196-
197-
const context = Utils.android.getApplicationContext();
198-
const resources = context.getResources();
199-
const scheduledIds: Array<number> = [];
186+
async schedule(scheduleOptions: ScheduleOptions[]): Promise<Array<number>> {
187+
try {
188+
await LocalNotificationsImpl.ensurePreconditions();
200189

201-
// TODO: All these changes in the options (other than setting the ID) should rather be done in Java so that
202-
// the persisted options are exactly like the original ones.
190+
const context = Utils.android.getApplicationContext();
191+
const resources = context.getResources();
192+
const scheduledIds: Array<number> = [];
203193

204-
for (let n in scheduleOptions) {
205-
const options = LocalNotificationsImpl.merge(scheduleOptions[n], LocalNotificationsImpl.defaults);
194+
// TODO: All these changes in the options (other than setting the ID) should rather be done in Java so that
195+
// the persisted options are exactly like the original ones.
206196

207-
options.icon = LocalNotificationsImpl.getIcon(context, resources, (LocalNotificationsImpl.IS_GTE_LOLLIPOP && options.silhouetteIcon) || options.icon);
197+
for (let n in scheduleOptions) {
198+
const options = LocalNotificationsImpl.merge(scheduleOptions[n], LocalNotificationsImpl.defaults);
208199

209-
options.atTime = options.at ? options.at.getTime() : 0;
200+
options.icon = LocalNotificationsImpl.getIcon(context, resources, (LocalNotificationsImpl.IS_GTE_LOLLIPOP && options.silhouetteIcon) || options.icon);
210201

211-
// Used when restoring the notification after a reboot:
212-
options.repeatInterval = LocalNotificationsImpl.getInterval(options.interval);
202+
options.atTime = options.at ? options.at.getTime() : 0;
213203

214-
if (options.color) {
215-
options.color = options.color.android;
216-
}
204+
// Used when restoring the notification after a reboot:
205+
options.repeatInterval = LocalNotificationsImpl.getInterval(options.interval);
217206

218-
if (options.notificationLed && options.notificationLed !== true) {
219-
options.notificationLed = options.notificationLed.android;
220-
}
207+
if (options.color) {
208+
options.color = options.color.android;
209+
}
221210

222-
LocalNotificationsImpl.ensureID(options);
211+
if (options.notificationLed && options.notificationLed !== true) {
212+
options.notificationLed = options.notificationLed.android;
213+
}
223214

224-
com.telerik.localnotifications.LocalNotificationsPlugin.scheduleNotification(new org.json.JSONObject(JSON.stringify(options)), context);
215+
LocalNotificationsImpl.ensureID(options);
225216

226-
scheduledIds.push(options.id);
227-
}
217+
com.telerik.localnotifications.LocalNotificationsPlugin.scheduleNotification(new org.json.JSONObject(JSON.stringify(options)), context);
228218

229-
resolve(scheduledIds);
230-
} catch (ex) {
231-
console.log('Error in LocalNotifications.schedule: ' + ex);
232-
reject(ex);
219+
scheduledIds.push(options.id);
233220
}
234-
});
221+
222+
return scheduledIds;
223+
} catch (ex) {
224+
console.log('Error in LocalNotifications.schedule: ' + ex);
225+
throw ex;
226+
}
227+
}
228+
229+
private static async ensurePreconditions(): Promise<void> {
230+
const hasPermission = await LocalNotificationsImpl.hasPermission();
231+
if (!hasPermission) {
232+
const granted = await LocalNotificationsImpl.requestPermission();
233+
if (!granted) throw new Error('Permission not granted');
234+
}
235+
// AFAIK can't do it on this platform. when 'false' is returned, the app could prompt the user to manually enable them in the Device Settings
236+
const enabled = LocalNotificationsImpl.areEnabled();
237+
if (!enabled) throw new Error('Notifications were manually disabled');
235238
}
236239

237-
private static hasPermission(): boolean {
240+
private static async canSend(): Promise<boolean> {
241+
const hasPermission = await LocalNotificationsImpl.hasPermission();
242+
const areEnabled = LocalNotificationsImpl.areEnabled();
243+
return hasPermission && areEnabled;
244+
}
245+
246+
private static areEnabled(): boolean {
238247
const context = Utils.android.getApplicationContext();
239248
return !context || NotificationManagerCompatPackageName.NotificationManagerCompat.from(context).areNotificationsEnabled();
240249
}
250+
251+
private static async hasPermission(): Promise<boolean> {
252+
const result = await check('notification');
253+
return LocalNotificationsImpl.isAuthorized(result);
254+
}
255+
256+
private static async requestPermission(): Promise<boolean> {
257+
try {
258+
const result = await request('notification');
259+
return LocalNotificationsImpl.isAuthorized(result);
260+
} catch (ex) {
261+
return false;
262+
}
263+
}
264+
265+
private static isAuthorized(result: Result): boolean {
266+
const [status, _] = result;
267+
return status === 'authorized';
268+
}
241269
}
242270

243271
export const LocalNotifications = new LocalNotificationsImpl();

packages/local-notifications/native-src/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
55
<uses-permission android:name="android.permission.WAKE_LOCK" />
6+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
67

78
<application>
89
<service

packages/local-notifications/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
},
4242
"homepage": "https://github.com/nativescript/plugins",
4343
"dependencies": {
44-
"@nativescript/shared-notification-delegate": "~1.0.0"
44+
"@nativescript/shared-notification-delegate": "~1.0.0",
45+
"@nativescript-community/perms": "^2.3.0"
4546
}
4647
}
18 Bytes
Binary file not shown.

references.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// <reference path="./node_modules/@nativescript/types-ios/index.d.ts" />
2-
/// <reference path="./node_modules/@nativescript/types-android/lib/android-29.d.ts" />
2+
/// <reference path="./node_modules/@nativescript/types-android/lib/android-33.d.ts" />
33
/// <reference path="./node_modules/@nativescript/types-ios/lib/ios/objc-x86_64/objc!AudioToolbox.d.ts" />
44
/// <reference path="./node_modules/@nativescript/types-ios/lib/ios/objc-x86_64/objc!CoreLocation.d.ts" />
55
/// <reference path="./node_modules/@nativescript/types-ios/lib/ios/objc-x86_64/objc!Darwin.d.ts" />

0 commit comments

Comments
 (0)