Update dependencies and setup project with node 16 / npm 7 (#5474)

This commit is contained in:
Elias Nahum 2021-06-21 18:36:38 -04:00 committed by GitHub
parent 683c6f5df7
commit f4f8379796
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 65792 additions and 10821 deletions

View file

@ -1,7 +1,7 @@
version: 2.1
orbs:
owasp: entur/owasp@0.0.10
node: circleci/node@4.4.0
node: circleci/node@4.5.1
executors:
android:
@ -92,14 +92,15 @@ commands:
npm-dependencies:
description: "Get JavaScript dependencies"
steps:
- node/install-npm:
version: '6.14.11'
- node/install:
node-version: '16.2.0'
install-npm: false
- restore_cache:
name: Restore npm cache
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
- run:
name: Getting JavaScript dependencies
command: NODE_ENV=development npm install --ignore-scripts
command: NODE_ENV=development npm ci --ignore-scripts
- save_cache:
name: Save npm cache
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}

View file

@ -0,0 +1,21 @@
package com.mattermost.rnbeta;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import java.util.Collections;
import java.util.List;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import com.ammarahmed.mmkv.RNMMKVModule;
public class CustomMMKVJSIModulePackage extends ReanimatedJSIModulePackage {
@Override
public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {
super.getJSIModules(reactApplicationContext, jsContext);
reactApplicationContext.getNativeModule(RNMMKVModule.class).installLib(jsContext, reactApplicationContext.getFilesDir().getAbsolutePath() + "/mmkv");
return Collections.emptyList();
}
}

View file

@ -40,7 +40,6 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.soloader.SoLoader;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
public class MainApplication extends NavigationApplication implements INotificationsApplication, INotificationsDrawerApplication {
@ -118,7 +117,7 @@ private final ReactNativeHost mReactNativeHost =
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
return new CustomMMKVJSIModulePackage();
}
};

View file

@ -83,7 +83,7 @@ exports[`QuickActions should match snapshot 1`] = `
}
value=""
/>
<FileQuickAction
<InjectIntl(FileQuickAction)
disabled={false}
fileCount={1}
maxFileCount={5}
@ -119,7 +119,7 @@ exports[`QuickActions should match snapshot 1`] = `
}
}
/>
<ImageQuickAction
<InjectIntl(ImageQuickAction)
disabled={false}
fileCount={1}
maxFileCount={5}
@ -155,7 +155,7 @@ exports[`QuickActions should match snapshot 1`] = `
}
}
/>
<CameraQuickAction
<InjectIntl(CameraQuickAction)
disabled={false}
fileCount={1}
maxFileCount={5}

View file

@ -1,22 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CameraButton should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"justifyContent": "center",
"opacity": 1,
"padding": 10,
}
}
testID="post_draft.quick_actions.camera_action"
type="opacity"
>
<CompassIcon
<Icon
color="rgba(61,60,64,0.64)"
name="camera-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
</View>
`;

View file

@ -1,12 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {fireEvent} from '@testing-library/react-native';
import React from 'react';
import {Alert, Platform, StatusBar} from 'react-native';
import {Alert} from 'react-native';
import Permissions from 'react-native-permissions';
import * as Navigation from '@actions/navigation';
import Preferences from '@mm-redux/constants/preferences';
import {shallowWithIntl} from 'test/intl-test-helper';
import {renderWithIntl} from 'test/testing_library';
import CameraQuickAction from './index';
@ -16,96 +18,30 @@ jest.mock('react-native-image-picker', () => ({
describe('CameraButton', () => {
const baseProps = {
disabled: false,
testID: 'post_draft.quick_actions.camera_action',
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
onUploadFiles: jest.fn(),
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<CameraQuickAction {...baseProps}/>);
const wrapper = renderWithIntl(<CameraQuickAction {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.toJSON()).toMatchSnapshot();
});
test('should return permission false if permission is denied in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
const showModalOverCurrentContext = jest.spyOn(Navigation, 'showModalOverCurrentContext');
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<CameraQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasCameraPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(showModalOverCurrentContext).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallowWithIntl(
<CameraQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasCameraPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('hasCameraPermission returns true when permission has been granted', async () => {
const platformPermissions = [{
platform: 'ios',
permission: Permissions.PERMISSIONS.IOS.CAMERA,
}, {
platform: 'android',
permission: Permissions.PERMISSIONS.ANDROID.CAMERA,
}];
for (let i = 0; i < platformPermissions.length; i++) {
const {platform, permission} = platformPermissions[i];
Platform.OS = platform;
const check = jest.spyOn(Permissions, 'check');
const request = jest.spyOn(Permissions, 'request');
request.mockReturnValue(Permissions.RESULTS.GRANTED);
const wrapper = shallowWithIntl(
<CameraQuickAction {...baseProps}/>,
);
const instance = wrapper.instance();
check.mockReturnValueOnce(Permissions.RESULTS.DENIED);
let hasPermission = await instance.hasCameraPermission(); // eslint-disable-line no-await-in-loop
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
check.mockReturnValueOnce(Permissions.RESULTS.UNAVAILABLE);
hasPermission = await instance.hasCameraPermission(); // eslint-disable-line no-await-in-loop
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
}
});
test('should re-enable StatusBar after ImagePicker launchCamera finishes', async () => {
const wrapper = shallowWithIntl(
<CameraQuickAction {...baseProps}/>,
);
const instance = wrapper.instance();
jest.spyOn(instance, 'hasCameraPermission').mockReturnValue(true);
jest.spyOn(StatusBar, 'setHidden');
await instance.attachFileFromCamera();
expect(StatusBar.setHidden).toHaveBeenCalledWith(false);
});
});

View file

@ -0,0 +1,129 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {View} from 'react-native';
import {CameraOptions} from 'react-native-image-picker';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {makeStyleSheetFromTheme} from '@utils/theme';
import type {Theme} from '@mm-redux/types/preferences';
type Props = {
onPress: (options: CameraOptions) => void;
theme: Theme;
}
const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
center: {
alignItems: 'center',
},
container: {
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
height: 200,
paddingVertical: 10,
},
flex: {
flex: 1,
},
options: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
width: '100%',
marginBottom: 20,
},
optionContainer: {
alignItems: 'flex-start',
},
title: {
color: theme.centerChannelColor,
fontSize: 18,
fontWeight: 'bold',
},
text: {
color: theme.centerChannelColor,
fontSize: 15,
},
}));
const CameraType = ({onPress, theme}: Props) => {
const style = getStyle(theme);
const onPhoto = () => {
const options: CameraOptions = {
quality: 0.8,
mediaType: 'photo',
saveToPhotos: true,
};
onPress(options);
};
const onVideo = () => {
const options: CameraOptions = {
videoQuality: 'high',
mediaType: 'video',
saveToPhotos: true,
};
onPress(options);
};
return (
<View style={style.container}>
<FormattedText
id='camera_type.title'
defaultMessage='Choose an action'
style={style.title}
/>
<View style={style.options}>
<View style={style.flex}>
<TouchableWithFeedback
onPress={onPhoto}
testID='camera_type.photo'
>
<View style={style.center}>
<CompassIcon
color={theme.centerChannelColor}
name='camera-outline'
size={38}
/>
<FormattedText
id='camera_type.photo.option'
defaultMessage='Capture Photo'
style={style.text}
/>
</View>
</TouchableWithFeedback>
</View>
<View style={style.flex}>
<TouchableWithFeedback
onPress={onVideo}
testID='camera_type.video'
>
<View style={style.center}>
<CompassIcon
color={theme.centerChannelColor}
name='video-outline'
size={38}
/>
<FormattedText
id='camera_type.video.option'
defaultMessage='Record Video'
style={style.text}
/>
</View>
</TouchableWithFeedback>
</View>
</View>
</View>
);
};
export default CameraType;

View file

@ -1,183 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
Alert,
Platform,
StatusBar,
StyleSheet,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {changeOpacity} from '@utils/theme';
export default class CameraQuickAction extends PureComponent {
static propTypes = {
testID: PropTypes.string,
disabled: PropTypes.bool,
fileCount: PropTypes.number,
maxFileCount: PropTypes.number,
onUploadFiles: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
attachFileFromCamera = async () => {
const {formatMessage} = this.context.intl;
const {title, text} = this.getPermissionDeniedMessage();
const options = {
quality: 0.8,
videoQuality: 'high',
noData: true,
mediaType: 'mixed',
storageOptions: {
cameraRoll: true,
waitUntilSaved: true,
},
permissionDenied: {
title,
text,
reTryTitle: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
},
};
const hasCameraPermission = await this.hasCameraPermission();
if (hasCameraPermission) {
ImagePicker.launchCamera(options, (response) => {
StatusBar.setHidden(false);
if (response.error || response.didCancel) {
return;
}
this.props.onUploadFiles([response]);
});
}
};
getPermissionDeniedMessage = () => {
const {formatMessage} = this.context.intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
}
handleButtonPress = () => {
const {
fileCount,
maxFileCount,
} = this.props;
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
this.attachFileFromCamera();
};
hasCameraPermission = async () => {
const {formatMessage} = this.context.intl;
const targetSource = Platform.OS === 'ios' ?
Permissions.PERMISSIONS.IOS.CAMERA :
Permissions.PERMISSIONS.ANDROID.CAMERA;
const hasPermission = await Permissions.check(targetSource);
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(targetSource);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = this.getPermissionDeniedMessage();
Alert.alert(
title,
text,
[
grantOption,
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
],
);
return false;
}
}
return true;
};
render() {
const {testID, disabled, theme} = this.props;
const actionTestID = disabled ?
`${testID}.disabled` :
testID;
const color = disabled ?
changeOpacity(theme.centerChannelColor, 0.16) :
changeOpacity(theme.centerChannelColor, 0.64);
return (
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={this.handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='camera-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
);
}
}
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});

View file

@ -0,0 +1,93 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {injectIntl} from 'react-intl';
import {Dimensions, StatusBar, StyleSheet} from 'react-native';
import {launchCamera, CameraOptions} from 'react-native-image-picker';
import {showModalOverCurrentContext} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {hasCameraPermission} from '@utils/permission';
import {changeOpacity} from '@utils/theme';
import type {QuickActionAttachmentProps} from '@typings/components/post_draft_quick_action';
import CameraType from './camera_type';
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});
const CameraQuickAction = ({disabled, fileCount, intl, maxFileCount, onUploadFiles, testID, theme}: QuickActionAttachmentProps) => {
const attachFileFromCamera = async (options: CameraOptions) => {
EventEmitter.emit(NavigationTypes.CLOSE_SLIDE_UP);
const hasPermission = await hasCameraPermission(intl);
if (hasPermission) {
launchCamera(options, (response) => {
StatusBar.setHidden(false);
if (response.errorCode || response.didCancel) {
return;
}
onUploadFiles(response.assets);
});
}
};
const handleButtonPress = useCallback(() => {
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
const passProps = {
allowStayMiddle: false,
children: (
<CameraType
onPress={attachFileFromCamera}
theme={theme}
/>
),
marginFromTop: Dimensions.get('window').height - 210,
theme,
};
showModalOverCurrentContext('SlideUp', passProps);
}, [fileCount, maxFileCount]);
const actionTestID = disabled ? `${testID}.disabled` : testID;
const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64);
return (
<>
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='camera-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
</>
);
};
export default injectIntl(CameraQuickAction);

