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',
};
});