Skip to content

Commit

Permalink
Fix channel and channel group implementation (#750)
Browse files Browse the repository at this point in the history
#535 started implementing custom channel and channels group creation but there were some missing parts that this PR finishes:

- Using the incoming channel in `PostNotification` after creating it
- Add channel group name and add this group to the custom channel
- Show it in the example app

This PR also fixes very old tests
Closes #723 

Co-authored-by: Yogev Ben David <[email protected]>
  • Loading branch information
DanielEliraz and yogevbd authored Jun 23, 2021
1 parent e214fd9 commit f5b25b5
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 71 deletions.
Binary file not shown.
30 changes: 26 additions & 4 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ class NotificationsExampleApp extends Component {
completion();
});

Notifications.ios.events().appNotificationSettingsLinked(() => {
console.warn('App Notification Settings Linked')
});
if (Platform.OS === 'ios') {
Notifications.ios.events().appNotificationSettingsLinked(() => {
console.warn('App Notification Settings Linked')
});
}
}

requestPermissionsIos(options) {
Expand Down Expand Up @@ -93,13 +95,30 @@ class NotificationsExampleApp extends Component {
sound: 'chime.aiff',
category: 'SOME_CATEGORY',
link: 'localNotificationLink',
android_channel_id: 'my-channel',
});
}

removeAllDeliveredNotifications() {
Notifications.removeAllDeliveredNotifications();
}

setNotificationChannel() {
Notifications.setNotificationChannel({
channelId: 'my-channel',
name: 'My Channel',
groupId: 'my-group-id',
groupName: 'my group name',
importance: 5,
description: 'My Description',
enableLights: true,
enableVibration: true,
showBadge: true,
soundFile: 'doorbell.mp3',
vibrationPattern: [200, 1000, 500, 1000, 500],
})
}