View file

@ -1,22 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FileQuickAction should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"justifyContent": "center",
"opacity": 1,
"padding": 10,
}
}
testID="post_draft.quick_actions.file_action"
type="opacity"
>
<CompassIcon
<Icon
color="rgba(61,60,64,0.64)"
name="file-document-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
</View>
`;

View file

@ -1,21 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {fireEvent} from '@testing-library/react-native';
import React from 'react';
import {Alert, Platform} from 'react-native';
import Permissions from 'react-native-permissions';
import Preferences from '@mm-redux/constants/preferences';
import {shallowWithIntl} from 'test/intl-test-helper';
import {renderWithIntl} from 'test/testing_library';
import FileQuickAction from './index';
describe('FileQuickAction', () => {
const baseProps = {
disabled: false,
testID: 'post_draft.quick_actions.file_action',
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
onUploadFiles: jest.fn(),
};
@ -29,70 +30,66 @@ describe('FileQuickAction', () => {
});
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<FileQuickAction {...baseProps}/>);
const wrapper = renderWithIntl(<FileQuickAction {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.toJSON()).toMatchSnapshot();
});
test('should return permission false if permission is denied in Android', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<FileQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasStoragePermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(Permissions.check).toHaveBeenCalled();
await expect(Permissions.request).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in Android', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Permissions, 'request');
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<FileQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasStoragePermission();
expect(Permissions.check).toHaveBeenCalled();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('hasStoragePermission returns true when permission has been granted', async () => {
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<FileQuickAction {...baseProps}/>,
);
const instance = wrapper.instance();
const check = jest.spyOn(Permissions, 'check');
const request = jest.spyOn(Permissions, 'request');
// On iOS storage permissions are not checked
Platform.OS = 'ios';
let hasPermission = await instance.hasStoragePermission();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
expect(check).not.toHaveBeenCalled();
expect(request).not.toHaveBeenCalled();
expect(hasPermission).toBe(true);
Platform.OS = 'android';
request.mockReturnValue(Permissions.RESULTS.GRANTED);
const permission = Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
check.mockReturnValueOnce(Permissions.RESULTS.DENIED);
hasPermission = await instance.hasStoragePermission();
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(check).toHaveBeenCalledWith(permission);
await expect(request).toHaveBeenCalled();
check.mockReturnValueOnce(Permissions.RESULTS.UNAVAILABLE);
hasPermission = await instance.hasStoragePermission();
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(check).toHaveBeenCalledWith(permission);
await expect(request).toHaveBeenCalled();
});
});

View file

@ -1,175 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {Alert, NativeModules, Platform, StyleSheet} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import AndroidOpenSettings from 'react-native-android-open-settings';
import DocumentPicker from 'react-native-document-picker';
import Permissions from 'react-native-permissions';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {changeOpacity} from '@utils/theme';
const ShareExtension = NativeModules.MattermostShare;
export default class FileQuickAction extends PureComponent {
static propTypes = {
testID: PropTypes.string,
disabled: PropTypes.bool,
fileCount: PropTypes.number,
maxFileCount: PropTypes.number,
onUploadFiles: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
static defaultProps = {
fileCount: 0,
maxFileCount: 5,
};
static contextTypes = {
intl: intlShape.isRequired,
};
getPermissionDeniedMessage = () => {
const {formatMessage} = this.context.intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
}
attachFileFromFiles = async () => {
const hasPermission = await this.hasStoragePermission();
const browseFileTypes = Platform.OS === 'ios' ? 'public.item' : '*/*';
if (hasPermission) {
try {
const res = await DocumentPicker.pick({type: [browseFileTypes]});
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud
const newUri = await ShareExtension.getFilePath(res.uri);
if (newUri.filePath) {
res.uri = newUri.filePath;
} else {
return;
}
}
if (Platform.OS === 'ios') {
// Decode file uri to get the actual path
res.uri = decodeURIComponent(res.uri);
}
this.props.onUploadFiles([res]);
} catch (error) {
// Do nothing
}
}
};
hasStoragePermission = async () => {
if (Platform.OS === 'android') {
const {formatMessage} = this.context.intl;
const storagePermission = Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const hasPermissionToStorage = await Permissions.check(storagePermission);
switch (hasPermissionToStorage) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(storagePermission);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const {title, text} = this.getPermissionDeniedMessage();
Alert.alert(
title,
text,
[
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
{
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => AndroidOpenSettings.appDetailsSettings(),
},
],
);
return false;
}
}
}
return true;
};
handleButtonPress = () => {
const {
fileCount,
maxFileCount,
} = this.props;
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
this.attachFileFromFiles();
};
render() {
const {testID, disabled, theme} = this.props;
const actionTestID = disabled ?
`${testID}.disabled` :
testID;
const color = disabled ?
changeOpacity(theme.centerChannelColor, 0.16) :
changeOpacity(theme.centerChannelColor, 0.64);
return (
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={this.handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='file-document-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
);
}
}
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});

View file

@ -0,0 +1,92 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {injectIntl} from 'react-intl';
import {NativeModules, Platform, StyleSheet} from 'react-native';
import DocumentPicker from 'react-native-document-picker';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import {ICON_SIZE, MAX_FILE_COUNT, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {hasStoragePermission} from '@utils/permission';
import {changeOpacity} from '@utils/theme';
import type {QuickActionAttachmentProps} from '@typings/components/post_draft_quick_action';
const ShareExtension = NativeModules.MattermostShare;
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});
const FileQuickAction = ({disabled, fileCount = 0, intl, maxFileCount = MAX_FILE_COUNT, onUploadFiles, testID = '', theme}: QuickActionAttachmentProps) => {
const attachFileFromFiles = async () => {
const hasPermission = await hasStoragePermission(intl);
const browseFileTypes = DocumentPicker.types.allFiles;
if (hasPermission) {
try {
const res = await DocumentPicker.pickMultiple({type: [browseFileTypes]});
const files = [];
for await (const file of res) {
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud
const newUri = await ShareExtension.getFilePath(file.uri);
if (newUri) {
files.push({uri: newUri.filePath});
}
} else {
// Decode file uri to get the actual path
files.push({uri: decodeURIComponent(file.uri)});
}
}
if ((fileCount + files.length) > maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
onUploadFiles(files);
} catch (error) {
// Do nothing
}
}
};
const handleButtonPress = useCallback(() => {
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
attachFileFromFiles();
}, [fileCount, maxFileCount]);
const actionTestID = disabled ? `${testID}.disabled` : testID;
const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64);
return (
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='file-document-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
);
};
export default injectIntl(FileQuickAction);

View file

@ -1,22 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ImageQuickAction should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"justifyContent": "center",
"opacity": 1,
"padding": 10,
}
}
testID="post_draft.quick_actions.image_action"
type="opacity"
>
<CompassIcon
<Icon
color="rgba(61,60,64,0.64)"
name="image-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
</View>
`;

View file

