Update dependencies and setup project with node 16 / npm 7 (#5474)
This commit is contained in:
parent
683c6f5df7
commit
f4f8379796
40 changed files with 65792 additions and 10821 deletions
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -246,6 +246,7 @@ const PostList = ({
|
|||
|
||||
return () => {
|
||||
EventEmitter.off('scroll-to-bottom', scrollToBottom);
|
||||
EventEmitter.off(NavigationTypes.NAVIGATION_DISMISS_AND_POP_TO_ROOT, closePermalink);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
63
app/screens/slide_up/index.tsx
Normal file
63
app/screens/slide_up/index.tsx
Normal 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;
|
||||
|
|
@ -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
195
app/utils/permission.ts
Normal 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;
|
||||
};
|
||||
|
|
@ -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
20231
detox/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
533
ios/Podfile.lock
533
ios/Podfile.lock
|
|
@ -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
53328
package-lock.json
generated
File diff suppressed because it is too large
Load diff
78
package.json
78
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
@ -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();
|
||||
+ }
|
||||
}
|
||||
|
|
@ -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"]];
|
||||
32
patches/react-native-mmkv-storage+0.6.0.patch
Normal file
32
patches/react-native-mmkv-storage+0.6.0.patch
Normal 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();
|
||||
});
|
||||
|
||||
|
|
@ -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/*"],
|
||||
|
|
|
|||
17
types/components/post_draft_quick_action.d.ts
vendored
Normal file
17
types/components/post_draft_quick_action.d.ts
vendored
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue