diff --git a/android/app/build.gradle b/android/app/build.gradle index aa14ea9cf2..7b6ae28efb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,7 +92,7 @@ android { buildToolsVersion "25.0.1" defaultConfig { - applicationId "com.mattermost.rn" + applicationId "com.mattermost.rnbeta" minSdkVersion 16 targetSdkVersion 23 versionCode 49 @@ -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 @@ -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' diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java index e87e25acd3..6bd2df3397 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java @@ -7,6 +7,10 @@ import android.content.res.Resources; 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; @@ -23,6 +27,8 @@ import com.wix.reactnativenotifications.core.AppLifecycleFacade; 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 { @@ -99,7 +105,13 @@ public class CustomPushNotification extends PushNotification { @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); } } @@ -108,9 +120,10 @@ public class CustomPushNotification extends PushNotification { 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) { @@ -120,13 +133,18 @@ public class CustomPushNotification extends PushNotification { 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; @@ -157,10 +175,12 @@ public class CustomPushNotification extends PushNotification { 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 @@ -223,6 +243,27 @@ public class CustomPushNotification extends PushNotification { 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; } @@ -236,7 +277,7 @@ public class CustomPushNotification extends PushNotification { return (Integer)objCount; } - return 0; + return 1; } private void cancelNotification(Bundle data, int notificationId) { diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java index 0a3b378b78..ee6ca2d17e 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -63,7 +63,7 @@ public class MainApplication extends NavigationApplication implements INotificat new LocalAuthPackage(), new JailMonkeyPackage(), new RNFetchBlobPackage(), - new MattermostManagedPackage(), + new MattermostPackage(this), new RNSentryPackage(this), new ReactNativeExceptionHandlerPackage(), new ReactNativeYouTube() diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedPackage.java b/android/app/src/main/java/com/mattermost/rnbeta/MattermostPackage.java similarity index 64% rename from android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedPackage.java rename to android/app/src/main/java/com/mattermost/rnbeta/MattermostPackage.java index 485ccbcc73..22d209f539 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedPackage.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MattermostPackage.java @@ -10,10 +10,19 @@ import com.facebook.react.bridge.ReactApplicationContext; 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 createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(MattermostManagedModule.getInstance(reactContext)); + return Arrays.asList( + MattermostManagedModule.getInstance(reactContext), + NotificationPreferencesModule.getInstance(mApplication, reactContext) + ); } @Override diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java new file mode 100644 index 0000000000..dbb42afc3d --- /dev/null +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationPreferencesModule.java @@ -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); + } +} diff --git a/app/notification_preferences/index.js b/app/notification_preferences/index.js new file mode 100644 index 0000000000..cc94e1a6c8 --- /dev/null +++ b/app/notification_preferences/index.js @@ -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; diff --git a/app/notification_preferences/notification_preferences.android.js b/app/notification_preferences/notification_preferences.android.js new file mode 100644 index 0000000000..50671d1588 --- /dev/null +++ b/app/notification_preferences/notification_preferences.android.js @@ -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 +}; diff --git a/app/notification_preferences/notification_preferences.ios.js b/app/notification_preferences/notification_preferences.ios.js new file mode 100644 index 0000000000..748d7b1d7a --- /dev/null +++ b/app/notification_preferences/notification_preferences.ios.js @@ -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 +}; diff --git a/app/push_notifications/push_notifications.android.js b/app/push_notifications/push_notifications.android.js index 051f5ad56d..9311f8ff64 100644 --- a/app/push_notifications/push_notifications.android.js +++ b/app/push_notifications/push_notifications.android.js @@ -92,6 +92,10 @@ class PushNotification { } } + localNotification(notification) { + NotificationsAndroid.localNotification(notification); + } + cancelAllLocalNotifications() { NotificationsAndroid.cancelAllLocalNotifications(); } diff --git a/app/screens/settings/notification_settings/notification_settings.js b/app/screens/settings/notification_settings/notification_settings.js index d8703d82f8..1961c6a63f 100644 --- a/app/screens/settings/notification_settings/notification_settings.js +++ b/app/screens/settings/notification_settings/notification_settings.js @@ -19,6 +19,7 @@ import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils' import FormattedText from 'app/components/formatted_text'; import {RadioButton, RadioButtonGroup} from 'app/components/radio_button'; import StatusBar from 'app/components/status_bar'; +import NotificationPreferences from 'app/notification_preferences'; import SettingsItem from 'app/screens/settings/settings_item'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -110,21 +111,25 @@ class NotificationSettings extends PureComponent { goToNotificationSettingsMobile = () => { const {currentUser, intl, navigator, theme} = this.props; - navigator.push({ - backButtonTitle: '', - screen: 'NotificationSettingsMobile', - title: intl.formatMessage({id: 'mobile.notification_settings.mobile_title', defaultMessage: 'Mobile Notifications'}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg - }, - passProps: { - currentUser, - onBack: this.saveNotificationProps - } + + NotificationPreferences.getPreferences().then((notificationPreferences) => { + navigator.push({ + backButtonTitle: '', + screen: 'NotificationSettingsMobile', + title: intl.formatMessage({id: 'mobile.notification_settings.mobile_title', defaultMessage: 'Mobile Notifications'}), + animated: true, + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg + }, + passProps: { + currentUser, + onBack: this.saveNotificationProps, + notificationPreferences + } + }); }); }; diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js index 6e2a5ec5e5..ae437d7461 100644 --- a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js +++ b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js @@ -371,8 +371,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, separator: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), - flex: 1, - height: 1 + height: 1, + width: '100%' }, scrollView: { flex: 1, diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js index 3bcf6e4bb5..56efa00cdf 100644 --- a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js +++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js @@ -6,16 +6,18 @@ import {injectIntl} from 'react-intl'; import { Modal, ScrollView, + Text, TouchableOpacity, View } from 'react-native'; import FormattedText from 'app/components/formatted_text'; import {RadioButton, RadioButtonGroup} from 'app/components/radio_button'; +import NotificationPreferences from 'app/notification_preferences'; +import PushNotifications from 'app/push_notifications'; import StatusBar from 'app/components/status_bar'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - import SectionItem from 'app/screens/settings/section_item'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import NotificationSettingsMobileBase from './notification_settings_mobile_base'; @@ -30,6 +32,11 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { this.pushStatus = this.state.push_status; }; + cancelMobileSoundsModal = () => { + this.setState({showMobileSoundsModal: false}); + this.sound = this.state.selectedUri; + }; + onMobilePushChanged = (value) => { this.push = value; }; @@ -38,6 +45,41 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { this.pushStatus = value; }; + onMobileSoundChanged = (value) => { + this.sound = value; + if (value && value !== 'none') { + NotificationPreferences.play(value); + } + }; + + renderMobileBlinkSection(style) { + const {config, theme} = this.props; + const {shouldBlink} = this.state; + + const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none'; + if (!showSection) { + return null; + } + + return ( + + + )} + action={this.toggleBlink} + actionType='toggle' + selected={shouldBlink} + theme={theme} + /> + + + ); + } + renderMobilePushModal(style) { const {config, intl} = this.props; const pushNotificationsEnabled = config.SendPushNotifications === 'true'; @@ -149,7 +191,7 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { @@ -216,6 +258,95 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { ); } + renderMobileSoundsModal(style) { + const {defaultSound, selectedUri} = this.state; + const {intl, notificationPreferences} = this.props; + const {defaultUri, sounds} = notificationPreferences; + const soundsArray = sounds.filter((s) => s.uri !== defaultUri).map((s, i) => { + return ( + + ); + }); + + return ( + + + + + + + + + + + + {soundsArray} + + + + + + + + + + + + + + + + + + + ); + } + renderMobilePushSection() { const {config, theme} = this.props; @@ -306,7 +437,7 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { )} label={( )} @@ -319,6 +450,114 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { ); } + renderMobileSoundSection(style) { + const {config, theme} = this.props; + const {defaultSound, sound} = this.state; + + const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none'; + if (!showSection) { + return null; + } + + let description; + if (sound === 'none') { + description = ( + + ); + } else if (sound) { + description = ( + + {sound} + + ); + } else { + description = ( + + ); + } + + return ( + + + )} + action={this.showMobileSoundsModal} + actionType='default' + theme={theme} + /> + + + ); + } + + renderMobileTestSection(style) { + const {config, theme} = this.props; + + const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none'; + if (!showSection) { + return null; + } + + return ( + + + )} + action={this.sendTestNotification} + actionType='default' + theme={theme} + /> + + + ); + } + + renderMobileVibrateSection(style) { + const {config, theme} = this.props; + const {shouldVibrate} = this.state; + + const showSection = config.SendPushNotifications === 'true' && this.state.push !== 'none'; + if (!showSection) { + return null; + } + + return ( + + + )} + action={this.toggleVibrate} + actionType='toggle' + selected={shouldVibrate} + theme={theme} + /> + + + ); + } + saveMobilePushModal = () => { this.setState({showMobilePushModal: false}); this.setMobilePush(this.push); @@ -329,6 +568,46 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { this.setMobilePushStatus(this.pushStatus); }; + saveMobileSoundsModal = () => { + const {defaultSound} = this.state; + const {intl, notificationPreferences} = this.props; + const {defaultUri, sounds} = notificationPreferences; + + let sound; + let selectedUri = null; + if (this.sound === defaultUri) { + sound = defaultSound; + } else if (this.sound === 'none') { + selectedUri = this.sound; + sound = intl.formatMessage({ + id: 'mobile.notification_settings_mobile.no_sound', + defaultMessage: 'None' + }); + } else { + selectedUri = this.sound; + const selected = sounds.find((s) => s.uri === selectedUri); + sound = selected.name; + } + + NotificationPreferences.setNotificationSound(selectedUri); + this.setState({selectedUri, sound, showMobileSoundsModal: false}); + }; + + sendTestNotification = () => { + const {intl} = this.props; + + PushNotifications.localNotification({ + message: intl.formatMessage({ + id: 'mobile.notification_settings_mobile.test_push', + defaultMessage: 'This is a test push notification' + }), + userInfo: { + localNotification: true, + localTest: true + } + }); + }; + showMobilePushModal = () => { this.setState({showMobilePushModal: true}); }; @@ -337,6 +616,20 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { this.setState({showMobilePushStatusModal: true}); }; + showMobileSoundsModal = () => { + this.setState({showMobileSoundsModal: true}); + }; + + toggleBlink = () => { + NotificationPreferences.setShouldBlink(this.state.shouldBlink); + this.setState({shouldBlink: !this.state.shouldBlink}); + }; + + toggleVibrate = () => { + NotificationPreferences.setShouldVibrate(this.state.shouldVibrate); + this.setState({shouldVibrate: !this.state.shouldVibrate}); + }; + render() { const {theme} = this.props; const style = getStyleSheet(theme); @@ -351,9 +644,14 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { {this.renderMobilePushSection()} {this.renderMobilePushStatusSection(style)} + {this.renderMobileSoundSection(style)} + {this.renderMobileVibrateSection(style)} + {this.renderMobileBlinkSection(style)} + {this.renderMobileTestSection(style)} {this.renderMobilePushModal(style)} {this.renderMobilePushStatusModal(style)} + {this.renderMobileSoundsModal(style)} ); } @@ -372,8 +670,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, separator: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), - flex: 1, - height: 1 + height: 1, + width: '100%' }, scrollView: { flex: 1, @@ -394,6 +692,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { width: '95%' }, modalBody: { + maxHeight: '80%', paddingHorizontal: 24 }, modalTitleContainer: { diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js index b3fa71f284..05df1aab21 100644 --- a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js +++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.ios.js @@ -94,7 +94,7 @@ class NotificationSettingsMobileIos extends NotificationSettingsMobileBase { return (
diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js index 71cdf51b5c..4c6b4d05cc 100644 --- a/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js +++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile_base.js @@ -2,6 +2,7 @@ // See License.txt for license information. import {PureComponent} from 'react'; +import {Platform} from 'react-native'; import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; @@ -11,6 +12,7 @@ export default class NotificationSettingsMobileBase extends PureComponent { currentUser: PropTypes.object.isRequired, intl: intlShape.isRequired, navigator: PropTypes.object, + notificationPreferences: PropTypes.object, onBack: PropTypes.func.isRequired, theme: PropTypes.object.isRequired }; @@ -23,14 +25,49 @@ export default class NotificationSettingsMobileBase extends PureComponent { this.state = { ...notifyProps, + ...this.getNotificationPreferences(props), showMobilePushModal: false, - showMobilePushStatusModal: false + showMobilePushStatusModal: false, + showMobileSoundsModal: false }; this.push = this.state.push; this.pushStatus = this.state.push_status; props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); } + getNotificationPreferences = (props) => { + if (Platform.OS === 'android') { + const { + defaultUri, + shouldBlink, + shouldVibrate, + selectedUri, + sounds + } = props.notificationPreferences; + + const defSound = sounds.find((s) => s.uri === defaultUri); + const defaultSound = defSound.name; + + let sound; + if (selectedUri && selectedUri === 'none') { + sound = 'none'; + } else if (selectedUri) { + const selected = sounds.find((s) => s.uri === selectedUri); + sound = selected.name; + } + + return { + defaultSound, + shouldVibrate, + shouldBlink, + selectedUri, + sound + }; + } + + return {}; + }; + onNavigatorEvent = (event) => { if (event.type === 'ScreenChangedEvent') { switch (event.id) { @@ -50,12 +87,28 @@ export default class NotificationSettingsMobileBase extends PureComponent { }; saveUserNotifyProps = () => { - const props = {...this.state}; + const { + channel, + comments, + desktop, + desktop_duration, + email, + first_name, + mention_keys, + push, + push_status + } = this.state; - Reflect.deleteProperty(props, 'showMobilePushModal'); - Reflect.deleteProperty(props, 'showMobilePushStatusModal'); this.props.onBack({ - ...props, + channel, + comments, + desktop, + desktop_duration, + email, + first_name, + mention_keys, + push, + push_status, user_id: this.props.currentUser.id }); }; diff --git a/app/screens/settings/section_item.js b/app/screens/settings/section_item.js index 960564c13b..629b798542 100644 --- a/app/screens/settings/section_item.js +++ b/app/screens/settings/section_item.js @@ -5,7 +5,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Switch, - TouchableWithoutFeedback, + TouchableOpacity, View } from 'react-native'; import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome'; @@ -83,9 +83,9 @@ function sectionItem(props) { if (actionType === ActionTypes.DEFAULT || actionType === ActionTypes.SELECT || actionType === ActionTypes.ARROW) { return ( - action(actionValue)}> + action(actionValue)}> {component} - + ); } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 5e4cfd14e9..36ed3c8482 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1925,10 +1925,18 @@ "mobile.notification_settings_mentions.keywordsDescription": "Other words that trigger a mention", "mobile.notification_settings_mentions.keywordsHelp": "Keywords are non-case sensitive and should be separated by a comma.", "mobile.notification_settings_mentions.wordsTrigger": "WORDS THAT TRIGGER MENTIONS", + "mobile.notification_settings_mobile.light": "Light", + "mobile.notification_settings_mobile.no_sound": "None", "mobile.notification_settings_mobile.push_activity": "SEND NOTIFICATIONS FOR", "mobile.notification_settings_mobile.push_activity_android": "Send notifications for", - "mobile.notification_settings_model.push_status": "TRIGGER PUSH NOTIFICATIONS WHEN", - "mobile.notification_settings_model.push_status_android": "Trigger push notifications when", + "mobile.notification_settings_mobile.push_status": "TRIGGER PUSH NOTIFICATIONS WHEN", + "mobile.notification_settings_mobile.push_status_android": "Trigger push notifications when", + "mobile.notification_settings_mobile.default_sound": "Default ({sound})", + "mobile.notification_settings_mobile.sound": "Sound", + "mobile.notification_settings_mobile.sounds_title": "Notification sound", + "mobile.notification_settings_mobile.test": "Send me a test notification", + "mobile.notification_settings_mobile.test_push": "This is a test push notification", + "mobile.notification_settings_mobile.vibrate": "Vibrate", "mobile.offlineIndicator.connected": "Connected", "mobile.offlineIndicator.connecting": "Connecting...", "mobile.offlineIndicator.offline": "No internet connection", diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a6c95ee7fd..6583d3d0da 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -417,6 +417,12 @@ platform :android do new_string: 'package com.mattermost.rn;' ) + find_replace_string( + path_to_file: './android/app/src/main/java/com/mattermost/rn/NotificationPreferencesModule.java', + old_string: 'package com.mattermost.rnbeta;', + new_string: 'package com.mattermost.rn;' + ) + find_replace_string( path_to_file: './android/app/src/main/java/com/mattermost/rn/MattermostManagedModule.java', old_string: 'package com.mattermost.rnbeta;', @@ -424,7 +430,7 @@ platform :android do ) find_replace_string( - path_to_file: './android/app/src/main/java/com/mattermost/rn/MattermostManagedPackage.java', + path_to_file: './android/app/src/main/java/com/mattermost/rn/MattermostPackage.java', old_string: 'package com.mattermost.rnbeta;', new_string: 'package com.mattermost.rn;' )