@ -4,9 +4,11 @@
import React from 'react';
import {Alert, Platform, StatusBar} from 'react-native';
import Permissions from 'react-native-permissions';
import {fireEvent} from '@testing-library/react-native';
import Preferences from '@mm-redux/constants/preferences';
import {shallowWithIntl} from 'test/intl-test-helper';
import * as PermissionUtils from '@utils/permission';
import {renderWithIntl} from 'test/testing_library';
import ImageQuickAction from './index';
@ -16,48 +18,46 @@ jest.mock('react-native-image-picker', () => ({
describe('ImageQuickAction', () => {
const baseProps = {
disabled: false,
testID: 'post_draft.quick_actions.image_action',
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
onUploadFiles: jest.fn(),
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<ImageQuickAction {...baseProps}/>);
const wrapper = renderWithIntl(<ImageQuickAction {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.toJSON()).toMatchSnapshot();
});
test('should return permission false if permission is denied in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<ImageQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasPhotoPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(Permissions.check).toHaveBeenCalled();
await expect(Permissions.request).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<ImageQuickAction {...baseProps}/>,
);
const hasPermission = await wrapper.instance().hasPhotoPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(Permissions.check).toHaveBeenCalled();
await expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('hasPhotoPermission returns true when permission has been granted', async () => {
@ -77,35 +77,34 @@ describe('ImageQuickAction', () => {
const request = jest.spyOn(Permissions, 'request');
request.mockReturnValue(Permissions.RESULTS.GRANTED);
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<ImageQuickAction {...baseProps}/>,
);
const instance = wrapper.instance();
check.mockReturnValueOnce(Permissions.RESULTS.DENIED);
let hasPermission = await instance.hasPhotoPermission(); // eslint-disable-line no-await-in-loop
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(check).toHaveBeenCalledWith(permission); // eslint-disable-line no-await-in-loop
await expect(request).toHaveBeenCalled(); // eslint-disable-line no-await-in-loop
check.mockReturnValueOnce(Permissions.RESULTS.UNAVAILABLE);
hasPermission = await instance.hasPhotoPermission(); // eslint-disable-line no-await-in-loop
expect(check).toHaveBeenCalledWith(permission);
expect(request).toHaveBeenCalled();
expect(hasPermission).toBe(true);
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(check).toHaveBeenCalledWith(permission); // eslint-disable-line no-await-in-loop
await expect(request).toHaveBeenCalled(); // eslint-disable-line no-await-in-loop
}
});
test('should re-enable StatusBar after ImagePicker launchImageLibrary finishes', async () => {
const wrapper = shallowWithIntl(
const wrapper = renderWithIntl(
<ImageQuickAction {...baseProps}/>,
);
const instance = wrapper.instance();
jest.spyOn(instance, 'hasPhotoPermission').mockReturnValue(true);
jest.spyOn(StatusBar, 'setHidden');
fireEvent.press(wrapper.getByTestId(baseProps.testID));
const check = jest.spyOn(Permissions, 'check');
jest.spyOn(PermissionUtils, 'hasPhotoPermission').mockReturnValue(true);
const setHidden = jest.spyOn(StatusBar, 'setHidden');
await instance.attachFileFromLibrary();
expect(StatusBar.setHidden).toHaveBeenCalledWith(false);
fireEvent.press(wrapper.getByTestId(baseProps.testID));
await expect(check).toHaveBeenCalled();
await expect(setHidden).toHaveBeenCalledWith(false);
});
});

View file

@ -1,190 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {Alert, Platform, StatusBar, StyleSheet} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import {changeOpacity} from '@utils/theme';
export default class ImageQuickAction extends PureComponent {
static propTypes = {
testID: PropTypes.string,
disabled: PropTypes.bool,
fileCount: PropTypes.number,
maxFileCount: PropTypes.number,
onUploadFiles: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
static defaultProps = {
fileCount: 0,
maxFileCount: 5,
};
static contextTypes = {
intl: intlShape.isRequired,
};
attachFileFromLibrary = async () => {
const {formatMessage} = this.context.intl;
const {title, text} = this.getPermissionDeniedMessage();
const options = {
quality: 0.8,
mediaType: 'mixed',
noData: true,
permissionDenied: {
title,
text,
reTryTitle: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
},
};
const hasPhotoPermission = await this.hasPhotoPermission();
if (hasPhotoPermission) {
ImagePicker.launchImageLibrary(options, (response) => {
StatusBar.setHidden(false);
if (response.error || response.didCancel) {
return;
}
this.props.onUploadFiles([response]);
});
}
};
getPermissionDeniedMessage = () => {
const {formatMessage} = this.context.intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
};
handleButtonPress = () => {
const {
fileCount,
maxFileCount,
} = this.props;
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
this.attachFileFromLibrary();
};
hasPhotoPermission = async () => {
const {formatMessage} = this.context.intl;
const targetSource = Platform.OS === 'ios' ?
Permissions.PERMISSIONS.IOS.PHOTO_LIBRARY :
Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const hasPermission = await Permissions.check(targetSource);
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(targetSource);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = this.getPermissionDeniedMessage();
Alert.alert(
title,
text,
[
grantOption,
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
],
);
return false;
}
}
return true;
};
render() {
const {testID, disabled, theme} = this.props;
const actionTestID = disabled ?
`${testID}.disabled` :
testID;
const color = disabled ?
changeOpacity(theme.centerChannelColor, 0.16) :
changeOpacity(theme.centerChannelColor, 0.64);
return (
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={this.handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='image-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
);
}
}
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});

View file

@ -0,0 +1,111 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {injectIntl} from 'react-intl';
import {NativeModules, Platform, StatusBar, StyleSheet} from 'react-native';
import {launchImageLibrary, ImageLibraryOptions} from 'react-native-image-picker';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {NavigationTypes} from '@constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {ICON_SIZE, MAX_FILE_COUNT, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
import {lookupMimeType} from '@utils/file';
import {hasPhotoPermission} from '@utils/permission';
import {changeOpacity} from '@utils/theme';
import type {QuickActionAttachmentProps} from '@typings/components/post_draft_quick_action';
const ShareExtension = NativeModules.MattermostShare;
const style = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
},
});
const ImageQuickAction = ({disabled, fileCount = 0, intl, maxFileCount = MAX_FILE_COUNT, onUploadFiles, testID = '', theme}: QuickActionAttachmentProps) => {
const attachFileFromLibrary = async () => {
const selectionLimit = maxFileCount - fileCount;
const options: ImageLibraryOptions = {
selectionLimit,
quality: 0.8,
mediaType: 'mixed',
includeBase64: false,
};
const hasPermission = await hasPhotoPermission(intl);
if (hasPermission) {
launchImageLibrary(options, async (response) => {
StatusBar.setHidden(false);
if (response.errorCode || response.didCancel) {
return;
}
if (response.assets.length > selectionLimit) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
const files = [];
for await (const file of response.assets) {
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud
const uri = (await ShareExtension.getFilePath(file.uri)).filePath;
const type = file.type || lookupMimeType(uri);
let fileName = file.fileName;
if (type.includes('video/')) {
fileName = uri.split('\\').pop().split('/').pop();
}
if (uri) {
files.push({...file, fileName, uri, type});
}
} else {
// Decode file uri to get the actual path
files.push(file);
}
}
onUploadFiles(files);
});
}
};
const handleButtonPress = useCallback(() => {
if (fileCount === maxFileCount) {
EventEmitter.emit(MAX_FILE_COUNT_WARNING);
return;
}
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
attachFileFromLibrary();
}, [fileCount, maxFileCount]);
const actionTestID = disabled ? `${testID}.disabled` : testID;
const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64);
return (
<TouchableWithFeedback
testID={actionTestID}
disabled={disabled}
onPress={handleButtonPress}
style={style.icon}
type={'opacity'}
>
<CompassIcon
color={color}
name='image-outline'
size={ICON_SIZE}
/>
</TouchableWithFeedback>
);
};
export default injectIntl(ImageQuickAction);

View file

@ -246,6 +246,7 @@ const PostList = ({
return () => {
EventEmitter.off('scroll-to-bottom', scrollToBottom);
EventEmitter.off(NavigationTypes.NAVIGATION_DISMISS_AND_POP_TO_ROOT, closePermalink);
};
}, []);

View file

@ -16,6 +16,7 @@ const NavigationTypes = keyMirror({
MAIN_SIDEBAR_DID_OPEN: null,
CLOSE_SETTINGS_SIDEBAR: null,
BLUR_POST_DRAFT: null,
CLOSE_SLIDE_UP: null,
});
export default NavigationTypes;

View file

@ -206,6 +206,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case 'UserProfile':
screen = require('@screens/user_profile').default;
break;
case 'SlideUp':
screen = require('@screens/slide_up').default;
break;
}
if (screen) {

View file

@ -11,6 +11,7 @@ import Permalink from './permalink.js';
describe('Permalink', () => {
const actions = {
addUserToTeam: jest.fn(),
closePermalink: jest.fn(),
getPostsAround: jest.fn(),
getPostThread: jest.fn(),
getChannel: jest.fn(),

View file

@ -0,0 +1,63 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement, useCallback, useEffect, useRef} from 'react';
import {StyleSheet, View} from 'react-native';
import {NavigationTypes} from '@constants';
import SlideUpPanel from '@components/slide_up_panel';
import EventEmitter from '@mm-redux/utils/event_emitter';
import type {Theme} from '@mm-redux/types/preferences';
import {dismissModal} from '@actions/navigation';
type Props = {
allowStayMiddle?: boolean;
containerHeight?: number;
children: ReactElement;
header?: () => ReactElement;
headerHeight?: number;
initialPosition?: number;
marginFromTop?: number;
onRequestClose?: () => void;
theme: Theme;
}
type SlideUpPanelRef = {
closeWithAnimation: () => void;
}
const style = StyleSheet.create({
flex: {flex: 1},
});
const SlideUp = (props: Props) => {
const {children, ...otherProps} = props;
const slideUpPanel = useRef<SlideUpPanelRef>();
const onClose = useCallback(() => {
slideUpPanel.current?.closeWithAnimation();
dismissModal();
}, []);
useEffect(() => {
EventEmitter.on(NavigationTypes.CLOSE_SLIDE_UP, onClose);
return () => EventEmitter.off(NavigationTypes.CLOSE_SLIDE_UP, onClose);
}, []);
return (
<View style={style.flex}>
<SlideUpPanel
{...otherProps}
onRequestClose={onClose}
// @ts-expect-error no ref typing for JS file
ref={slideUpPanel}
>
{children}
</SlideUpPanel>
</View>
);
};
export default SlideUp;

View file

@ -91,7 +91,9 @@ export default async function getStorage(identifier = 'default') {
callback(null);
}
return MMKV.removeItem(key);
return new Promise(() => {
return MMKV.removeItem(key);
});
},
};
}

195
app/utils/permission.ts Normal file
View file

@ -0,0 +1,195 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {intlShape} from 'react-intl';
import {Alert, Platform} from 'react-native';
import AndroidOpenSettings from 'react-native-android-open-settings';
import DeviceInfo from 'react-native-device-info';
import Permissions from 'react-native-permissions';
const getCameraPermissionDeniedMessage = (intl: typeof intlShape) => {
const {formatMessage} = intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
};
const getPhotoPermissionDeniedMessage = (intl: typeof intlShape) => {
const {formatMessage} = intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
};
const getStoragePermissionDeniedMessage = (intl: typeof intlShape) => {
const {formatMessage} = intl;
const applicationName = DeviceInfo.getApplicationName();
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.',
}),
};
};
export const hasCameraPermission = async (intl: typeof intlShape) => {
const {formatMessage} = intl;
const targetSource = Platform.OS === 'ios' ? Permissions.PERMISSIONS.IOS.CAMERA : Permissions.PERMISSIONS.ANDROID.CAMERA;
const hasPermission = await Permissions.check(targetSource);
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(targetSource);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = getCameraPermissionDeniedMessage(intl);
Alert.alert(
title,
text,
[
grantOption,
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
],
);
return false;
}
}
return true;
};
export const hasPhotoPermission = async (intl: typeof intlShape) => {
const {formatMessage} = intl;
const targetSource = Platform.OS === 'ios' ? Permissions.PERMISSIONS.IOS.PHOTO_LIBRARY : Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const hasPermission = await Permissions.check(targetSource);
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(targetSource);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = getPhotoPermissionDeniedMessage(intl);
Alert.alert(
title,
text,
[
grantOption,
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
],
);
return false;
}
}
return true;
};
export const hasStoragePermission = async (intl: typeof intlShape) => {
if (Platform.OS === 'android') {
const {formatMessage} = intl;
const storagePermission = Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const hasPermissionToStorage = await Permissions.check(storagePermission);
switch (hasPermissionToStorage) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(storagePermission);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const {title, text} = getStoragePermissionDeniedMessage(intl);
Alert.alert(
title,
text,
[
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
{
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => AndroidOpenSettings.appDetailsSettings(),
},
],
);
return false;
}
}
}
return true;
};

View file

@ -53,6 +53,9 @@
"apps.suggestion.no_static": "No matching options.",
"apps.suggestion.no_suggestion": "No matching suggestions.",
"archivedChannelMessage": "You are viewing an **archived channel**. New messages cannot be posted.",
"camera_type.photo.option": "Capture Photo",
"camera_type.title": "Choose an action",
"camera_type.video.option": "Record Video",
"center_panel.archived.closeChannel": "Close Channel",
"channel_header.addMembers": "Add Members",
"channel_header.directchannel.you": "{displayname} (you) ",