async componentDidMount() {
const initialNotification = await Notifications.getInitialNotification();
if (initialNotification) {
Expand Down Expand Up @@ -134,7 +153,7 @@ class NotificationsExampleApp extends Component {
{this.renderNotification(notification)}
</View>
));
const openedNotifications = this.state.openedNotifications.map((notification, idx) =>
const openedNotifications = this.state.openedNotifications.map((notification, idx) =>
(
<View key={`notification_${idx}`}>
{this.renderOpenedNotification(notification)}
Expand All @@ -148,6 +167,9 @@ class NotificationsExampleApp extends Component {
<Button title={'Request permissions with provisional'} onPress={() => this.requestPermissionsIos(['Provisional'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with app notification settings and provisional'} onPress={() => this.requestPermissionsIos(['ProvidesAppNotificationSettings', 'Provisional'])} testID={'requestPermissionsWithAppSettings'} />
</>)}
{Platform.OS === 'android' &&
<Button title={'Set channel'} onPress={this.setNotificationChannel} testID={'setNotificationChannel'} />
}
<Button title={'Send local notification'} onPress={this.sendLocalNotification} testID={'sendLocalNotification'} />
<Button title={'Remove all delivered notifications'} onPress={this.removeAllDeliveredNotifications} />
{notifications}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wix.reactnativenotifications.core.notification;

import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Color;
Expand Down Expand Up @@ -33,6 +34,9 @@ public void setNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
final NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);

android.app.NotificationChannel channel = new android.app.NotificationChannel(
mNotificationChannelProps.getChannelId(),
mNotificationChannelProps.getName(),
Expand All @@ -48,8 +52,13 @@ public void setNotificationChannel() {
if (mNotificationChannelProps.hasEnableVibration()) {
channel.enableVibration(mNotificationChannelProps.getEnableVibration());
}
if (mNotificationChannelProps.hasGroupId()) {
channel.setGroup(mNotificationChannelProps.getGroupId());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && mNotificationChannelProps.hasGroupId()) {
final String groupId = mNotificationChannelProps.getGroupId();
if(notificationManager.getNotificationChannelGroup(groupId) == null) {
notificationManager.createNotificationChannelGroup(
new NotificationChannelGroup(groupId, mNotificationChannelProps.getGroupName()));
}
channel.setGroup(groupId);
}
if (mNotificationChannelProps.hasLightColor()) {
channel.setLightColor(Color.parseColor(mNotificationChannelProps.getLightColor()));
Expand All @@ -65,9 +74,6 @@ public void setNotificationChannel() {
createVibrationPatternFromList(mNotificationChannelProps.getVibrationPattern())
);
}

final NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ public boolean hasGroupId() {
return mBundle.containsKey("groupId");
}

public String getGroupName() {
String name = mBundle.getString("groupName");
if (name == null) {
name = getGroupId();
}
return name;
}

public boolean hasGroupName() {
return mBundle.containsKey("groupName");
}

public int getImportance() {
return (int) mBundle.getDouble("importance");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public void onAppVisible() {
public void onAppNotVisible() {
}
};
final private String DEFAULT_CHANNEL_ID = "channel_01";
final private String DEFAULT_CHANNEL_NAME = "Channel Name";

public static IPushNotification get(Context context, Bundle bundle) {
Context appContext = context.getApplicationContext();
Expand All @@ -56,6 +58,7 @@ protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade ap
mAppLaunchHelper = appLaunchHelper;
mJsIOHelper = JsIOHelper;
mNotificationProps = createProps(bundle);
initDefaultChannel(context);
}

@Override
Expand Down Expand Up @@ -144,10 +147,6 @@ protected Notification buildNotification(PendingIntent intent) {
}

protected Notification.Builder getNotificationBuilder(PendingIntent intent) {

String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Channel Name";

final Notification.Builder notification = new Notification.Builder(mContext)
.setContentTitle(mNotificationProps.getTitle())
.setContentText(mNotificationProps.getBody())
Expand All @@ -158,12 +157,10 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
setUpIcon(notification);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
notification.setChannelId(CHANNEL_ID);
String channelId = mNotificationProps.getChannelId();
NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
notification.setChannelId(channel != null ? channelId : DEFAULT_CHANNEL_ID);
}

return notification;
Expand Down Expand Up @@ -226,4 +223,14 @@ protected void launchOrResumeApp() {
private int getAppResourceId(String resName, String resType) {
return mContext.getResources().getIdentifier(resName, resType, mContext.getPackageName());
}

private void initDefaultChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel defaultChannel = new NotificationChannel(DEFAULT_CHANNEL_ID,
DEFAULT_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(defaultChannel);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public String getBody() {
return getBundleStringFirstNotNull("gcm.notification.body", "body");
}

public String getChannelId() {
return getBundleStringFirstNotNull("gcm.notification.android_channel_id", "android_channel_id");
}

public Bundle asBundle() {
return (Bundle) mBundle.clone();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowNotification;

import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_BACKGROUND_EVENT_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -49,6 +50,7 @@ public class PushNotificationTest {

private static final String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened";
private static final String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived";
private static final String NOTIFICATION_RECEIVED_BACKGROUND_EVENT_NAME = "notificationReceivedBackground";

private static final String DEFAULT_NOTIFICATION_TITLE = "Notification-title";
private static final String DEFAULT_NOTIFICATION_BODY = "Notification-body";
Expand Down Expand Up @@ -206,7 +208,7 @@ public void onOpened_reactInitializedWithNoActivities_setAsInitialNotification()
}

@Test
public void onReceived_validData_postNotificationAndNotifyJS() throws Exception {
public void onReceived_validData_dontPostNotificationAndNotifyJS() throws Exception {
// Arrange

setUpForegroundApp();
Expand All @@ -219,19 +221,15 @@ public void onReceived_validData_postNotificationAndNotifyJS() throws Exception
// Assert

ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);

// Notifications should not be visible while app is in foreground
verify(mNotificationManager, never()).notify(anyInt(), notificationCaptor.capture());

// Notifications should be reported to javascript while app is in background
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(mReactContext));
}

@Test
public void onReceived_validDataForBackgroundApp_postNotificationAndNotifyJs() throws Exception {
// Arrange

setUpForegroundApp();
setUpBackgroundApp();

// Act

Expand All @@ -241,12 +239,8 @@ public void onReceived_validDataForBackgroundApp_postNotificationAndNotifyJs() t
// Assert

ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);

// Notifications should not be visible while app is in foreground
verify(mNotificationManager, never()).notify(anyInt(), notificationCaptor.capture());

// Notifications should be reported to javascript while app is in background
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(mReactContext));
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_BACKGROUND_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(mReactContext));
}

@Test
Expand All @@ -257,8 +251,7 @@ public void onReceived_validDataForDeadApp_postNotificationDontNotifyJS() throws
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verifyNotification(notificationCaptor.getValue());

verify(mJsIOHelper, never()).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), any(Bundle.class), any(ReactContext.class));
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_BACKGROUND_EVENT_NAME), argThat(new isValidNotification(mNotificationBundle)), eq(null));
}

@Test
Expand Down
51 changes: 13 additions & 38 deletions lib/src/interfaces/NotificationChannel.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,14 @@
export class NotificationChannel {
channelId: string
name: string
importance: -1000 | 0 | 1 | 2 | 3 | 4 | 5
description?: string
enableLights?: boolean
enableVibration?: boolean
groupId?: string
lightColor?: string
showBadge?: boolean
soundFile?: string // "sound_file.mp3" for the file "android/app/src/main/res/raw/sound_file.mp3"
vibrationPattern?: number[]

constructor(
channelId: string,
name: string,
importance: -1000 | 0 | 1 | 2 | 3 | 4 | 5,
description?: string,
enableLights?: boolean,
enableVibration?: boolean,
groupId?: string,
lightColor?: string,
showBadge?: boolean,
soundFile?: string,
vibrationPattern?: number[],
) {
this.channelId = channelId;
this.name = name;
this.importance = importance;
this.description = description;
this.enableLights = enableLights;
this.enableVibration = enableVibration;
this.groupId = groupId;
this.lightColor = lightColor;
this.showBadge = showBadge;
this.soundFile = soundFile;
this.vibrationPattern = vibrationPattern;
}
export interface NotificationChannel {
channelId: string;
name: string;
importance: -1000 | 0 | 1 | 2 | 3 | 4 | 5;
description?: string;
enableLights?: boolean;
enableVibration?: boolean;
groupId?: string;
groupName?: string;
lightColor?: string;
showBadge?: boolean;
soundFile?: string;
vibrationPattern?: number[];
}
3 changes: 2 additions & 1 deletion website/docs/api/android-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Notifications.setNotificationChannel({
description: 'My Description',
enableLights: true,
enableVibration: true,
groupId: 'your-group', // optional
groupId: 'my-group', // optional
groupName: 'My Group', // optional, will be presented in Android OS notification permission
showBadge: true,
soundFile: 'custom_sound.mp3', // place this in <project_root>/android/app/src/main/res/raw/custom_sound.mp3
vibrationPattern: [200, 1000, 500, 1000, 500],
Expand Down

0 comments on commit f5b25b5

Please sign in to comment.