Skip to content

Commit 6a8161b

Browse files
author
Buicescu Alexandru
authored
Channels (wix#535)
* creating channels works * check if app is visible when showing notification * changed protected method to private * fixes imports in typescript * fixes push notifications tests * rerun tests - mac slave went offline
1 parent 2fa5ae3 commit 6a8161b

File tree

11 files changed

+335
-5
lines changed

11 files changed

+335
-5
lines changed

lib/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java

+12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.wix.reactnativenotifications.core.InitialNotificationHolder;
2020
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
2121
import com.wix.reactnativenotifications.core.ReactAppLifecycleFacade;
22+
import com.wix.reactnativenotifications.core.notification.INotificationChannel;
2223
import com.wix.reactnativenotifications.core.notification.IPushNotification;
24+
import com.wix.reactnativenotifications.core.notification.NotificationChannel;
2325
import com.wix.reactnativenotifications.core.notification.PushNotification;
2426
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
2527
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
@@ -128,6 +130,16 @@ public void isRegisteredForRemoteNotifications(Promise promise) {
128130
notificationsDrawer.onAllNotificationsClearRequest();
129131
}
130132

133+
@ReactMethod
134+
void setNotificationChannel(ReadableMap notificationChannelPropsMap) {
135+
final Bundle notificationChannelProps = Arguments.toBundle(notificationChannelPropsMap);
136+
INotificationChannel notificationsDrawer = NotificationChannel.get(
137+
getReactApplicationContext().getApplicationContext(),
138+
notificationChannelProps
139+
);
140+
notificationsDrawer.setNotificationChannel();
141+
}
142+
131143
protected void startFcmIntentService(String extraFlag) {
132144
final Context appContext = getReactApplicationContext().getApplicationContext();
133145
final Intent tokenFetchIntent = new Intent(appContext, FcmInstanceIdRefreshHandlerService.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.wix.reactnativenotifications.core.notification;
2+
3+
public interface INotificationChannel {
4+
5+
/**
6+
* Creates a new notification channel with the given parameters. This also updates an existing
7+
* notification channel.
8+
*
9+
*/
10+
void setNotificationChannel();
11+
12+
NotificationChannelProps asProps();
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.wix.reactnativenotifications.core.notification;
2+
3+
import android.app.NotificationManager;
4+
import android.content.Context;
5+
import android.graphics.Color;
6+
import android.media.RingtoneManager;
7+
import android.net.Uri;
8+
import android.os.Build;
9+
import android.os.Bundle;
10+
11+
import java.util.List;
12+
13+
public class NotificationChannel implements INotificationChannel {
14+
15+
final protected Context mContext;
16+
final protected NotificationChannelProps mNotificationChannelProps;
17+
18+
protected NotificationChannel(Context context, Bundle bundle) {
19+
mContext = context;
20+
mNotificationChannelProps = createProps(bundle);
21+
}
22+
23+
public static INotificationChannel get(Context context, Bundle bundle) {
24+
return new NotificationChannel(context, bundle);
25+
}
26+
27+
protected NotificationChannelProps createProps(Bundle bundle) {
28+
return new NotificationChannelProps(bundle);
29+
}
30+
31+
@Override
32+
public void setNotificationChannel() {
33+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
34+
return;
35+
}
36+
android.app.NotificationChannel channel = new android.app.NotificationChannel(
37+
mNotificationChannelProps.getChannelId(),
38+
mNotificationChannelProps.getName(),
39+
mNotificationChannelProps.getImportance()
40+
);
41+
42+
if (mNotificationChannelProps.hasDescription()) {
43+
channel.setDescription(mNotificationChannelProps.getDescription());
44+
}
45+
if (mNotificationChannelProps.hasEnableLights()) {
46+
channel.enableLights(mNotificationChannelProps.getEnableLights());
47+
}
48+
if (mNotificationChannelProps.hasEnableVibration()) {
49+
channel.enableVibration(mNotificationChannelProps.getEnableVibration());
50+
}
51+
if (mNotificationChannelProps.hasGroupId()) {
52+
channel.setGroup(mNotificationChannelProps.getGroupId());
53+
}
54+
if (mNotificationChannelProps.hasLightColor()) {
55+
channel.setLightColor(Color.parseColor(mNotificationChannelProps.getLightColor()));
56+
}
57+
if (mNotificationChannelProps.hasShowBadge()) {
58+
channel.setShowBadge(mNotificationChannelProps.getShowBadge());
59+
}
60+
if (mNotificationChannelProps.hasSoundFile()) {
61+
channel.setSound(getSound(mNotificationChannelProps.getSoundFile()), null);
62+
}
63+
if (mNotificationChannelProps.hasVibrationPattern()) {
64+
channel.setVibrationPattern(
65+
createVibrationPatternFromList(mNotificationChannelProps.getVibrationPattern())
66+
);
67+
}
68+
69+
final NotificationManager notificationManager = (NotificationManager) mContext
70+
.getSystemService(Context.NOTIFICATION_SERVICE);
71+
notificationManager.createNotificationChannel(channel);
72+
}
73+
74+
@Override
75+
public NotificationChannelProps asProps() {
76+
return mNotificationChannelProps.copy();
77+
}
78+
79+
private long[] createVibrationPatternFromList(List patternRequest) {
80+
if (patternRequest == null) {
81+
return null;
82+
}
83+
84+
long[] pattern = new long[patternRequest.size()];
85+
for (int i = 0; i < patternRequest.size(); i++) {
86+
if (patternRequest.get(i) instanceof Number) {
87+
pattern[i] = ((Number) patternRequest.get(i)).longValue();
88+
}
89+
}
90+
return pattern;
91+
}
92+
93+
private Uri getSound(String sound) {
94+
if (sound == null) {
95+
return null;
96+
} else if (sound.contains("://")) {
97+
return Uri.parse(sound);
98+
} else if (sound.equalsIgnoreCase("default")) {
99+
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
100+
} else {
101+
int soundResourceId = getResourceId("raw", sound);
102+
if (soundResourceId == 0) {
103+
soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.')));
104+
}
105+
return Uri.parse("android.resource://" + mContext.getPackageName() + "/" + soundResourceId);
106+
}
107+
}
108+
109+
private int getResourceId(String type, String image) {
110+
return mContext
111+
.getResources()
112+
.getIdentifier(image, type, mContext.getPackageName());
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.wix.reactnativenotifications.core.notification;
2+
3+
import android.os.Bundle;
4+
5+
import java.util.List;
6+
7+
public class NotificationChannelProps {
8+
9+
protected Bundle mBundle;
10+
11+
public NotificationChannelProps(Bundle bundle) {
12+
mBundle = bundle;
13+
}
14+
15+
public String getChannelId() {
16+
return mBundle.getString("channelId");
17+
}
18+
19+
public boolean hasChannelId() {
20+
return mBundle.containsKey("channelId");
21+
}
22+
23+
public String getDescription() {
24+
return mBundle.getString("description");
25+
}
26+
27+
public boolean hasDescription() {
28+
return mBundle.containsKey("description");
29+
}
30+
31+
public boolean getEnableLights() {
32+
return mBundle.getBoolean("enableLights");
33+
}
34+
35+
public boolean hasEnableLights() {
36+
return mBundle.containsKey("enableLights");
37+
}
38+
39+
public boolean getEnableVibration() {
40+
return mBundle.getBoolean("enableVibration");
41+
}
42+
43+
public boolean hasEnableVibration() {
44+
return mBundle.containsKey("enableVibration");
45+
}
46+
47+
public String getGroupId() {
48+
return mBundle.getString("groupId");
49+
}
50+
51+
public boolean hasGroupId() {
52+
return mBundle.containsKey("groupId");
53+
}
54+
55+
public int getImportance() {
56+
return (int) mBundle.getDouble("importance");
57+
}
58+
59+
public boolean hasImportance() {
60+
return mBundle.containsKey("importance");
61+
}
62+
63+
public String getLightColor() {
64+
return mBundle.getString("lightColor");
65+
}
66+
67+
public boolean hasLightColor() {
68+
return mBundle.containsKey("lightColor");
69+
}
70+
71+
public String getName() {
72+
return mBundle.getString("name");
73+
}
74+
75+
public boolean hasName() {
76+
return mBundle.containsKey("name");
77+
}
78+
79+
public boolean getShowBadge() {
80+
return mBundle.getBoolean("showBadge");
81+
}
82+
83+
public boolean hasShowBadge() {
84+
return mBundle.containsKey("showBadge");
85+
}
86+
87+
public String getSoundFile() {
88+
return mBundle.getString("soundFile");
89+
}
90+
91+
public boolean hasSoundFile() {
92+
return mBundle.containsKey("soundFile");
93+
}
94+
95+
public List getVibrationPattern() {
96+
return mBundle.getParcelableArrayList("vibrationPattern");
97+
}
98+
99+
public boolean hasVibrationPattern() {
100+
return mBundle.containsKey("vibrationPattern");
101+
}
102+
103+
public Bundle asBundle() {
104+
return (Bundle) mBundle.clone();
105+
}
106+
107+
@Override
108+
public String toString() {
109+
StringBuilder sb = new StringBuilder(1024);
110+
for (String key : mBundle.keySet()) {
111+
sb.append(key).append("=").append(mBundle.get(key)).append(", ");
112+
}
113+
return sb.toString();
114+
}
115+
116+
protected NotificationChannelProps copy() {
117+
return new NotificationChannelProps((Bundle) mBundle.clone());
118+
}
119+
}

lib/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade ap
5959

6060
@Override
6161
public void onReceived() throws InvalidNotificationException {
62-
postNotification(null);
62+
if (!mAppLifecycleFacade.isAppVisible()) {
63+
postNotification(null);
64+
}
6365
notifyReceivedToJS();
6466
}
6567

lib/android/app/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,11 @@ public void onReceived_validData_postNotificationAndNotifyJS() throws Exception
219219
// Assert
220220

221221
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
222-
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
223-
verifyNotification(notificationCaptor.getValue());
224222

223+
// Notifications should not be visible while app is in foreground
224+
verify(mNotificationManager, never()).notify(anyInt(), notificationCaptor.capture());
225+
226+
// Notifications should be reported to javascript while app is in background
225227
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(mReactContext));
226228
}
227229

@@ -239,9 +241,11 @@ public void onReceived_validDataForBackgroundApp_postNotificationAndNotifyJs() t
239241
// Assert
240242

241243
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
242-
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
243-
verifyNotification(notificationCaptor.getValue());
244244

245+
// Notifications should not be visible while app is in foreground
246+
verify(mNotificationManager, never()).notify(anyInt(), notificationCaptor.capture());
247+
248+
// Notifications should be reported to javascript while app is in background
245249
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(mReactContext));
246250
}
247251

lib/src/Notifications.ts

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Notification } from './DTO/Notification';
77
import { UniqueIdProvider } from './adapters/UniqueIdProvider';
88
import { CompletionCallbackWrapper } from './adapters/CompletionCallbackWrapper';
99
import { NotificationCategory } from './interfaces/NotificationCategory';
10+
import { NotificationChannel } from './interfaces/NotificationChannel';
1011
import { NotificationsIOS } from './NotificationsIOS';
1112
import { NotificationsAndroid } from './NotificationsAndroid';
1213
import { NotificationFactory } from './DTO/NotificationFactory';
@@ -92,6 +93,13 @@ export class NotificationsRoot {
9293
return this.commands.isRegisteredForRemoteNotifications();
9394
}
9495