20231
detox/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,20 +6,20 @@
"devDependencies": {
"@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-transform-modules-commonjs": "7.14.0",
"@babel/plugin-transform-runtime": "7.13.15",
"@babel/preset-env": "7.14.1",
"@babel/plugin-transform-runtime": "7.14.3",
"@babel/preset-env": "7.14.5",
"axios": "0.21.1",
"babel-jest": "26.6.3",
"babel-jest": "27.0.2",
"babel-plugin-module-resolver": "4.1.0",
"client-oauth2": "github:larkox/js-client-oauth2#e24e2eb5dfcbbbb3a59d095e831dbe0012b0ac49",
"deepmerge": "4.2.2",
"detox": "18.13.0",
"detox": "18.18.1",
"form-data": "4.0.0",
"jest": "26.6.3",
"jest-circus": "26.6.3",
"jest-cli": "26.6.3",
"jest-html-reporters": "2.1.6",
"jest-junit": "12.0.0",
"jest-junit": "12.1.0",
"sanitize-filename": "1.6.3",
"uuid": "8.3.2"
},

View file

@ -7,8 +7,8 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.1)
aws-partitions (1.467.0)
aws-sdk-core (3.114.2)
aws-partitions (1.468.0)
aws-sdk-core (3.114.3)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
@ -55,7 +55,7 @@ GEM
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.4)
fastlane (2.185.0)
fastlane (2.185.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
@ -99,7 +99,7 @@ GEM
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.5.0)
google-apis-androidpublisher_v3 (0.6.0)
google-apis-core (~> 0.1)
google-apis-core (0.3.0)
addressable (~> 2.5, >= 2.5.1)

View file

