Skip to content

Commit

Permalink
RN-138 Android notification preferences (mattermost#889)
Browse files Browse the repository at this point in the history
* Fix Android and iOS flow

* Own review

* RN-138 Android notification preferences

* Feedback review

* Feedback review 2
  • Loading branch information
enahum authored Sep 13, 2017
1 parent 7aebcbd commit e0cfefc
Show file tree
Hide file tree
Showing 17 changed files with 632 additions and 48 deletions.
10 changes: 5 additions & 5 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ android {
buildToolsVersion "25.0.1"

defaultConfig {
applicationId "com.mattermost.rn"
applicationId "com.mattermost.rnbeta"
minSdkVersion 16
targetSdkVersion 23
versionCode 49
Expand Down Expand Up @@ -152,10 +152,6 @@ android {
}

dependencies {
compile project(':react-native-youtube')
compile project(':react-native-sentry')
compile project(':react-native-exception-handler')
compile project(':react-native-fetch-blob')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:25.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
Expand All @@ -174,6 +170,10 @@ dependencies {
compile project(':react-native-svg')
compile project(':react-native-local-auth')
compile project(':jail-monkey')
compile project(':react-native-youtube')
compile project(':react-native-sentry')
compile project(':react-native-exception-handler')
compile project(':react-native-fetch-blob')

// For animated GIF support
compile 'com.facebook.fresco:animated-base-support:1.0.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.app.Notification;
Expand All @@ -23,6 +27,8 @@
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.helpers.ApplicationBadgeHelper;

import android.util.Log;

import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;

public class CustomPushNotification extends PushNotification {
Expand Down Expand Up @@ -99,7 +105,13 @@ public void onOpened() {

@Override
protected void postNotification(int id, Notification notification) {
if (!mAppLifecycleFacade.isAppVisible()) {
boolean force = false;
Bundle bundle = notification.extras;
if (bundle != null) {
force = bundle.getBoolean("localTest");
}

if (!mAppLifecycleFacade.isAppVisible() || force) {
super.postNotification(id, notification);
}
}
Expand All @@ -108,9 +120,10 @@ protected void postNotification(int id, Notification notification) {
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
NotificationPreferencesModule notificationPreferences = NotificationPreferencesModule.getInstance();

// First, get a builder initialized with defaults from the core class.
final Notification.Builder notification = super.getNotificationBuilder(intent);
final Notification.Builder notification = new Notification.Builder(mContext);
Bundle bundle = mNotificationProps.asBundle();
String title = bundle.getString("title");
if (title == null) {
Expand All @@ -120,13 +133,18 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {

String channelId = bundle.getString("channel_id");
String postId = bundle.getString("post_id");
int notificationId = channelId.hashCode();
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
String message = bundle.getString("message");
String subText = bundle.getString("subText");
String numberString = bundle.getString("badge");
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");

Bundle b = bundle.getBundle("userInfo");
if (b != null) {
notification.addExtras(b);
}

int smallIconResId;
int largeIconResId;

Expand Down Expand Up @@ -157,10 +175,12 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
int numMessages = getMessageCountInChannel(channelId);

notification
.setContentIntent(intent)
.setGroupSummary(true)
.setSmallIcon(smallIconResId)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH);
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);

if (numMessages == 1) {
notification
Expand Down Expand Up @@ -223,6 +243,27 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
notification.setSubText(subText);
}

String soundUri = notificationPreferences.getNotificationSound();
if (soundUri != null) {
if (soundUri != "none") {
notification.setSound(Uri.parse(soundUri), AudioManager.STREAM_NOTIFICATION);
}
} else {
Uri defaultUri = RingtoneManager.getActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_NOTIFICATION);
notification.setSound(defaultUri, AudioManager.STREAM_NOTIFICATION);
}

boolean vibrate = notificationPreferences.getShouldVibrate();
if (vibrate) {
// Each element then alternates between delay, vibrate, sleep, vibrate, sleep
notification.setVibrate(new long[] {1000, 1000, 500, 1000, 500});
}

boolean blink = notificationPreferences.getShouldBlink();
if (blink) {
notification.setLights(Color.CYAN, 500, 500);
}

return notification;
}

Expand All @@ -236,7 +277,7 @@ public static Integer getMessageCountInChannel(String channelId) {
return (Integer)objCount;
}

return 0;
return 1;
}

private void cancelNotification(Bundle data, int notificationId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public List<ReactPackage> createAdditionalReactPackages() {
new LocalAuthPackage(),
new JailMonkeyPackage(),
new RNFetchBlobPackage(),
new MattermostManagedPackage(),
new MattermostPackage(this),
new RNSentryPackage(this),
new ReactNativeExceptionHandlerPackage(),
new ReactNativeYouTube()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;

public class MattermostManagedPackage implements ReactPackage {
public class MattermostPackage implements ReactPackage {
private final MainApplication mApplication;

public MattermostPackage(MainApplication application) {
mApplication = application;
}

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(MattermostManagedModule.getInstance(reactContext));
return Arrays.<NativeModule>asList(
MattermostManagedModule.getInstance(reactContext),
NotificationPreferencesModule.getInstance(mApplication, reactContext)
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.mattermost.rnbeta;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.os.Bundle;
import android.net.Uri;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;

public class NotificationPreferencesModule extends ReactContextBaseJavaModule {
private static NotificationPreferencesModule instance;
private final MainApplication mApplication;
private SharedPreferences mSharedPreferences;

private final String SHARED_NAME = "NotificationPreferences";
private final String SOUND_PREF = "NotificationSound";
private final String VIBRATE_PREF = "NotificationVibrate";
private final String BLINK_PREF = "NotificationLights";

private NotificationPreferencesModule(MainApplication application, ReactApplicationContext reactContext) {
super(reactContext);
mApplication = application;
Context context = mApplication.getApplicationContext();
mSharedPreferences = context.getSharedPreferences(SHARED_NAME, Context.MODE_PRIVATE);
}

public static NotificationPreferencesModule getInstance(MainApplication application, ReactApplicationContext reactContext) {
if (instance == null) {
instance = new NotificationPreferencesModule(application, reactContext);
}

return instance;
}

public static NotificationPreferencesModule getInstance() {
return instance;
}

@Override
public String getName() {
return "NotificationPreferences";
}

@ReactMethod
public void getPreferences(final Promise promise) {
try {
Context context = mApplication.getApplicationContext();
RingtoneManager manager = new RingtoneManager(context);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();

WritableMap result = Arguments.createMap();
WritableArray sounds = Arguments.createArray();
while (cursor.moveToNext()) {
String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String notificationId = cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
String notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);

WritableMap map = Arguments.createMap();
map.putString("name", notificationTitle);
map.putString("uri", (notificationUri + "/" + notificationId));
sounds.pushMap(map);
}

Uri defaultUri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_NOTIFICATION);
result.putString("defaultUri", Uri.decode(defaultUri.toString()));
result.putString("selectedUri", getNotificationSound());
result.putBoolean("shouldVibrate", getShouldVibrate());
result.putBoolean("shouldBlink", getShouldBlink());
result.putArray("sounds", sounds);

promise.resolve(result);
} catch (Exception e) {
promise.reject("no notification sounds found", e);
}
}

@ReactMethod
public void previewSound(String url) {
Context context = mApplication.getApplicationContext();
Uri uri = Uri.parse(url);
Ringtone r = RingtoneManager.getRingtone(context, uri);
r.play();
}

@ReactMethod
public void setNotificationSound(String soundUri) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(SOUND_PREF, soundUri);
editor.commit();
}

@ReactMethod
public void setShouldVibrate(boolean vibrate) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(VIBRATE_PREF, vibrate);
editor.commit();
}

@ReactMethod
public void setShouldBlink(boolean vibrate) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(BLINK_PREF, vibrate);
editor.commit();
}

public String getNotificationSound() {
return mSharedPreferences.getString(SOUND_PREF, null);
}

public boolean getShouldVibrate() {
return mSharedPreferences.getBoolean(VIBRATE_PREF, true);
}

public boolean getShouldBlink() {
return mSharedPreferences.getBoolean(BLINK_PREF, false);
}
}
5 changes: 5 additions & 0 deletions app/notification_preferences/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import NotificationPreferences from './notification_preferences';
export default NotificationPreferences;
14 changes: 14 additions & 0 deletions app/notification_preferences/notification_preferences.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import {NativeModules} from 'react-native';

const {NotificationPreferences} = NativeModules;

export default {
getPreferences: NotificationPreferences.getPreferences,
setNotificationSound: NotificationPreferences.setNotificationSound,
setShouldVibrate: NotificationPreferences.setShouldVibrate,
setShouldBlink: NotificationPreferences.setShouldBlink,
play: NotificationPreferences.previewSound
};
10 changes: 10 additions & 0 deletions app/notification_preferences/notification_preferences.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

export default {
getPreferences: async () => null,
setNotificationSound: () => null,
setShouldVibrate: () => null,
setShouldBlink: () => null,
play: () => null
};
4 changes: 4 additions & 0 deletions app/push_notifications/push_notifications.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class PushNotification {
}
}

localNotification(notification) {
NotificationsAndroid.localNotification(notification);
}

cancelAllLocalNotifications() {
NotificationsAndroid.cancelAllLocalNotifications();
}
Expand Down
Loading

0 comments on commit e0cfefc

Please sign in to comment.