96+
/**
97+
* setNotificationChannel
98+
*/
99+
public setNotificationChannel(notificationChannel: NotificationChannel) {
100+
return this.android.setNotificationChannel(notificationChannel);
101+
}
102+
95103
/**
96104
* Obtain the events registry instance
97105
*/

lib/src/NotificationsAndroid.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Commands } from './commands/Commands';
22
import { Platform } from 'react-native';
3+
import { NotificationChannel } from './interfaces/NotificationChannel';
34

45
export class NotificationsAndroid {
56
constructor(private readonly commands: Commands) {
@@ -20,4 +21,11 @@ export class NotificationsAndroid {
2021
public registerRemoteNotifications() {
2122
this.commands.refreshToken();
2223
}
24+
25+
/**
26+
* setNotificationChannel
27+
*/
28+
public setNotificationChannel(notificationChannel: NotificationChannel) {
29+
return this.commands.setNotificationChannel(notificationChannel);
30+
}
2331
}

lib/src/adapters/NativeCommandsSender.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Notification } from '../DTO/Notification';
33
import { NotificationCompletion } from '../interfaces/NotificationCompletion';
44
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
55
import { NotificationCategory } from '../interfaces/NotificationCategory';
6+
import { NotificationChannel } from '../interfaces/NotificationChannel';
67

78
interface NativeCommandsModule {
89
getInitialNotification(): Promise<Object>;
@@ -23,6 +24,7 @@ interface NativeCommandsModule {
2324
setCategories(categories: [NotificationCategory?]): void;
2425
finishPresentingNotification(notificationId: string, callback: NotificationCompletion): void;
2526
finishHandlingAction(notificationId: string): void;
27+
setNotificationChannel(notificationChannel: NotificationChannel): void;
2628
}
2729

2830
export class NativeCommandsSender {
@@ -102,4 +104,8 @@ export class NativeCommandsSender {
102104
finishHandlingAction(notificationId: string): void {
103105
this.nativeCommandsModule.finishHandlingAction(notificationId);
104106
}
107+
108+
setNotificationChannel(notificationChannel: NotificationChannel) {
109+
this.nativeCommandsModule.setNotificationChannel(notificationChannel);
110+
}
105111
}

lib/src/commands/Commands.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as _ from 'lodash';
22
import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
33
import { Notification } from '../DTO/Notification';
44
import { NotificationCategory } from '../interfaces/NotificationCategory';
5+
import { NotificationChannel } from '../interfaces/NotificationChannel';
56
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
67
import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
78
import { NotificationFactory } from '../DTO/NotificationFactory';
@@ -86,4 +87,8 @@ export class Commands {
8687
public refreshToken() {
8788
this.nativeCommandsSender.refreshToken();
8889
}
90+
91+
public setNotificationChannel(notificationChannel: NotificationChannel) {
92+
this.nativeCommandsSender.setNotificationChannel(notificationChannel);
93+
}
8994
}

0 commit comments

Comments
 (0)