MM-12879 Fix iOS photo/camera permission denied prompt (#2904)

* Update Github issues link

* Update GH issue to permalink

* Add missing comma

* Fix ios not prompting alert after denied photo or camera permission

* Rename permission var

* Update text

* Change to correct text

* Update text for attachement button

* Update text for image_preview

* Fix test

* Integrate react-native-android-open-settings for storage permission denied

* Move react-native-android-open-settings

* Change all default permission denied message

* Revert "Merge remote-tracking branch 'upstream/master'"

This reverts commit 65187f8f98, reversing
changes made to daca425676.

* Revert "Revert "Merge remote-tracking branch 'upstream/master'""

This reverts commit c82fa628d5.

* Add NSPhotoLibraryAddUsageDescription description

* Fix bad import

* Lazy load react-native-android-open-settings

* Fix indent
This commit is contained in:
Ewe Tek Min
2019-09-11 12:18:53 +08:00
committed by Elias Nahum
parent 076e518b11
commit f6c9c950c5
12 changed files with 210 additions and 102 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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'

View File

@@ -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;

View File

@@ -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(
<AttachmentButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
await wrapper.instance().hasPhotoPermission('camera');
expect(Alert.alert).toBeCalled();
});
});

View File

@@ -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 {
</ProfilePictureButton>
</View>
);
}
};
render() {
const {theme} = this.props;

View File

@@ -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;

View File

@@ -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",

View File

@@ -71,7 +71,9 @@
<key>NSMotionUsageDescription</key>
<string>Share your route in your Mattermost instance</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Upload Photos and Videos to your Mattermost instance or save them to your device</string>
<string>Upload Photos and Videos from your device to your Mattermost instance</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save downloaded Photos and Videos from your Mattermost instance to your device</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Send voice messages to your Mattermost instance</string>
<key>UIAppFonts</key>

5
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -56,6 +56,7 @@ jest.mock('react-native-device-info', () => {
getBuildNumber: () => '0',
getModel: () => 'iPhone X',
isTablet: () => false,
getApplicationName: () => 'Mattermost',
getDeviceLocale: () => 'en-US',
};
});