diff --git a/android/app/build.gradle b/android/app/build.gradle index b891ee993..caf66db82 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -242,6 +242,7 @@ dependencies { implementation project(':react-native-gesture-handler') implementation project(':@react-native-community_async-storage') implementation project(':@react-native-community_netinfo') + implementation project(':react-native-android-open-settings') implementation project(':react-native-haptic-feedback') // For animated GIF support 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 8f8e9edfa..6d16fb9dc 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -1,6 +1,5 @@ package com.mattermost.rnbeta; -import android.app.Activity; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.content.Context; @@ -32,6 +31,7 @@ import com.reactnativedocumentpicker.DocumentPicker; import com.oblador.keychain.KeychainModule; import com.reactnativecommunity.asyncstorage.AsyncStorageModule; import com.reactnativecommunity.netinfo.NetInfoModule; +import com.levelasquez.androidopensettings.AndroidOpenSettings; import com.mkuczera.RNReactNativeHapticFeedbackModule; import com.reactnativecommunity.webview.RNCWebViewPackage; @@ -43,7 +43,6 @@ import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; import com.reactnativenavigation.NavigationApplication; import com.reactnativenavigation.react.NavigationReactNativeHost; import com.reactnativenavigation.react.ReactGateway; -import com.wix.reactnativenotifications.RNNotificationsPackage; import com.wix.reactnativenotifications.core.notification.INotificationsApplication; import com.wix.reactnativenotifications.core.notification.IPushNotification; import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer; @@ -152,6 +151,8 @@ public class MainApplication extends NavigationApplication implements INotificat return new AsyncStorageModule(reactContext); case NetInfoModule.NAME: return new NetInfoModule(reactContext); + case "RNAndroidOpenSettings": + return new AndroidOpenSettings(reactContext); case "RNReactNativeHapticFeedbackModule": return new RNReactNativeHapticFeedbackModule(reactContext); default: @@ -187,6 +188,7 @@ public class MainApplication extends NavigationApplication implements INotificat map.put("RNKeychainManager", new ReactModuleInfo("RNKeychainManager", "com.oblador.keychain.KeychainModule", false, false, true, false, false)); map.put(AsyncStorageModule.NAME, new ReactModuleInfo(AsyncStorageModule.NAME, "com.reactnativecommunity.asyncstorage.AsyncStorageModule", false, false, false, false, false)); map.put(NetInfoModule.NAME, new ReactModuleInfo(NetInfoModule.NAME, "com.reactnativecommunity.netinfo.NetInfoModule", false, false, false, false, false)); + map.put("RNAndroidOpenSettings", new ReactModuleInfo("RNAndroidOpenSettings", "com.levelasquez.androidopensettings.AndroidOpenSettings", false, false, false, false, false)); map.put("RNReactNativeHapticFeedbackModule", new ReactModuleInfo("RNReactNativeHapticFeedback", "com.mkuczera.RNReactNativeHapticFeedbackModule", false, false, false, false, false)); return map; } diff --git a/android/settings.gradle b/android/settings.gradle index 338fdaed6..68f0dd54c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'Mattermost' +include ':react-native-android-open-settings' +project(':react-native-android-open-settings').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-open-settings/android') include ':react-native-haptic-feedback' project(':react-native-haptic-feedback').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-haptic-feedback/android') include ':react-native-gesture-handler' diff --git a/app/components/attachment_button/attachment_button.js b/app/components/attachment_button/attachment_button.js index 07a3257c0..92e5c1af9 100644 --- a/app/components/attachment_button/attachment_button.js +++ b/app/components/attachment_button/attachment_button.js @@ -11,6 +11,8 @@ import { TouchableOpacity, } from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; +import DeviceInfo from 'react-native-device-info'; +import AndroidOpenSettings from 'react-native-android-open-settings'; import Icon from 'react-native-vector-icons/Ionicons'; import {DocumentPicker} from 'react-native-document-picker'; @@ -67,12 +69,93 @@ export default class AttachmentButton extends PureComponent { intl: intlShape.isRequired, }; + getPermissionDeniedMessage = (source, mediaType = '') => { + const {formatMessage} = this.context.intl; + const applicationName = DeviceInfo.getApplicationName(); + switch (source) { + case 'camera': { + if (mediaType === 'video') { + return { + title: formatMessage({ + id: 'mobile.camera_video_permission_denied_title', + defaultMessage: '{applicationName} would like to access your camera', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.camera_video_permission_denied_description', + defaultMessage: 'Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', + }), + }; + } + + return { + title: formatMessage({ + id: 'mobile.camera_photo_permission_denied_title', + defaultMessage: '{applicationName} would like to access your camera', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.camera_photo_permission_denied_description', + defaultMessage: 'Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.', + }), + }; + } + case 'storage': + return { + title: formatMessage({ + id: 'mobile.storage_permission_denied_title', + defaultMessage: '{applicationName} would like to access your files', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.storage_permission_denied_description', + defaultMessage: 'Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.', + }), + }; + case 'video': + return { + title: formatMessage({ + id: 'mobile.android.videos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your videos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.android.videos_permission_denied_description', + defaultMessage: 'Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.', + }), + }; + case 'photo': + default: { + if (Platform.OS === 'android') { + return { + title: formatMessage({ + id: 'mobile.android.photos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your photos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.android.photos_permission_denied_description', + defaultMessage: 'Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.', + }), + }; + } + + return { + title: formatMessage({ + id: 'mobile.ios.photos_permission_denied_title', + defaultMessage: '{applicationName} would like to access your photos', + }, {applicationName}), + text: formatMessage({ + id: 'mobile.ios.photos_permission_denied_description', + defaultMessage: 'Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.', + }), + }; + } + } + } + attachPhotoFromCamera = () => { - return this.attachFileFromCamera('photo', 'camera'); + return this.attachFileFromCamera('camera', 'photo'); }; - attachFileFromCamera = async (mediaType, source) => { + attachFileFromCamera = async (source, mediaType) => { const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('camera', mediaType); const options = { quality: 0.8, videoQuality: 'high', @@ -83,25 +166,19 @@ export default class AttachmentButton extends PureComponent { waitUntilSaved: true, }, permissionDenied: { - title: formatMessage({ - id: 'mobile.android.camera_permission_denied_title', - defaultMessage: 'Camera access is required', - }), - text: formatMessage({ - id: 'mobile.android.camera_permission_denied_description', - defaultMessage: 'To take photos and videos with your camera, please change your permission settings.', - }), + title, + text, reTryTitle: formatMessage({ - id: 'mobile.android.permission_denied_retry', - defaultMessage: 'Set Permission', + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', }), - okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), }, }; - const hasPhotoPermission = await this.hasPhotoPermission(source); + const hasCameraPermission = await this.hasPhotoPermission(source, mediaType); - if (hasPhotoPermission) { + if (hasCameraPermission) { ImagePicker.launchCamera(options, (response) => { if (response.error || response.didCancel) { return; @@ -112,25 +189,20 @@ export default class AttachmentButton extends PureComponent { } }; - attachFileFromLibrary = () => { + attachFileFromLibrary = async () => { const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('photo'); const options = { quality: 0.8, noData: true, permissionDenied: { - title: formatMessage({ - id: 'mobile.android.photos_permission_denied_title', - defaultMessage: 'Photo library access is required', - }), - text: formatMessage({ - id: 'mobile.android.photos_permission_denied_description', - defaultMessage: 'To upload images from your library, please change your permission settings.', - }), + title, + text, reTryTitle: formatMessage({ - id: 'mobile.android.permission_denied_retry', - defaultMessage: 'Set Permission', + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', }), - okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), }, }; @@ -138,39 +210,38 @@ export default class AttachmentButton extends PureComponent { options.mediaType = 'mixed'; } - ImagePicker.launchImageLibrary(options, (response) => { - if (response.error || response.didCancel) { - return; - } + const hasPhotoPermission = await this.hasPhotoPermission('photo'); - this.uploadFiles([response]); - }); + if (hasPhotoPermission) { + ImagePicker.launchImageLibrary(options, (response) => { + if (response.error || response.didCancel) { + return; + } + + this.uploadFiles([response]); + }); + } }; attachVideoFromCamera = () => { - return this.attachFileFromCamera('video', 'camera'); + return this.attachFileFromCamera('camera', 'video'); }; attachVideoFromLibraryAndroid = () => { const {formatMessage} = this.context.intl; + const {title, text} = this.getPermissionDeniedMessage('video'); const options = { videoQuality: 'high', mediaType: 'video', noData: true, permissionDenied: { - title: formatMessage({ - id: 'mobile.android.videos_permission_denied_title', - defaultMessage: 'Video library access is required', - }), - text: formatMessage({ - id: 'mobile.android.videos_permission_denied_description', - defaultMessage: 'To upload videos from your library, please change your permission settings.', - }), + title, + text, reTryTitle: formatMessage({ - id: 'mobile.android.permission_denied_retry', - defaultMessage: 'Set Permission', + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', }), - okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}), + okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}), }, }; @@ -213,15 +284,16 @@ export default class AttachmentButton extends PureComponent { } }; - hasPhotoPermission = async (source) => { + hasPhotoPermission = async (source, mediaType = '') => { if (Platform.OS === 'ios') { const {formatMessage} = this.context.intl; let permissionRequest; - const hasPermissionToStorage = await Permissions.check(source || 'photo'); + const targetSource = source || 'photo'; + const hasPermissionToStorage = await Permissions.check(targetSource); switch (hasPermissionToStorage) { case PermissionTypes.UNDETERMINED: - permissionRequest = await Permissions.request('photo'); + permissionRequest = await Permissions.request(targetSource); if (permissionRequest !== PermissionTypes.AUTHORIZED) { return false; } @@ -232,28 +304,24 @@ export default class AttachmentButton extends PureComponent { if (canOpenSettings) { grantOption = { text: formatMessage({ - id: 'mobile.android.permission_denied_retry', - defaultMessage: 'Set permission', + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', }), onPress: () => Permissions.openSettings(), }; } + const {title, text} = this.getPermissionDeniedMessage(source, mediaType); + Alert.alert( - formatMessage({ - id: 'mobile.android.photos_permission_denied_title', - defaultMessage: 'Photo library access is required', - }), - formatMessage({ - id: 'mobile.android.photos_permission_denied_description', - defaultMessage: 'To upload images from your library, please change your permission settings.', - }), + title, + text, [ grantOption, { text: formatMessage({ - id: 'mobile.android.permission_denied_dismiss', - defaultMessage: 'Dismiss', + id: 'mobile.permission_denied_dismiss', + defaultMessage: 'Don\'t Allow', }), }, ] @@ -280,35 +348,25 @@ export default class AttachmentButton extends PureComponent { } break; case PermissionTypes.DENIED: { - const canOpenSettings = await Permissions.canOpenSettings(); - let grantOption = null; - if (canOpenSettings) { - grantOption = { - text: formatMessage({ - id: 'mobile.android.permission_denied_retry', - defaultMessage: 'Set permission', - }), - onPress: () => Permissions.openSettings(), - }; - } + const {title, text} = this.getPermissionDeniedMessage('storage'); Alert.alert( - formatMessage({ - id: 'mobile.android.storage_permission_denied_title', - defaultMessage: 'File Storage access is required', - }), - formatMessage({ - id: 'mobile.android.storage_permission_denied_description', - defaultMessage: 'To upload images from your Android device, please change your permission settings.', - }), + title, + text, [ { text: formatMessage({ - id: 'mobile.android.permission_denied_dismiss', - defaultMessage: 'Dismiss', + id: 'mobile.permission_denied_dismiss', + defaultMessage: 'Don\'t Allow', }), }, - grantOption, + { + text: formatMessage({ + id: 'mobile.permission_denied_retry', + defaultMessage: 'Settings', + }), + onPress: () => AndroidOpenSettings.appDetailsSettings(), + }, ] ); return false; diff --git a/app/components/attachment_button/attachment_button.test.js b/app/components/attachment_button/attachment_button.test.js index e513d3634..1c0f52128 100644 --- a/app/components/attachment_button/attachment_button.test.js +++ b/app/components/attachment_button/attachment_button.test.js @@ -3,15 +3,25 @@ import React from 'react'; import {shallow} from 'enzyme'; +import Permissions from 'react-native-permissions'; +import {Alert} from 'react-native'; import Preferences from 'mattermost-redux/constants/preferences'; import {VALID_MIME_TYPES} from 'app/screens/edit_profile/edit_profile'; import AttachmentButton from './attachment_button'; +import {PermissionTypes} from 'app/constants'; jest.mock('react-intl'); +jest.mock('Platform', () => { + const Platform = require.requireActual('Platform'); + Platform.OS = 'ios'; + return Platform; +}); + describe('AttachmentButton', () => { + const formatMessage = jest.fn(); const baseProps = { actions: { showModalOverCurrentContext: jest.fn(), @@ -73,4 +83,20 @@ describe('AttachmentButton', () => { expect(props.uploadFiles).toHaveBeenCalled(); }); }); + + test('should show permission denied alert if permission is denied in iOS', async () => { + expect.assertions(1); + + jest.spyOn(Permissions, 'check').mockReturnValue(PermissionTypes.DENIED); + jest.spyOn(Permissions, 'canOpenSettings').mockReturnValue(true); + jest.spyOn(Alert, 'alert').mockReturnValue(true); + + const wrapper = shallow( + , + {context: {intl: {formatMessage}}}, + ); + + await wrapper.instance().hasPhotoPermission('camera'); + expect(Alert.alert).toBeCalled(); + }); }); diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index 75f42530d..fef4bbbea 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -259,7 +259,7 @@ export default class EditProfile extends PureComponent { this.setState({profileImageRemove: true}); this.emitCanUpdateAccount(true); this.props.actions.dismissModal(); - } + }; uploadProfileImage = async () => { const {profileImage} = this.state; @@ -547,7 +547,7 @@ export default class EditProfile extends PureComponent { ); - } + }; render() { const {theme} = this.props; diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index 68fe38aa9..e902a6b77 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -21,6 +21,7 @@ import LinearGradient from 'react-native-linear-gradient'; import {intlShape} from 'react-intl'; import Permissions from 'react-native-permissions'; import Gallery from 'react-native-image-gallery'; +import DeviceInfo from 'react-native-device-info'; import {Navigation} from 'react-native-navigation'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -474,20 +475,24 @@ export default class ImagePreview extends PureComponent { let grantOption = null; if (canOpenSettings) { grantOption = { - text: formatMessage({id: 'mobile.android.permission_denied_retry', defaultMessage: 'Set permission'}), + text: formatMessage({id: 'mobile.permission_denied_retry', defaultMessage: 'Settings'}), onPress: () => Permissions.openSettings(), }; } + const applicationName = DeviceInfo.getApplicationName(); Alert.alert( - formatMessage({id: 'mobile.android.photos_permission_denied_title', defaultMessage: 'Photo library access is required'}), formatMessage({ - id: 'mobile.ios.photos_permission_denied_description', + id: 'mobile.photo_library_permission_denied_title', + defaultMessage: '{applicationName} would like to access your photo library', + }, {applicationName}), + formatMessage({ + id: 'mobile.photo_library_permission_denied_description', defaultMessage: 'To save images and videos to your library, please change your permission settings.', }), [ grantOption, - {text: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})}, + {text: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'})}, ] ); return; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 6641d7481..310d9b1a4 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -138,16 +138,10 @@ "mobile.advanced_settings.timezone": "Timezone", "mobile.advanced_settings.title": "Advanced Settings", "mobile.alert_dialog.alertCancel": "Cancel", - "mobile.android.camera_permission_denied_description": "To take photos and videos with your camera, please change your permission settings.", - "mobile.android.camera_permission_denied_title": "Camera access is required", - "mobile.android.permission_denied_dismiss": "Dismiss", - "mobile.android.permission_denied_retry": "Set permission", - "mobile.android.photos_permission_denied_description": "To upload images from your library, please change your permission settings.", - "mobile.android.photos_permission_denied_title": "Photo library access is required", - "mobile.android.storage_permission_denied_description": "To upload images from your Android device, please change your permission settings.", - "mobile.android.storage_permission_denied_title": "File Storage access is required", - "mobile.android.videos_permission_denied_description": "To upload videos from your library, please change your permission settings.", - "mobile.android.videos_permission_denied_title": "Video library access is required", + "mobile.android.photos_permission_denied_description": "Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.", + "mobile.android.photos_permission_denied_title": "{applicationName} would like to access your photos", + "mobile.android.videos_permission_denied_description": "Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.", + "mobile.android.videos_permission_denied_title": "{applicationName} would like to access your videos", "mobile.announcement_banner.title": "Announcement", "mobile.authentication_error.message": "Mattermost has encountered an error. Please re-authenticate to start a new session.", "mobile.authentication_error.title": "Authentication Error", @@ -155,6 +149,10 @@ "mobile.calendar.dayNamesShort": "Sun,Mon,Tue,Wed,Thu,Fri,Sat", "mobile.calendar.monthNames": "January,February,March,April,May,June,July,August,September,October,November,December", "mobile.calendar.monthNamesShort": "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + "mobile.camera_photo_permission_denied_description": "Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.", + "mobile.camera_photo_permission_denied_title": "{applicationName} would like to access your camera", + "mobile.camera_video_permission_denied_description": "Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.", + "mobile.camera_video_permission_denied_title": "{applicationName} would like to access your camera", "mobile.channel_drawer.search": "Jump to...", "mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to archive the {term} {name}?", "mobile.channel_info.alertMessageLeaveChannel": "Are you sure you want to leave the {term} {name}?", @@ -266,7 +264,8 @@ "mobile.intro_messages.default_message": "This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.", "mobile.intro_messages.default_welcome": "Welcome to {name}!", "mobile.intro_messages.DM": "This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.", - "mobile.ios.photos_permission_denied_description": "To save images and videos to your library, please change your permission settings.", + "mobile.ios.photos_permission_denied_description": "Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.", + "mobile.ios.photos_permission_denied_title": "{applicationName} would like to access your photos", "mobile.join_channel.error": "We couldn't join the channel {displayName}. Please check your connection and try again.", "mobile.loading_channels": "Loading Channels...", "mobile.loading_members": "Loading Members...", @@ -342,6 +341,10 @@ "mobile.open_dm.error": "We couldn't open a direct message with {displayName}. Please check your connection and try again.", "mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.", "mobile.open_unknown_channel.error": "Unable to join the channel. Please reset the cache and try again.", + "mobile.permission_denied_dismiss": "Don't Allow", + "mobile.permission_denied_retry": "Settings", + "mobile.photo_library_permission_denied_description": "To save images and videos to your library, please change your permission settings.", + "mobile.photo_library_permission_denied_title": "{applicationName} would like to access your photo library", "mobile.pinned_posts.empty_description": "Pin important items by holding down on any message and selecting \"Pin to Channel\".", "mobile.pinned_posts.empty_title": "No Pinned Posts", "mobile.post_info.add_reaction": "Add Reaction", @@ -440,6 +443,8 @@ "mobile.share_extension.error_message": "An error has occurred while using the share extension.", "mobile.share_extension.error_title": "Extension Error", "mobile.share_extension.team": "Team", + "mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.", + "mobile.storage_permission_denied_title": "{applicationName} would like to access your files", "mobile.sidebar_settings.permanent": "Permanent Sidebar", "mobile.sidebar_settings.permanent_description": "Keep the sidebar open permanently", "mobile.suggestion.members": "Members", diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 37060e443..60705c9ed 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -71,7 +71,9 @@ NSMotionUsageDescription Share your route in your Mattermost instance NSPhotoLibraryUsageDescription - Upload Photos and Videos to your Mattermost instance or save them to your device + Upload Photos and Videos from your device to your Mattermost instance + NSPhotoLibraryAddUsageDescription + Save downloaded Photos and Videos from your Mattermost instance to your device NSSpeechRecognitionUsageDescription Send voice messages to your Mattermost instance UIAppFonts diff --git a/package-lock.json b/package-lock.json index 055ea2669..2677d281e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16454,6 +16454,11 @@ } } }, + "react-native-android-open-settings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-native-android-open-settings/-/react-native-android-open-settings-1.3.0.tgz", + "integrity": "sha512-h4FTWRtTLRVNS7RK4Bg2gAXe8G5bFZL8Jtmfk2rG2a/N/wJR+v1rAY2BxRkC48lQTqwQCIAQRbWgLVkJ8XmaLQ==" + }, "react-native-animatable": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.2.tgz", diff --git a/package.json b/package.json index fc9b5718e..98e78668c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react": "16.8.6", "react-intl": "2.8.0", "react-native": "0.59.9", + "react-native-android-open-settings": "1.3.0", "react-native-animatable": "1.3.2", "react-native-button": "2.4.0", "react-native-calendars": "github:mattermost/react-native-calendars#4937ec5a3bf7e86f9f35fcd85eb4aa6133f45b58", diff --git a/test/setup.js b/test/setup.js index aa6c4a227..795986636 100644 --- a/test/setup.js +++ b/test/setup.js @@ -56,6 +56,7 @@ jest.mock('react-native-device-info', () => { getBuildNumber: () => '0', getModel: () => 'iPhone X', isTablet: () => false, + getApplicationName: () => 'Mattermost', getDeviceLocale: () => 'en-US', }; });