@ -3,16 +3,16 @@ PODS:
- BVLinearGradient (2.5.6):
- React
- DoubleConversion (1.1.6)
- FBLazyVector (0.64.1)
- FBReactNativeSpec (0.64.1):
- FBLazyVector (0.64.2)
- FBReactNativeSpec (0.64.2):
- RCT-Folly (= 2020.01.13.00)
- RCTRequired (= 0.64.1)
- RCTTypeSafety (= 0.64.1)
- React-Core (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- RCTRequired (= 0.64.2)
- RCTTypeSafety (= 0.64.2)
- React-Core (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- glog (0.3.5)
- jail-monkey (2.3.4):
- jail-monkey (2.4.0):
- React-Core
- libwebp (1.2.0):
- libwebp/demux (= 1.2.0)
@ -25,18 +25,18 @@ PODS:
- libwebp/webp (1.2.0)
- MMKV (1.2.7):
- MMKVCore (~> 1.2.7)
- MMKVCore (1.2.7)
- Permission-Camera (3.0.3):
- MMKVCore (1.2.9)
- Permission-Camera (3.0.4):
- RNPermissions
- Permission-MediaLibrary (3.0.3):
- Permission-MediaLibrary (3.0.4):
- RNPermissions
- Permission-Microphone (3.0.3):
- Permission-Microphone (3.0.4):
- RNPermissions
- Permission-Notifications (3.0.3):
- Permission-Notifications (3.0.4):
- RNPermissions
- Permission-PhotoLibrary (3.0.3):
- Permission-PhotoLibrary (3.0.4):
- RNPermissions
- Permission-PhotoLibraryAddOnly (3.0.3):
- Permission-PhotoLibraryAddOnly (3.0.4):
- RNPermissions
- RCT-Folly (2020.01.13.00):
- boost-for-react-native
@ -47,211 +47,211 @@ PODS:
- boost-for-react-native
- DoubleConversion
- glog
- RCTRequired (0.64.1)
- RCTTypeSafety (0.64.1):
- FBLazyVector (= 0.64.1)
- RCTRequired (0.64.2)
- RCTTypeSafety (0.64.2):
- FBLazyVector (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTRequired (= 0.64.1)
- React-Core (= 0.64.1)
- RCTRequired (= 0.64.2)
- React-Core (= 0.64.2)
- RCTYouTube (2.0.1):
- React
- YoutubePlayer-in-WKWebView (~> 0.3.1)
- React (0.64.1):
- React-Core (= 0.64.1)
- React-Core/DevSupport (= 0.64.1)
- React-Core/RCTWebSocket (= 0.64.1)
- React-RCTActionSheet (= 0.64.1)
- React-RCTAnimation (= 0.64.1)
- React-RCTBlob (= 0.64.1)
- React-RCTImage (= 0.64.1)
- React-RCTLinking (= 0.64.1)
- React-RCTNetwork (= 0.64.1)
- React-RCTSettings (= 0.64.1)
- React-RCTText (= 0.64.1)
- React-RCTVibration (= 0.64.1)
- React-callinvoker (0.64.1)
- React-Core (0.64.1):
- React (0.64.2):
- React-Core (= 0.64.2)
- React-Core/DevSupport (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-RCTActionSheet (= 0.64.2)
- React-RCTAnimation (= 0.64.2)
- React-RCTBlob (= 0.64.2)
- React-RCTImage (= 0.64.2)
- React-RCTLinking (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- React-RCTSettings (= 0.64.2)
- React-RCTText (= 0.64.2)
- React-RCTVibration (= 0.64.2)
- React-callinvoker (0.64.2)
- React-Core (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.1)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-Core/Default (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/CoreModulesHeaders (0.64.1):
- React-Core/CoreModulesHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/Default (0.64.1):
- React-Core/Default (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/DevSupport (0.64.1):
- React-Core/DevSupport (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.1)
- React-Core/RCTWebSocket (= 0.64.1)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-jsinspector (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-Core/Default (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-jsinspector (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTActionSheetHeaders (0.64.1):
- React-Core/RCTActionSheetHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTAnimationHeaders (0.64.1):
- React-Core/RCTAnimationHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTBlobHeaders (0.64.1):
- React-Core/RCTBlobHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTImageHeaders (0.64.1):
- React-Core/RCTImageHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTLinkingHeaders (0.64.1):
- React-Core/RCTLinkingHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTNetworkHeaders (0.64.1):
- React-Core/RCTNetworkHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTSettingsHeaders (0.64.1):
- React-Core/RCTSettingsHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTTextHeaders (0.64.1):
- React-Core/RCTTextHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTVibrationHeaders (0.64.1):
- React-Core/RCTVibrationHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTWebSocket (0.64.1):
- React-Core/RCTWebSocket (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.1)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsiexecutor (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-Core/Default (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-CoreModules (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- React-CoreModules (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.1)
- React-Core/CoreModulesHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- React-RCTImage (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-cxxreact (0.64.1):
- RCTTypeSafety (= 0.64.2)
- React-Core/CoreModulesHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTImage (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-cxxreact (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- glog
- RCT-Folly (= 2020.01.13.00)
- React-callinvoker (= 0.64.1)
- React-jsi (= 0.64.1)
- React-jsinspector (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-runtimeexecutor (= 0.64.1)
- React-jsi (0.64.1):
- React-callinvoker (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsinspector (= 0.64.2)
- React-perflogger (= 0.64.2)
- React-runtimeexecutor (= 0.64.2)
- React-jsi (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- glog
- RCT-Folly (= 2020.01.13.00)
- React-jsi/Default (= 0.64.1)
- React-jsi/Default (0.64.1):
- React-jsi/Default (= 0.64.2)
- React-jsi/Default (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- glog
- RCT-Folly (= 2020.01.13.00)
- React-jsiexecutor (0.64.1):
- React-jsiexecutor (0.64.2):
- DoubleConversion
- glog
- RCT-Folly (= 2020.01.13.00)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-jsinspector (0.64.1)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-perflogger (= 0.64.2)
- React-jsinspector (0.64.2)
- react-native-cameraroll (4.0.4):
- React-Core
- react-native-cookies (6.0.7):
- react-native-cookies (6.0.8):
- React-Core
- react-native-document-picker (5.0.3):
- react-native-document-picker (5.2.0):
- React-Core
- react-native-hw-keyboard-event (0.0.4):
- React
- react-native-image-picker (2.3.4):
- react-native-image-picker (4.0.4):
- React-Core
- react-native-local-auth (1.6.0):
- React
- react-native-mmkv-storage (0.4.4):
- react-native-mmkv-storage (0.6.0):
- MMKV (= 1.2.7)
- React-Core
- react-native-netinfo (6.0.0):
- React-Core
- react-native-notifications (3.4.2):
- react-native-notifications (3.5.0):
- React-Core
- react-native-passcode-status (1.1.2):
- React
@ -266,72 +266,72 @@ PODS:
- react-native-video/Video (= 5.1.1)
- react-native-video/Video (5.1.1):
- React-Core
- react-native-webview (11.6.2):
- react-native-webview (11.6.4):
- React-Core
- React-perflogger (0.64.1)
- React-RCTActionSheet (0.64.1):
- React-Core/RCTActionSheetHeaders (= 0.64.1)
- React-RCTAnimation (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- React-perflogger (0.64.2)
- React-RCTActionSheet (0.64.2):
- React-Core/RCTActionSheetHeaders (= 0.64.2)
- React-RCTAnimation (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.1)
- React-Core/RCTAnimationHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTBlob (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTAnimationHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTBlob (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- React-Core/RCTBlobHeaders (= 0.64.1)
- React-Core/RCTWebSocket (= 0.64.1)
- React-jsi (= 0.64.1)
- React-RCTNetwork (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTImage (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- React-Core/RCTBlobHeaders (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTImage (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.1)
- React-Core/RCTImageHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- React-RCTNetwork (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTLinking (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- React-Core/RCTLinkingHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTNetwork (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTImageHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTLinking (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- React-Core/RCTLinkingHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTNetwork (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.1)
- React-Core/RCTNetworkHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTSettings (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTNetworkHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTSettings (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.1)
- React-Core/RCTSettingsHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-RCTText (0.64.1):
- React-Core/RCTTextHeaders (= 0.64.1)
- React-RCTVibration (0.64.1):
- FBReactNativeSpec (= 0.64.1)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTSettingsHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTText (0.64.2):
- React-Core/RCTTextHeaders (= 0.64.2)
- React-RCTVibration (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (= 0.64.1)
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (= 0.64.1)
- React-runtimeexecutor (0.64.1):
- React-jsi (= 0.64.1)
- ReactCommon/turbomodule/core (0.64.1):
- React-Core/RCTVibrationHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-runtimeexecutor (0.64.2):
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (0.64.2):
- DoubleConversion
- glog
- RCT-Folly (= 2020.01.13.00)
- React-callinvoker (= 0.64.1)
- React-Core (= 0.64.1)
- React-cxxreact (= 0.64.1)
- React-jsi (= 0.64.1)
- React-perflogger (= 0.64.1)
- React-callinvoker (= 0.64.2)
- React-Core (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-perflogger (= 0.64.2)
- ReactNativeExceptionHandler (2.10.10):
- React-Core
- ReactNativeKeyboardTrackingView (5.7.0):
@ -369,13 +369,13 @@ PODS:
- React-Core
- RNKeychain (7.0.0):
- React-Core
- RNLocalize (2.0.3):
- RNLocalize (2.1.1):
- React-Core
- RNPermissions (3.0.3):
- RNPermissions (3.0.4):
- React-Core
- RNReactNativeHapticFeedback (1.11.0):
- React-Core
- RNReanimated (2.1.0):
- RNReanimated (2.2.0):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
@ -407,27 +407,28 @@ PODS:
- RNRudderSdk (1.0.0):
- React
- Rudder
- RNScreens (3.1.1):
- RNScreens (3.4.0):
- React-Core
- RNSentry (2.4.2):
- React-RCTImage
- RNSentry (2.5.2):
- React-Core
- Sentry (= 6.1.4)
- RNShare (6.0.1):
- Sentry (= 7.0.0)
- RNShare (6.2.1):
- React-Core
- RNSVG (12.1.1):
- React
- RNVectorIcons (8.1.0):
- React-Core
- Rudder (1.0.11)
- SDWebImage (5.10.4):
- SDWebImage/Core (= 5.10.4)
- SDWebImage/Core (5.10.4)
- Rudder (1.0.18)
- SDWebImage (5.11.1):
- SDWebImage/Core (= 5.11.1)
- SDWebImage/Core (5.11.1)
- SDWebImageWebPCoder (0.6.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.7)
- Sentry (6.1.4):
- Sentry/Core (= 6.1.4)
- Sentry/Core (6.1.4)
- Sentry (7.0.0):
- Sentry/Core (= 7.0.0)
- Sentry/Core (7.0.0)
- Swime (3.0.6)
- XCDYouTubeKit (2.8.3)
- Yoga (1.14.0)
@ -684,88 +685,88 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: 7b423f9e248eae65987838148c36eec1dbfe0b53
FBReactNativeSpec: 5f648f4f07de2b0b37afa48029ed2b894d9ebfd0
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
jail-monkey: 0b62fb896246639f4203ea90358e08db91b7f5ea
jail-monkey: 01cd0a75aa1034d08fd851869e6e6c3b063242d7
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
MMKV: 22e5136f7d00197bc0fc9694b7f71519f0d1ca12
MMKVCore: 607b7b05f2c2140056b5d338e45f2c14bf3f4232
Permission-Camera: 104ac17250aa2632a0d488caca4e91b1d42fb70a
Permission-MediaLibrary: 5da8d6cdc18fcea48f9dd900f852bce112d68754
Permission-Microphone: 19d712e494fe874d61d9e537998d10237d28465b
Permission-Notifications: 41bc12e1ea68c62ac782526c361f15fd0a26e6f4
Permission-PhotoLibrary: 67f5337aa90463df9b1df53bc195ec280a4a327e
Permission-PhotoLibraryAddOnly: 80ff7188f5c2c1c7e995b66bbf7c6177d2a217ca
MMKVCore: 27bf04f4376305c9c0187136a7b58a236ee83f9e
Permission-Camera: 74dc395c37a06ebe915069c5dca954c7a2a5a2e3
Permission-MediaLibrary: f57d9a116468c560c2a4a2a479fc211b770f7a92
Permission-Microphone: 450ff628a7146c415cfb98025e518810d9fa76ad
Permission-Notifications: 5b4a12a9b52866667423c93d3af8e43b81872cf4
Permission-PhotoLibrary: 440f206f0d36e5951c4c9b28567f60471fdddfb5
Permission-PhotoLibraryAddOnly: 0ac89da5e01e80479947b517091a67c9e462c99a
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
RCTRequired: ec2ebc96b7bfba3ca5c32740f5a0c6a014a274d2
RCTTypeSafety: 22567f31e67c3e088c7ac23ea46ab6d4779c0ea5
RCTYouTube: 7ff7d42f5ed42d185198681e967fd2c2b661375d
React: a241e3dbb1e91d06332f1dbd2b3ab26e1a4c4b9d
React-callinvoker: da4d1c6141696a00163960906bc8a55b985e4ce4
React-Core: 46ba164c437d7dac607b470c83c8308b05799748
React-CoreModules: 217bd14904491c7b9940ff8b34a3fe08013c2f14
React-cxxreact: 0090588ae6660c4615d3629fdd5c768d0983add4
React-jsi: 5de8204706bd872b78ea646aee5d2561ca1214b6
React-jsiexecutor: 124e8f99992490d0d13e0649d950d3e1aae06fe9
React-jsinspector: 500a59626037be5b3b3d89c5151bc3baa9abf1a9
react-native-cameraroll: 7c6c7ca84844f93b3dac9a87670bbad6541684ec
react-native-cookies: 6004d512ffc6f2c498c5b70cda0bc040350c0585
react-native-document-picker: d2b8fa4d9b268a316515b1f631236f7a87813df6
RCTRequired: 6d3e854f0e7260a648badd0d44fc364bc9da9728
RCTTypeSafety: c1f31d19349c6b53085766359caac425926fafaa
RCTYouTube: 4509d59a7de050dd0c7c6cb1f427d37678d63b5c
React: bda6b6d7ae912de97d7a61aa5c160db24aa2ad69
React-callinvoker: 9840ea7e8e88ed73d438edb725574820b29b5baa
React-Core: b5e385da7ce5f16a220fc60fd0749eae2c6120f0
React-CoreModules: 17071a4e2c5239b01585f4aa8070141168ab298f
React-cxxreact: 9be7b6340ed9f7c53e53deca7779f07cd66525ba
React-jsi: 67747b9722f6dab2ffe15b011bcf6b3f2c3f1427
React-jsiexecutor: 80c46bd381fd06e418e0d4f53672dc1d1945c4c3
React-jsinspector: cc614ec18a9ca96fd275100c16d74d62ee11f0ae
react-native-cameraroll: 88f4e62d9ecd0e1f253abe4f685474f2ea14bfa2
react-native-cookies: 2cb6ef472da68610dfcf0eaee68464c244943abd
react-native-document-picker: f1b5398801b332c77bc62ae0eae2116f49bdff26
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
react-native-local-auth: 359af242caa1e5c501ac9dfe33b1e238ad8f08c6
react-native-mmkv-storage: 5ed53d7c4ac98f1ef184404e781f63a998f98121
react-native-netinfo: 34f4d7a42f49157f3b45c14217d256bce7dc9682
react-native-notifications: e914ff49de3b2268cfa1b5e2ad21fd27c36d5b7b
react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a
react-native-image-picker: c07b072faa83f3480b473a15ea3c19cc39b3d6fa
react-native-local-auth: 49d336dcf0cb0268a930100013a6525844093844
react-native-mmkv-storage: 684f1adf671592c18fb023e0cf1305a84108b59d
react-native-netinfo: e849fc21ca2f4128a5726c801a82fc6f4a6db50d
react-native-notifications: 89a73cd2cd2648e1734fa10e3507681c9e4f14de
react-native-passcode-status: e78f76b3c8db613e6ced6bd40b54aa4f53374173
react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3
react-native-safe-area-context: e471852c5ed67eea4b10c5d9d43c1cebae3b231d
react-native-startup-time: c4c433fcf7ccac3c622f5d522e4ef18fb3bb0c9c
react-native-video: 1574074179ecaf6a9dd067116c8f31bf9fec15c8
react-native-webview: 1f56115845c98f0a59dfbbac685797c014a821be
React-perflogger: aad6d4b4a267936b3667260d1f649b6f6069a675
React-RCTActionSheet: fc376be462c9c8d6ad82c0905442fd77f82a9d2a
React-RCTAnimation: ba0a1c3a2738be224a08092fa7f1b444ab77d309
React-RCTBlob: f758d4403fc5828a326dc69e27b41e1a92f34947
React-RCTImage: ce57088705f4a8d03f6594b066a59c29143ba73e
React-RCTLinking: 852a3a95c65fa63f657a4b4e2d3d83a815e00a7c
React-RCTNetwork: 9d7ccb8a08d522d71700b4fb677d9fa28cccd118
React-RCTSettings: d8aaf4389ff06114dee8c42ef5f0f2915946011e
React-RCTText: 809c12ed6b261796ba056c04fcd20d8b90bcc81d
React-RCTVibration: 4b99a7f5c6c0abbc5256410cc5425fb8531986e1
React-runtimeexecutor: ff951a0c241bfaefc4940a3f1f1a229e7cb32fa6
ReactCommon: bedc99ed4dae329c4fcf128d0c31b9115e5365ca
react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79
react-native-startup-time: 1a068b744ce5097a85ebe0fbff691b05961c324d
react-native-video: 0bb76b6d6b77da3009611586c7dbf817b947f30e
react-native-webview: 1a19adb5578cdf7f005b7961dcc50c1c6b70f41b
React-perflogger: 25373e382fed75ce768a443822f07098a15ab737
React-RCTActionSheet: af7796ba49ffe4ca92e7277a5d992d37203f7da5
React-RCTAnimation: 6a2e76ab50c6f25b428d81b76a5a45351c4d77aa
React-RCTBlob: 02a2887023e0eed99391b6445b2e23a2a6f9226d
React-RCTImage: ce5bf8e7438f2286d9b646a05d6ab11f38b0323d
React-RCTLinking: ccd20742de14e020cb5f99d5c7e0bf0383aefbd9
React-RCTNetwork: dfb9d089ab0753e5e5f55fc4b1210858f7245647
React-RCTSettings: b14aef2d83699e48b410fb7c3ba5b66cd3291ae2
React-RCTText: 41a2e952dd9adc5caf6fb68ed46b275194d5da5f
React-RCTVibration: 24600e3b1aaa77126989bc58b6747509a1ba14f3
React-runtimeexecutor: a9904c6d0218fb9f8b19d6dd88607225927668f9
ReactCommon: 149906e01aa51142707a10665185db879898e966
ReactNativeExceptionHandler: b11ff67c78802b2f62eed0e10e75cb1ef7947c60
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
ReactNativeNavigation: 4c4ca87edc0da4ee818158a62cb6188088454e5c
rn-fetch-blob: 17961aec08caae68bb8fc0e5b40f93b3acfa6932
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCMaskedView: f127cd9652acfa31b91dcff613e07ba18b774db6
RNDeviceInfo: 49f6d50f861c7810fac2dd9b71cfb56cc1940e14
RNDevMenu: 9f80d65b80ba1fa84e5361d017b8c854a2f05005
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
RNDeviceInfo: 8d3a29207835f972bce883723882980143270d55
RNDevMenu: fd325b5554b61fe7f48d9205a3877cf5ee88cd7c
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNKeychain: f75b8c8b2f17d3b2aa1f25b4a0ac5b83d947ff8f
RNLocalize: 29e84ea169d3bca6c3b83584536c7f586a07fb98
RNPermissions: d6679ecf0c21bf6de5a619b20df4304182792da8
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
RNPermissions: c46788f4cd402287425983e04a4c9bb4356036eb
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
RNReanimated: b8c8004b43446e3c2709fe64b2b41072f87428ad
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
RNRudderSdk: 5d99b1a5a582ab55d6213b38018d35e98818af63
RNScreens: bd1523c3bde7069b8e958e5a16e1fc7722ad0bdd
RNSentry: e86fb2e2fec0365644f4b582938bf66be515acce
RNShare: 755de6bac084428f8fd8fb54c376f126f40e560c
RNScreens: 21b73c94c9117e1110a79ee0ee80c93ccefed8ce
RNSentry: bfa1e2776c7413570e790cbbf79d2060dd0a565b
RNShare: 5ac8f6532ca4cd80fc71caef1cfbba1854a6a045
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
Rudder: ef340f877a39653f19e69124dffae12fde3f881b
SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84
Rudder: 2a70cad66a3f5f3f6be056bf1ce0ce8d10b5ca67
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
Sentry: 9d055e2de30a77685e86b219acf02e59b82091fc
Sentry: 89d26e036063b9cb9caa59b6951dd2f8277aa13b
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
XCDYouTubeKit: 46df93c4dc4d48763ad720d997704384635c4335
Yoga: a7de31c64fe738607e7a3803e3f591a4b1df7393
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
YoutubePlayer-in-WKWebView: cfbf46da51d7370662a695a8f351e5fa1d3e1008
PODFILE CHECKSUM: a4402d26aaec4ef7e58bd8304848d89c876d285a

53328
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,8 @@
"license": "Apache 2.0",
"private": true,
"dependencies": {
"@babel/runtime": "7.14.0",
"@react-native-cookies/cookies": "6.0.7",
"@babel/runtime": "7.14.6",
"@react-native-cookies/cookies": "6.0.8",
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/cameraroll": "4.0.4",
"@react-native-community/clipboard": "1.5.1",
@ -16,8 +16,8 @@
"@react-native-community/netinfo": "6.0.0",
"@react-navigation/native": "5.9.4",
"@react-navigation/stack": "5.14.5",
"@rudderstack/rudder-sdk-react-native": "1.0.6",
"@sentry/react-native": "2.4.2",
"@rudderstack/rudder-sdk-react-native": "1.0.10",
"@sentry/react-native": "2.5.2",
"analytics-react-native": "1.2.0",
"commonmark": "github:mattermost/commonmark.js#d716e1c89e0a6721051df7bc74ad7683e1ae438f",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81af294317ebe19b5cc195d7fbc4f4a58177854c",
@ -27,51 +27,51 @@
"form-data": "4.0.0",
"fuse.js": "6.4.6",
"intl": "1.2.5",
"jail-monkey": "2.3.4",
"mime-db": "1.47.0",
"jail-monkey": "2.4.0",
"mime-db": "1.48.0",
"moment-timezone": "0.5.33",
"prop-types": "15.7.2",
"react": "17.0.2",
"react-intl": "2.8.0",
"react-native": "0.64.1",
"react-native": "0.64.2",
"react-native-android-open-settings": "1.3.0",
"react-native-animatable": "1.3.3",
"react-native-button": "3.0.1",
"react-native-calendars": "1.1260.0",
"react-native-calendars": "1.1263.0",
"react-native-device-info": "8.1.3",
"react-native-document-picker": "5.0.3",
"react-native-elements": "3.4.1",
"react-native-document-picker": "5.2.0",
"react-native-elements": "3.4.2",
"react-native-exception-handler": "2.10.10",
"react-native-fast-image": "8.3.4",
"react-native-file-viewer": "2.1.4",
"react-native-gesture-handler": "1.10.3",
"react-native-haptic-feedback": "1.11.0",
"react-native-hw-keyboard-event": "0.0.4",
"react-native-image-picker": "2.3.4",
"react-native-image-picker": "4.0.4",
"react-native-keyboard-aware-scrollview": "2.1.0",
"react-native-keyboard-tracking-view": "5.7.0",
"react-native-keychain": "7.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-local-auth": "1.6.0",
"react-native-localize": "2.0.3",
"react-native-mmkv-storage": "0.4.4",
"react-native-localize": "2.1.1",
"react-native-mmkv-storage": "0.6.0",
"react-native-navigation": "7.13.0",
"react-native-notifications": "3.4.2",
"react-native-notifications": "3.5.0",
"react-native-passcode-status": "1.1.2",
"react-native-permissions": "3.0.3",
"react-native-reanimated": "2.1.0",
"react-native-permissions": "3.0.4",
"react-native-reanimated": "2.2.0",
"react-native-redash": "16.0.11",
"react-native-safe-area": "0.5.1",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.1.1",
"react-native-screens": "3.4.0",
"react-native-section-list-get-item-layout": "2.2.3",
"react-native-share": "6.0.1",
"react-native-share": "6.2.1",
"react-native-slider": "0.11.0",
"react-native-startup-time": "2.0.0",
"react-native-svg": "12.1.1",
"react-native-vector-icons": "8.1.0",
"react-native-video": "5.1.1",
"react-native-webview": "11.6.2",
"react-native-webview": "11.6.4",
"react-native-youtube": "2.0.1",
"react-redux": "7.2.4",
"redux": "4.1.0",
@ -90,12 +90,12 @@
"url-parse": "1.5.1"
},
"devDependencies": {
"@babel/cli": "7.13.16",
"@babel/core": "7.14.0",
"@babel/plugin-transform-runtime": "7.13.15",
"@babel/preset-env": "7.14.1",
"@babel/register": "7.13.16",
"@react-native-community/eslint-config": "2.0.0",
"@babel/cli": "7.14.5",
"@babel/core": "7.14.6",
"@babel/plugin-transform-runtime": "7.14.5",
"@babel/preset-env": "7.14.5",
"@babel/register": "7.14.5",
"@react-native-community/eslint-config": "3.0.0",
"@storybook/addon-knobs": "6.2.9",
"@storybook/addon-ondevice-knobs": "5.3.25",
"@storybook/react-native": "5.3.25",
@ -105,32 +105,32 @@
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/jest": "26.0.23",
"@types/moment-timezone": "0.5.30",
"@types/react": "17.0.5",
"@types/react-native": "0.64.5",
"@types/react": "17.0.11",
"@types/react-native": "0.64.10",
"@types/react-native-share": "3.3.2",
"@types/react-native-video": "5.0.4",
"@types/react-native-video": "5.0.6",
"@types/react-redux": "7.1.16",
"@types/react-test-renderer": "17.0.1",
"@types/shallow-equals": "1.0.0",
"@types/tinycolor2": "1.4.2",
"@types/url-parse": "1.4.3",
"@typescript-eslint/eslint-plugin": "4.22.1",
"@typescript-eslint/parser": "4.22.1",
"@typescript-eslint/eslint-plugin": "4.27.0",
"@typescript-eslint/parser": "4.27.0",
"babel-eslint": "10.1.0",
"babel-jest": "26.6.3",
"babel-jest": "27.0.2",
"babel-loader": "8.2.2",
"babel-plugin-module-resolver": "4.1.0",
"babel-plugin-transform-remove-console": "6.9.4",
"deep-freeze": "0.0.1",
"detox": "18.13.0",
"detox": "18.18.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"enzyme-to-json": "3.6.2",
"eslint": "7.26.0",
"eslint": "7.28.0",
"eslint-plugin-header": "3.1.1",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react": "7.24.0",
"harmony-reflect": "1.6.2",
"husky": "^6.0.0",
"isomorphic-fetch": "3.0.0",
@ -143,7 +143,7 @@
"mmjstool": "github:mattermost/mattermost-utilities#3faa6075089a541d8c90ed85114e644c7a23fedf",
"mock-async-storage": "2.2.0",
"mock-socket": "9.0.3",
"nock": "13.0.11",
"nock": "13.1.0",
"nyc": "15.1.0",
"patch-package": "6.4.7",
"react-dom": "17.0.2",
@ -152,11 +152,11 @@
"react-native-storybook-loader": "2.0.4",
"redux-mock-store": "1.5.4",
"redux-persist-node-storage": "2.0.0",
"socketcluster": "16.0.1",
"ts-jest": "26.5.6",
"typescript": "4.2.4",
"socketcluster": "16.0.2",
"ts-jest": "27.0.3",
"typescript": "4.3.4",
"underscore": "1.13.1",
"util": "0.12.3"
"util": "0.12.4"
},
"scripts": {
"android": "react-native run-android",

View file

@ -6,7 +6,7 @@ index 29f27c0..cac1ee4 100644
buildToolsVersion "28.0.3"
defaultConfig {
- minSdkVersion 19
- minSdkVersion 21
+ minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 19
targetSdkVersion 28
versionCode 1

View file

@ -1,5 +1,5 @@
diff --git a/node_modules/react-native-elements/dist/searchbar/SearchBar-android.js b/node_modules/react-native-elements/dist/searchbar/SearchBar-android.js
index fe25d36..a2a1a34 100644
index 1bfd2b4..820ccbc 100644
--- a/node_modules/react-native-elements/dist/searchbar/SearchBar-android.js
+++ b/node_modules/react-native-elements/dist/searchbar/SearchBar-android.js
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
@ -31,10 +31,10 @@ index fe25d36..a2a1a34 100644
render() {
var _a;
diff --git a/node_modules/react-native-elements/dist/searchbar/SearchBar-ios.js b/node_modules/react-native-elements/dist/searchbar/SearchBar-ios.js
index 9581256..b10d579 100644
index 8fe90be..bb0e071 100644
--- a/node_modules/react-native-elements/dist/searchbar/SearchBar-ios.js
+++ b/node_modules/react-native-elements/dist/searchbar/SearchBar-ios.js
@@ -79,6 +79,11 @@ class SearchBar extends Component {
@@ -85,6 +85,11 @@ class SearchBar extends Component {
cancelButtonWidth: null,
};
}
@ -44,9 +44,9 @@ index 9581256..b10d579 100644
+ }
+ }
render() {
const _a = this.props, { theme, cancelButtonProps, cancelButtonTitle, clearIcon, containerStyle, leftIconContainerStyle, rightIconContainerStyle, inputContainerStyle, inputStyle, placeholderTextColor, showLoading, loadingProps, searchIcon, showCancel } = _a, attributes = __rest(_a, ["theme", "cancelButtonProps", "cancelButtonTitle", "clearIcon", "containerStyle", "leftIconContainerStyle", "rightIconContainerStyle", "inputContainerStyle", "inputStyle", "placeholderTextColor", "showLoading", "loadingProps", "searchIcon", "showCancel"]);
const { hasFocus, isEmpty } = this.state;
@@ -154,7 +159,6 @@ const styles = StyleSheet.create({
var _a, _b, _c, _d, _e, _f, _g;
const _h = this.props, { theme, cancelButtonProps, cancelButtonTitle, clearIcon, containerStyle, leftIconContainerStyle, rightIconContainerStyle, inputContainerStyle, inputStyle, placeholderTextColor, showLoading, loadingProps, searchIcon, showCancel } = _h, attributes = __rest(_h, ["theme", "cancelButtonProps", "cancelButtonTitle", "clearIcon", "containerStyle", "leftIconContainerStyle", "rightIconContainerStyle", "inputContainerStyle", "inputStyle", "placeholderTextColor", "showLoading", "loadingProps", "searchIcon", "showCancel"]);
@@ -167,7 +172,6 @@ const styles = StyleSheet.create({
paddingBottom: 13,
paddingTop: 13,
flexDirection: 'row',
@ -54,7 +54,7 @@ index 9581256..b10d579 100644
alignItems: 'center',
},
input: {
@@ -164,7 +168,7 @@ const styles = StyleSheet.create({
@@ -177,7 +181,7 @@ const styles = StyleSheet.create({
inputContainer: {
borderBottomWidth: 0,
borderRadius: 9,

View file

@ -1,811 +0,0 @@
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
index 48fb5c1..5ca1e20 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
@@ -3,6 +3,7 @@ package com.imagepicker;
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -69,6 +70,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
public static final int REQUEST_LAUNCH_IMAGE_LIBRARY = 13002;
public static final int REQUEST_LAUNCH_VIDEO_LIBRARY = 13003;
public static final int REQUEST_LAUNCH_VIDEO_CAPTURE = 13004;
+ public static final int REQUEST_LAUNCH_MIXED_CAPTURE = 13005;
public static final int REQUEST_PERMISSIONS_FOR_CAMERA = 14001;
public static final int REQUEST_PERMISSIONS_FOR_LIBRARY = 14002;
@@ -265,27 +267,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
cameraIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, videoDurationLimit);
}
- }
- else
- {
+ } else if (pickBoth) {
+ Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ this.setImageCaptureUri(takePictureIntent);
+ Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ cameraIntent = new Intent(Intent.ACTION_CHOOSER);
+ Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ Intent[] intentArray = new Intent[]{takePictureIntent,takeVideoIntent};
+ cameraIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
+ cameraIntent.putExtra(Intent.EXTRA_TITLE, "Choose an action");
+ cameraIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_CAPTURE;
cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- final File original = createNewFile(reactContext, this.options, false);
- imageConfig = imageConfig.withOriginalFile(original);
-
- if (imageConfig.original != null) {
- cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
- }else {
- responseHelper.invokeError(callback, "Couldn't get file path for photo");
- return;
- }
- if (cameraCaptureURI == null)
- {
- responseHelper.invokeError(callback, "Couldn't get file path for photo");
- return;
- }
- cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ this.setImageCaptureUri(cameraIntent);
}
if (cameraIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -349,17 +346,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
requestCode = REQUEST_LAUNCH_VIDEO_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK);
libraryIntent.setType("video/*");
- }
- else
- {
+ } else if (pickBoth) {
+ libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ libraryIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ libraryIntent.setType("image/*");
+ libraryIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"});
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- if (pickBoth)
- {
- libraryIntent.setType("image/* video/*");
- }
+ libraryIntent.setType("image/*");
}
if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -385,75 +383,42 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
}
}
- @Override
- public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
- //robustness code
- if (passResult(requestCode))
- {
- return;
- }
-
- responseHelper.cleanResponse();
-
- // user cancel
- if (resultCode != Activity.RESULT_OK)
- {
- removeUselessFiles(requestCode, imageConfig);
- responseHelper.invokeCancel(callback);
- callback = null;
- return;
- }
-
- Uri uri = null;
- switch (requestCode)
- {
- case REQUEST_LAUNCH_IMAGE_CAPTURE:
- uri = cameraCaptureURI;
- break;
-
- case REQUEST_LAUNCH_IMAGE_LIBRARY:
- uri = data.getData();
- String realPath = getRealPathFromURI(uri);
- final boolean isUrl = !TextUtils.isEmpty(realPath) &&
- Patterns.WEB_URL.matcher(realPath).matches();
- if (realPath == null || isUrl)
- {
- try
- {
- File file = createFileFromURI(uri);
- realPath = file.getAbsolutePath();
- uri = Uri.fromFile(file);
- }
- catch (Exception e)
- {
- // image not in cache
- responseHelper.putString("error", "Could not read photo");
- responseHelper.putString("uri", uri.toString());
- responseHelper.invokeResponse(callback);
- callback = null;
- return;
- }
- }
- imageConfig = imageConfig.withOriginalFile(new File(realPath));
- break;
-
- case REQUEST_LAUNCH_VIDEO_LIBRARY:
- responseHelper.putString("uri", data.getData().toString());
- responseHelper.putString("path", getRealPathFromURI(data.getData()));
- responseHelper.invokeResponse(callback);
- callback = null;
- return;
+ protected String getMimeType(Activity activity, Uri uri) {
+ String mimeType = null;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = activity.getApplicationContext().getContentResolver();
+ mimeType = cr.getType(uri);
+ } else {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ fileExtension.toLowerCase());
+ }
+ return mimeType;
+ }
- case REQUEST_LAUNCH_VIDEO_CAPTURE:
- final String path = getRealPathFromURI(data.getData());
- responseHelper.putString("uri", data.getData().toString());
- responseHelper.putString("path", path);
- fileScan(reactContext, path);
+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) {
+ String realPath = getRealPathFromURI(uri);
+ String mime = getMimeType(activity, uri);
+ final boolean isUrl = !TextUtils.isEmpty(realPath) &&
+ Patterns.WEB_URL.matcher(realPath).matches();
+ if (isUrl) {
+ try {
+ File file = createFileFromURI(uri);
+ realPath = file.getAbsolutePath();
+ uri = Uri.fromFile(file);
+ } catch (Exception e) {
+ // image not in cache
+ responseHelper.putString("error", "Could not read photo");
+ responseHelper.putString("uri", uri.toString());
responseHelper.invokeResponse(callback);
callback = null;
+ this.options = null;
return;
+ }
}
+ imageConfig = imageConfig.withOriginalFile(new File(realPath));
+
final ReadExifResult result = readExifInterface(responseHelper, imageConfig);
if (result.error != null)
@@ -461,6 +426,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
removeUselessFiles(requestCode, imageConfig);
responseHelper.invokeError(callback, result.error.getMessage());
callback = null;
+ this.options = null;
return;
}
@@ -472,7 +438,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
updatedResultResponse(uri, imageConfig.original.getAbsolutePath());
// don't create a new file if contraint are respected
- if (imageConfig.useOriginal(initialWidth, initialHeight, result.currentRotation))
+ if (imageConfig.useOriginal(initialWidth, initialHeight, result.currentRotation) || mime.equals("image/gif"))
{
responseHelper.putInt("width", initialWidth);
responseHelper.putInt("height", initialHeight);
@@ -481,6 +447,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
else
{
imageConfig = getResizedImage(reactContext, this.options, imageConfig, initialWidth, initialHeight, requestCode);
+ if (imageConfig == null)
+ {
+ responseHelper.invokeError(callback, "Could not read image");
+ callback = null;
+ this.options = null;
+ return;
+ }
if (imageConfig.resized == null)
{
removeUselessFiles(requestCode, imageConfig);
@@ -523,6 +496,61 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
this.options = null;
}
+ protected void extractVideoFromResult(Uri uri) {
+ responseHelper.putString("uri", uri.toString());
+ responseHelper.putString("path", getRealPathFromURI(uri));
+ responseHelper.invokeResponse(callback);
+ callback = null;
+ this.options = null;
+ }
+
+ @Override
+ public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
+ //robustness code
+ if (passResult(requestCode))
+ {
+ return;
+ }
+
+ responseHelper.cleanResponse();
+
+ // user cancel
+ if (resultCode != Activity.RESULT_OK)
+ {
+ removeUselessFiles(requestCode, imageConfig);
+ responseHelper.invokeCancel(callback);
+ callback = null;
+ return;
+ }
+
+ switch (requestCode)
+ {
+ case REQUEST_LAUNCH_IMAGE_CAPTURE:
+ extractImageFromResult(activity, cameraCaptureURI, requestCode);
+ break;
+ case REQUEST_LAUNCH_IMAGE_LIBRARY:
+ extractImageFromResult(activity, data.getData(), requestCode);
+ break;
+ case REQUEST_LAUNCH_VIDEO_LIBRARY:
+ extractVideoFromResult(data.getData());
+ break;
+ case REQUEST_LAUNCH_MIXED_CAPTURE:
+ case REQUEST_LAUNCH_VIDEO_CAPTURE:
+ if (data == null || data.getData() == null) {
+ extractImageFromResult(activity, cameraCaptureURI, requestCode);
+ } else {
+ Uri selectedMediaUri = data.getData();
+ String mime = getMimeType(activity, selectedMediaUri);
+ if (mime.contains("image")) {
+ extractImageFromResult(activity, selectedMediaUri, requestCode);
+ } else {
+ extractVideoFromResult(selectedMediaUri);
+ }
+ }
+ break;
+ }
+ }
+
public void invokeCustomButton(@NonNull final String action)
{
responseHelper.invokeCustomButton(this.callback, action);
@@ -551,7 +579,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
return callback == null || (cameraCaptureURI == null && requestCode == REQUEST_LAUNCH_IMAGE_CAPTURE)
|| (requestCode != REQUEST_LAUNCH_IMAGE_CAPTURE && requestCode != REQUEST_LAUNCH_IMAGE_LIBRARY
- && requestCode != REQUEST_LAUNCH_VIDEO_LIBRARY && requestCode != REQUEST_LAUNCH_VIDEO_CAPTURE);
+ && requestCode != REQUEST_LAUNCH_VIDEO_LIBRARY && requestCode != REQUEST_LAUNCH_VIDEO_CAPTURE
+ && requestCode != REQUEST_LAUNCH_MIXED_CAPTURE);
}
private void updatedResultResponse(@Nullable final Uri uri,
@@ -571,22 +600,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@NonNull final Callback callback,
@NonNull final int requestCode)
{
- final int writePermission = ActivityCompat
- .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
- final int cameraPermission = ActivityCompat
- .checkSelfPermission(activity, Manifest.permission.CAMERA);
-
- boolean permissionsGranted = false;
+ int selfCheckResult = 0;
switch (requestCode) {
case REQUEST_PERMISSIONS_FOR_LIBRARY:
- permissionsGranted = writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
case REQUEST_PERMISSIONS_FOR_CAMERA:
- permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.CAMERA);
+ if (selfCheckResult == PackageManager.PERMISSION_GRANTED) {
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
break;
}
+ final boolean permissionsGranted = selfCheckResult == PackageManager.PERMISSION_GRANTED;
if (!permissionsGranted)
{
final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA);
@@ -787,4 +818,21 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
videoDurationLimit = options.getInt("durationLimit");
}
}
+
+ private void setImageCaptureUri(Intent cameraIntent) {
+ final File original = createNewFile(reactContext, this.options, false);
+ imageConfig = imageConfig.withOriginalFile(original);
+
+ if (imageConfig.original != null) {
+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
+ } else {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ if (cameraCaptureURI == null) {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ }
}
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
index cc90dce..72ddc92 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
@@ -1,201 +1,270 @@
package com.imagepicker.utils;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
-import android.content.ContentUris;
+import android.provider.OpenableColumns;
+import android.content.ContentResolver;
import android.os.Environment;
+import android.webkit.MimeTypeMap;
+import android.util.Log;
+import android.text.TextUtils;
+
+import android.os.ParcelFileDescriptor;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
-import java.io.File;
+import java.io.*;
+import java.nio.channels.FileChannel;
+import java.util.Objects;
+
+// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
public class RealPathUtil {
+ public static final String CACHE_DIR_NAME = "mmShare";
+
+ public static @Nullable
+ Uri compatUriFromFile(@NonNull final Context context,
+ @NonNull final File file) {
+ Uri result = null;
+ final String packageName = context.getApplicationContext().getPackageName();
+ final String authority = packageName + ".provider";
+ try {
+ result = FileProvider.getUriForFile(context, authority, file);
+ }
+ catch(IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public static String getRealPathFromURI(final Context context, final Uri uri) {
+
+ // DocumentProvider
+ if (DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+ } else if (isDownloadsDocument(uri)) {
+ // DownloadsProvider
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ if (!TextUtils.isEmpty(id)) {
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "");
+ }
+ try {
+ return getPathFromSavingTempFile(context, uri);
+ } catch (NumberFormatException e) {
+ Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString());
+ return null;
+ }
+ }
+ } else if (isMediaDocument(uri)) {
+ // MediaProvider
+
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ if (contentUri != null) {
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ } else {
+ return getPathFromSavingTempFile(context, uri);
+ }
+ }
+ }
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ // MediaStore (and general)
+
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+
+ // Try save to tmp file, and return tmp file path
+ return getPathFromSavingTempFile(context, uri);
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ public static String getPathFromSavingTempFile(Context context, final Uri uri) {
+ File tmpFile;
+ String fileName = null;
+
+ if (uri == null || uri.isRelative()) {
+ return null;
+ }
+
+ // Try and get the filename from the Uri
+ try {
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = sanitizeFilename(returnCursor.getString(nameIndex));
+ returnCursor.close();
+
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (TextUtils.isEmpty(fileName)) {
+ fileName = sanitizeFilename(uri.getLastPathSegment().trim());
+ }
+
+
+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME);
+ if (!cacheDir.exists()) {
+ cacheDir.mkdirs();
+ }
+
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
+
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+
+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
+ FileChannel dst = new FileOutputStream(tmpFile).getChannel();
+ dst.transferFrom(src, 0, src.size());
+ src.close();
+ dst.close();
+ } catch (IOException ex) {
+ return null;
+ }
+ return tmpFile.getAbsolutePath();
+ }
+
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ public static String getExtension(String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ int dot = uri.lastIndexOf(".");
+ if (dot >= 0) {
+ return uri.substring(dot);
+ } else {
+ // No extension.
+ return "";
+ }
+ }
+
+ public static String getMimeType(File file) {
+
+ String extension = getExtension(file.getName());
+
+ if (extension.length() > 0)
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
+
+ return "application/octet-stream";
+ }
+
+ public static String getMimeType(String filePath) {
+ File file = new File(filePath);
+ return getMimeType(file);
+ }
+
+ public static String getMimeTypeFromUri(final Context context, final Uri uri) {
+ try {
+ ContentResolver cR = context.getContentResolver();
+ return cR.getType(uri);
+ } catch (Exception e) {
+ return "application/octet-stream";
+ }
+ }
+
+ public static void deleteTempFiles(final File dir) {
+ try {
+ if (dir.isDirectory()) {
+ deleteRecursive(dir);
+ }
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
+
+ private static void deleteRecursive(File fileOrDirectory) {
+ if (fileOrDirectory.isDirectory())
+ for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
+ deleteRecursive(child);
+
+ fileOrDirectory.delete();
+ }
+
+ private static String sanitizeFilename(String filename) {
+ if (filename == null) {
+ return null;
+ }
- public static @Nullable Uri compatUriFromFile(@NonNull final Context context,
- @NonNull final File file) {
- Uri result = null;
- if (Build.VERSION.SDK_INT < 21) {
- result = Uri.fromFile(file);
- }
- else {
- final String packageName = context.getApplicationContext().getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- try {
- result = FileProvider.getUriForFile(context, authority, file);
- }
- catch(IllegalArgumentException e) {
- e.printStackTrace();
- }
- }
- return result;
- }
-
- @SuppressLint("NewApi")
- public static @Nullable String getRealPathFromURI(@NonNull final Context context,
- @NonNull final Uri uri) {
-
- final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
- // DocumentProvider
- if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
- // ExternalStorageProvider
- if (isExternalStorageDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- if ("primary".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/" + split[1];
- }
-
- // TODO handle non-primary volumes
- }
- // DownloadsProvider
- else if (isDownloadsDocument(uri)) {
-
- final String id = DocumentsContract.getDocumentId(uri);
- final Uri contentUri = ContentUris.withAppendedId(
- Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
-
- return getDataColumn(context, contentUri, null, null);
- }
- // MediaProvider
- else if (isMediaDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
-
- final String selection = "_id=?";
- final String[] selectionArgs = new String[] {
- split[1]
- };
-
- return getDataColumn(context, contentUri, selection, selectionArgs);
- }
- }
- // MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
-
- // Return the remote address
- if (isGooglePhotosUri(uri))
- return uri.getLastPathSegment();
-
- if (isFileProviderUri(context, uri))
- return getFileProviderPath(context, uri);
-
- return getDataColumn(context, uri, null, null);
- }
- // File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
- return uri.getPath();
- }
-
- return null;
- }
-
- /**
- * Get the value of the data column for this Uri. This is useful for
- * MediaStore Uris, and other file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @param selection (Optional) Filter used in the query.
- * @param selectionArgs (Optional) Selection arguments used in the query.
- * @return The value of the _data column, which is typically a file path.
- */
- public static String getDataColumn(Context context, Uri uri, String selection,
- String[] selectionArgs) {
-
- Cursor cursor = null;
- final String column = "_data";
- final String[] projection = {
- column
- };
-
- try {
- cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- final int index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
- return null;
- }
-
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public static boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- public static boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is MediaProvider.
- */
- public static boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(@NonNull final Uri uri) {
- return "com.google.android.apps.photos.content".equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return Whether the Uri authority is FileProvider
- */
- public static boolean isFileProviderUri(@NonNull final Context context,
- @NonNull final Uri uri) {
- final String packageName = context.getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- return authority.equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return File path or null if file is missing
- */
- public static @Nullable String getFileProviderPath(@NonNull final Context context,
- @NonNull final Uri uri)
- {
- final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
- final File file = new File(appDir, uri.getLastPathSegment());
- return file.exists() ? file.toString(): null;
- }
+ File f = new File(filename);
+ return f.getName();
+ }
}

View file

@ -1,19 +0,0 @@
diff --git a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
index 2875bda..8235771 100644
--- a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
+++ b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
@@ -45,10 +45,10 @@ - (id)init
{
self = [super init];
if (self) {
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
- NSString *libraryPath = (NSString *) [paths firstObject];
- NSString *rootDir = [libraryPath stringByAppendingPathComponent:@"mmkv"];
- [MMKV initializeMMKV:rootDir];
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *APP_GROUP_ID = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"];
+ NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID].path;
+ [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
secureStorage = [[SecureStorage alloc]init];
IdStore = [[IDStore alloc] initWithMMKV:[MMKV mmkvWithID:@"mmkvIdStore"]];

View file

@ -0,0 +1,32 @@
diff --git a/node_modules/react-native-mmkv-storage/ios/MMKVNative.mm b/node_modules/react-native-mmkv-storage/ios/MMKVNative.mm
index 807b9e7..1549f04 100644
--- a/node_modules/react-native-mmkv-storage/ios/MMKVNative.mm
+++ b/node_modules/react-native-mmkv-storage/ios/MMKVNative.mm
@@ -62,13 +62,12 @@ BOOL functionDiedBeforeCompletion = YES;
mmkvInstances = [NSMutableDictionary dictionary];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
- NSUserDomainMask, YES);
- NSString *libraryPath = (NSString *)[paths firstObject];
- NSString *rootDir = [libraryPath stringByAppendingPathComponent:@"mmkv"];
- rPath = rootDir;
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *APP_GROUP_ID = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"];
+ NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID].path;
+ rPath = groupDir;
_secureStorage = [[SecureStorage alloc] init];
- [MMKV initializeMMKV:rootDir];
+ [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
install(*(jsi::Runtime *)cxxBridge.runtime);
[self migrate];
}
@@ -171,7 +170,7 @@ static void install(jsi::Runtime &jsiRuntime) {
jsiRuntime, PropNameID::forAscii(jsiRuntime, "initializeMMKV"), 0,
[](Runtime &runtime, const Value &thisValue, const Value *arguments,
size_t count) -> Value {
- [MMKV initializeMMKV:rPath];
+ [MMKV initializeMMKV:nil groupDir:rPath logLevel:MMKVLogInfo];
return Value::undefined();
});

View file

@ -50,6 +50,7 @@
"@store": ["app/store/index"],
"@store/*": ["app/store/*"],
"@telemetry": ["app/telemetry/index"],
"@typings/*": ["types/*"],
"@utils/*": ["app/utils/*"],
"@websocket": ["app/client/websocket"],
"*": ["./*", "node_modules/*"],

View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {intlShape} from 'react-intl';
import {Asset} from 'react-native-image-picker';
import type {Theme} from '@mm-redux/types/preferences';
export interface QuickActionAttachmentProps {
disabled: boolean;
fileCount: number;
intl: typeof intlShape;
maxFileCount: number;
onUploadFiles: (files: Asset[]) => void;
testID: string;
theme: Theme;
}