RN-80 Add ability to download files (#866)

* RN-80 Add ability to download files

* Add android permission check and toasts

* Add check for ability to download from config
This commit is contained in:
Chris Duarte 2017-09-01 08:11:49 -07:00 committed by enahum
parent 95e89c39ee
commit 220fb588b9
15 changed files with 743 additions and 15 deletions

View file

@ -1279,3 +1279,75 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-circular-progress
React Native component for creating animated, circular progress with ReactART
* HOMEPAGE:
* https://github.com/bgryszko/react-native-circular-progress
* LICENSE:
The MIT License (MIT)
Copyright (c) 2015 Bart Gryszko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
## react-native-fetch-blob
A project committed to making file access and data transfer easier, efficient for React Native developers.
* HOMEPAGE:
* https://github.com/wkh237/react-native-fetch-blob#user-content-upload-example--dropbox-files-upload-api
* LICENSE:
MIT License
Copyright (c) 2017 xeiyan@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -144,6 +144,7 @@ android {
}
dependencies {
compile project(':react-native-fetch-blob')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:25.0.1"
compile "com.facebook.react:react-native:+" // From node_modules

View file

@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.google.android.c2dm.permission.SEND" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -42,6 +43,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

View file

@ -6,6 +6,7 @@ import android.content.Context;
import android.os.Bundle;
import com.facebook.react.ReactApplication;
import com.RNFetchBlob.RNFetchBlobPackage;
import com.gantix.JailMonkey.JailMonkeyPackage;
import io.tradle.react.LocalAuthPackage;
import com.facebook.react.ReactInstanceManager;
@ -58,6 +59,7 @@ public class MainApplication extends NavigationApplication implements INotificat
new RNNotificationsPackage(this),
new LocalAuthPackage(),
new JailMonkeyPackage(),
new RNFetchBlobPackage(),
new MattermostManagedPackage()
);
}

View file

@ -1,4 +1,6 @@
rootProject.name = 'Mattermost'
include ':react-native-fetch-blob'
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
include ':jail-monkey'
project(':jail-monkey').projectDir = new File(rootProject.projectDir, '../node_modules/jail-monkey/android')
include ':react-native-local-auth'

View file

@ -248,7 +248,7 @@ class PostTextbox extends PureComponent {
};
ImagePicker.launchCamera(options, (response) => {
if (response.error) {
if (response.error || response.didCancel) {
return;
}
@ -267,7 +267,7 @@ class PostTextbox extends PureComponent {
}
ImagePicker.launchImageLibrary(options, (response) => {
if (response.error) {
if (response.error || response.didCancel) {
return;
}
@ -283,7 +283,7 @@ class PostTextbox extends PureComponent {
};
ImagePicker.launchImageLibrary(options, (response) => {
if (response.error) {
if (response.error || response.didCancel) {
return;
}

View file

@ -0,0 +1,127 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
PermissionsAndroid,
StyleSheet,
ToastAndroid,
TouchableOpacity,
View
} from 'react-native';
import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
import FormattedText from 'app/components/formatted_text';
import {emptyFunction} from 'app/utils/general';
const EXTERNAL_STORAGE_PERMISSION = 'android.permission.WRITE_EXTERNAL_STORAGE';
const HEADER_HEIGHT = 64;
const OPTION_LIST_WIDTH = 39;
export default class Downloader extends PureComponent {
static propTypes = {
file: PropTypes.object.isRequired,
onDownloadCancel: PropTypes.func,
onDownloadStart: PropTypes.func,
onDownloadSuccess: PropTypes.func,
show: PropTypes.bool
};
static defaultProps = {
onCancelPress: emptyFunction,
onDownloadStart: emptyFunction,
onDownloadSuccess: emptyFunction,
show: false
};
checkForPermissions = async () => {
const canWriteToStorage = await PermissionsAndroid.check(EXTERNAL_STORAGE_PERMISSION);
if (!canWriteToStorage) {
const permissionRequest = await PermissionsAndroid.request(EXTERNAL_STORAGE_PERMISSION, 'We need access to the downloads folder to save files.');
return permissionRequest === 'granted';
}
return true;
}
handleDownload = async () => {
const {file, onDownloadCancel, onDownloadStart, onDownloadSuccess} = this.props;
const canWriteToStorage = await this.checkForPermissions();
if (!canWriteToStorage) {
onDownloadCancel();
return;
}
try {
ToastAndroid.show('Download started', ToastAndroid.SHORT);
onDownloadStart();
const imageUrl = Client4.getFileUrl(file.id);
const task = RNFetchBlob.config({
fileCache: true,
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
path: `${RNFetchBlob.fs.dirs.DownloadDir}/${file.name}`,
title: `${file.name} download successful`,
mime: file.mime_type,
description: file.name,
mediaScannable: true
}
}).fetch('GET', imageUrl, {
Authorization: `Bearer ${Client4.token}`
});
await task;
ToastAndroid.show('Download complete', ToastAndroid.SHORT);
onDownloadSuccess();
} catch (error) {
ToastAndroid.show('Download failed', ToastAndroid.SHORT);
onDownloadCancel();
}
}
render() {
const {show} = this.props;
return (
<View style={[styles.wrapper, {height: show ? OPTION_LIST_WIDTH : 0}]}>
<TouchableOpacity
style={styles.downloadButton}
onPress={this.handleDownload}
>
<FormattedText
id='file_attachment.download'
defaultMessage='Download'
style={styles.downloadButtonText}
/>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
downloadButton: {
flex: 1,
justifyContent: 'center'
},
downloadButtonText: {
color: 'white',
fontSize: 15,
paddingLeft: 15
},
wrapper: {
position: 'absolute',
backgroundColor: '#575757',
top: HEADER_HEIGHT,
right: 0,
width: 150
}
});

View file

@ -0,0 +1,287 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Animated,
CameraRoll,
Dimensions,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import RNFetchBlob from 'react-native-fetch-blob';
import {AnimatedCircularProgress} from 'react-native-circular-progress';
import Icon from 'react-native-vector-icons/Ionicons';
import {Client4} from 'mattermost-redux/client';
import FormattedText from 'app/components/formatted_text';
import {emptyFunction} from 'app/utils/general';
const {View: AnimatedView} = Animated;
const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
export default class Downloader extends PureComponent {
static propTypes = {
file: PropTypes.object.isRequired,
onDownloadCancel: PropTypes.func,
onDownloadSuccess: PropTypes.func,
show: PropTypes.bool
};
static defaultProps = {
onCancelPress: emptyFunction,
onDownloadSuccess: emptyFunction,
show: false
};
state = {
downloaderTop: new Animated.Value(deviceHeight),
progress: 0
}
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
this.toggleDownloader();
this.setState({
didCancel: false,
progress: 0
});
} else if (!nextProps.show && this.props.show) {
this.toggleDownloader(false);
}
}
toggleDownloader = (show = true) => {
const top = show ? (deviceHeight / 2) - 100 : deviceHeight;
Animated.spring(this.state.downloaderTop, {
toValue: top,
tension: 8,
friction: 5
}).start(() => {
if (show) {
this.startDownload();
}
});
}
startDownload = async () => {
try {
const {file} = this.props;
const imageUrl = Client4.getFileUrl(file.id);
this.downloadTask = RNFetchBlob.config({
fileCache: true,
appendExt: file.extension
}).fetch('GET', imageUrl).progress((received, total) => {
const progress = (received / total) * 100;
this.setState({
progress
});
});
const res = await this.downloadTask;
const path = res.path();
const newPath = await CameraRoll.saveToCameraRoll(path);
this.setState({
progress: 100
}, () => {
// need to wait a bit for the progress circle UI to update to the give progress
setTimeout(async () => {
try {
// handles the case of a late cancellation by the user
// and ensures that we remove the file if they did cancel late.
if (this.state.didCancel) {
// TODO: There's issue with deleting files from the cameraRoll on iOS here https://github.com/wkh237/react-native-fetch-blob/issues/479
// This only occurs if the user cancels when the download is around 80% or more
await RNFetchBlob.fs.unlink(newPath);
this.props.onDownloadCancel();
} else {
this.props.onDownloadSuccess();
}
} catch (error) {
// ensure the downloader at least closes if a error
this.props.onDownloadCancel();
}
}, 2000);
});
res.flush(); // remove the temp file
this.downloadTask = null;
} catch (error) {
// cancellation throws so we need to catch
}
}
handleCancelDownload = () => {
this.setState({
didCancel: true
});
if (this.downloadTask) {
this.downloadTask.cancel(() => {
this.props.onDownloadCancel();
});
}
}
renderProgress = (fill) => {
const realFill = Number(fill.toFixed(0));
let component;
if (realFill === 100) {
component = (
<Icon
name='ios-checkmark'
size={64}
color='white'
/>
);
} else {
component = (
<View style={styles.progressCirclePercentage}>
<Text style={styles.progressText}>
{`${fill.toFixed(0)}%`}
</Text>
<TouchableOpacity
style={styles.cancelButton}
onPress={this.handleCancelDownload}
>
<FormattedText
id='channel_modal.cancel'
defaultMessage='Cancel'
style={styles.cancelText}
/>
</TouchableOpacity>
</View>
);
}
return (
<View style={styles.progressContent}>
{component}
<View style={styles.bottomContent}>
{(realFill < 100 || this.state.didCancel) ?
<FormattedText
id='mobile.downloader.downloading'
defaultMessage='Downloading...'
style={styles.bottomText}
/> :
<FormattedText
id='mobile.downloader.success'
defaultMessage='Image Saved'
style={styles.bottomText}
/>
}
</View>
</View>
);
}
render() {
const {show} = this.props;
const {didCancel, progress} = this.state;
const trueProgress = didCancel ? 0 : progress;
const containerHeight = show ? deviceHeight : 0;
return (
<View style={[styles.container, {height: containerHeight}]}>
<AnimatedView style={[styles.downloader, {top: this.state.downloaderTop}]}>
<View style={styles.progressCircleContent}>
<AnimatedCircularProgress
size={120}
fill={trueProgress}
width={4}
backgroundColor='rgba(255, 255, 255, 0.5)'
tintColor='white'
rotation={0}
style={styles.progressCircle}
>
{this.renderProgress}
</AnimatedCircularProgress>
</View>
</AnimatedView>
</View>
);
}
}
const styles = StyleSheet.create({
bottomContent: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
bottomText: {
color: 'white',
fontSize: 16,
fontWeight: '600'
},
cancelButton: {
height: 30,
width: 60,
alignItems: 'center',
justifyContent: 'center',
marginTop: 5
},
cancelText: {
color: 'white',
fontSize: 12
},
container: {
position: 'absolute',
top: 0,
left: 0,
width: deviceWidth
},
downloader: {
alignItems: 'center',
left: (deviceWidth / 2) - 118,
height: 220,
width: 236,
borderRadius: 8,
backgroundColor: 'rgba(0, 0, 0, 0.8)'
},
progressContainer: {
flex: 1
},
progressContent: {
position: 'absolute',
height: '100%',
width: '100%',
top: 0,
left: 0,
alignItems: 'center',
justifyContent: 'center'
},
progressCircle: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center'
},
progressCircleContent: {
width: 200,
height: 200,
alignItems: 'center',
justifyContent: 'center'
},
progressCirclePercentage: {
flex: 1,
alignItems: 'center',
marginTop: 80
},
progressText: {
color: 'white',
fontSize: 18
}
});

View file

@ -20,8 +20,13 @@ import Icon from 'react-native-vector-icons/Ionicons';
import LinearGradient from 'react-native-linear-gradient';
import Orientation from 'react-native-orientation';
import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
import {NavigationTypes} from 'app/constants';
import {emptyFunction} from 'app/utils/general';
import Downloader from './downloader';
import Previewer from './previewer';
const {View: AnimatedView} = Animated;
@ -40,6 +45,7 @@ export default class ImagePreview extends PureComponent {
actions: PropTypes.shape({
addFileToFetchCache: PropTypes.func.isRequired
}),
canDownloadFiles: PropTypes.bool.isRequired,
fetchCache: PropTypes.object.isRequired,
fileId: PropTypes.string.isRequired,
files: PropTypes.array.isRequired,
@ -144,6 +150,7 @@ export default class ImagePreview extends PureComponent {
};
handleImageTap = () => {
this.hideDownloader(false);
this.setHeaderAndFileInfoVisible(!this.state.showFileInfo);
};
@ -204,6 +211,103 @@ export default class ImagePreview extends PureComponent {
}
};
showDownloadOptions = () => {
if (Platform.OS === 'android') {
if (this.state.showDownloader) {
this.hideDownloader();
} else {
this.showDownloader();
}
} else {
this.showIOSDownloadOptions();
}
}
showIOSDownloadOptions = () => {
this.setHeaderAndFileInfoVisible(false);
const options = {
title: this.props.files[this.state.currentFile].name,
items: [{
action: this.showDownloader,
text: {
id: 'mobile.image_preview.save',
defaultMessage: 'Save Image'
}
}],
onCancelPress: () => this.setHeaderAndFileInfoVisible(true)
};
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
...options
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext'
}
});
}
showDownloader = () => {
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
this.setState({
showDownloader: true
});
}
hideDownloader = (hideFileInfo = true) => {
this.setState({showDownloader: false});
if (hideFileInfo) {
this.setHeaderAndFileInfoVisible(true);
}
}
renderDownloadButton = () => {
const {canDownloadFiles, files} = this.props;
const file = files[this.state.currentFile];
let icon;
let action = emptyFunction;
if (canDownloadFiles) {
if (Platform.OS === 'android') {
action = this.showDownloadOptions;
icon = (
<Icon
name='md-more'
size={32}
color='#fff'
/>
);
} else if (file.has_preview_image) {
action = this.showDownloadOptions;
icon = (
<Icon
name='ios-download-outline'
size={26}
color='#fff'
/>
);
}
}
return (
<TouchableOpacity
onPress={action}
style={style.headerIcon}
>
{icon}
</TouchableOpacity>
);
}
render() {
const maxImageHeight = this.state.deviceHeight - STATUSBAR_HEIGHT;
@ -303,16 +407,7 @@ export default class ImagePreview extends PureComponent {
<Text style={style.title}>
{`${this.state.currentFile + 1}/${this.props.files.length}`}
</Text>
<TouchableOpacity
onPress={() => true}
style={style.headerIcon}
>
{/*<Icon
name='download'
size={15}
color='#fff'
/>*/}
</TouchableOpacity>
{this.renderDownloadButton()}
</View>
</View>
<LinearGradient
@ -328,6 +423,13 @@ export default class ImagePreview extends PureComponent {
</LinearGradient>
</AnimatedView>
</AnimatedView>
<Downloader
show={this.state.showDownloader}
file={this.props.files[this.state.currentFile]}
onDownloadCancel={this.hideDownloader}
onDownloadStart={this.hideDownloader}
onDownloadSuccess={this.hideDownloader}
/>
</View>
);
}

View file

@ -7,6 +7,7 @@ import {Platform} from 'react-native';
import {addFileToFetchCache} from 'app/actions/views/file_preview';
import {getTheme} from 'app/selectors/preferences';
import {canDownloadFilesOnMobile} from 'mattermost-redux/selectors/entities/general';
import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
import ImagePreview from './image_preview';
@ -18,6 +19,7 @@ function makeMapStateToProps() {
return function mapStateToProps(state, ownProps) {
return {
...ownProps,
canDownloadFiles: canDownloadFilesOnMobile(state),
fetchCache: state.views.fetchCache,
files: getFilesForPost(state, ownProps.postId),
theme: getTheme(state),

View file

@ -14,6 +14,7 @@ import {
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes} from 'app/constants';
import {emptyFunction} from 'app/utils/general';
import OptionsModalList from './options_modal_list';
@ -25,12 +26,17 @@ export default class OptionsModal extends PureComponent {
static propTypes = {
items: PropTypes.array.isRequired,
navigator: PropTypes.object,
onCancelPress: PropTypes.func,
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
};
static defaultProps = {
onCancelPress: emptyFunction
}
state = {
top: new Animated.Value(deviceHeight)
};
@ -47,6 +53,11 @@ export default class OptionsModal extends PureComponent {
EventEmitter.off(NavigationTypes.NAVIGATION_CLOSE_MODAL, this.close);
}
handleCancel = () => {
this.props.onCancelPress();
this.close();
}
close = () => {
Animated.timing(this.state.top, {
toValue: deviceHeight,
@ -70,7 +81,7 @@ export default class OptionsModal extends PureComponent {
<AnimatedView style={{height: deviceHeight, left: 0, top: this.state.top, width: deviceWidth}}>
<OptionsModalList
items={items}
onCancelPress={this.close}
onCancelPress={this.handleCancel}
title={title}
/>
</AnimatedView>

View file

@ -1864,6 +1864,7 @@
"mobile.file_upload.more": "More",
"mobile.file_upload.video": "Video Library",
"mobile.help.title": "Help",
"mobile.image_preview.save": "Save Image",
"mobile.intro_messages.DM": "This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.",
"mobile.intro_messages.default_message": "This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.",
"mobile.intro_messages.default_welcome": "Welcome to {name}!",

View file

@ -25,10 +25,13 @@
146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
1BCA51319AC6442991C6A208 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0A091BF1A3D04650AD306A0D /* Zocial.ttf */; };
1C1F002AC5DE465FAE9E0D74 /* libFastImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C0C75A424714EAC8E876A8F /* libFastImage.a */; };
2B2679D44CFA48E6A60D68BC /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */; };
2B4C9B708010475DA575B81D /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */; };
2D5296A8926B4D7FBAF2D6E2 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */; };
2E3655C7E15049DBBC3A666B /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DFE628F5A526417D85AD635E /* libRNImagePicker.a */; };
374634801E8480C2005E1244 /* libRCTOrientation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 374634671E848085005E1244 /* libRCTOrientation.a */; };
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3752184F1F4B9E980035444B /* libRCTCameraRoll.a */; };
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37ABD39C1F4CE13B001FDE6B /* libART.a */; };
382D94CF15EE4FC292C3F341 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */; };
38FB8B0A3AAD4F839EF6EF0B /* libJailMonkey.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 77810F0A063349439B0D8B6B /* libJailMonkey.a */; };
3D38ABA732A34A9BB3294F90 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; };
@ -135,6 +138,20 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTOrientation;
};
375218401F4B9E320035444B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = A15C300E1CD25C330074CB35;
remoteInfo = RNFetchBlob;
};
3752184E1F4B9E980035444B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTCameraRoll;
};
3779BE9F1EB1235400D081C1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 85E82BCA0FD245EEA520D106 /* RNImagePicker.xcodeproj */;
@ -142,6 +159,13 @@
remoteGlobalIDString = 014A3B5C1C6CF33500B6D375;
remoteInfo = RNImagePicker;
};
37ABD39B1F4CE13B001FDE6B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 0CF68AC11AF0540F00FF9E5C;
remoteInfo = ART;
};
37D8FEC11E80B5230091F3BD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */;
@ -414,9 +438,13 @@
349FBA7338E74D9BBD709528 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
356C9186FA374641A00EB2EA /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBold.ttf"; path = "../assets/fonts/OpenSans-ExtraBold.ttf"; sourceTree = "<group>"; };
3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = "../node_modules/react-native/Libraries/CameraRoll/RCTCameraRoll.xcodeproj"; sourceTree = "<group>"; };
37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ART.xcodeproj; path = "../node_modules/react-native/Libraries/ART/ART.xcodeproj"; sourceTree = "<group>"; };
41F3AFE83AAF4B74878AB78A /* OpenSans-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Italic.ttf"; path = "../assets/fonts/OpenSans-Italic.ttf"; sourceTree = "<group>"; };
50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = FastImage.xcodeproj; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = "<group>"; };
4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = "<group>"; };
51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFetchBlob.xcodeproj; path = "../node_modules/react-native-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = "<group>"; };
5C3B95629BA74FB3A8377CB7 /* RCTOrientation.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTOrientation.xcodeproj; path = "../node_modules/react-native-orientation/iOS/RCTOrientation.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = "<group>"; };
@ -469,6 +497,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */,
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */,
7FBB5E9A1E1F5A4B000DE18A /* libRNSVG.a in Frameworks */,
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
@ -494,6 +524,7 @@
FDEF24E9D9AB47728E11033B /* libRNPasscodeStatus.a in Frameworks */,
38FB8B0A3AAD4F839EF6EF0B /* libJailMonkey.a in Frameworks */,
1C1F002AC5DE465FAE9E0D74 /* libFastImage.a in Frameworks */,
2B2679D44CFA48E6A60D68BC /* libRNFetchBlob.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -648,6 +679,22 @@
name = Products;
sourceTree = "<group>";
};
3752181C1F4B9E320035444B /* Products */ = {
isa = PBXGroup;
children = (
375218411F4B9E320035444B /* libRNFetchBlob.a */,
);
name = Products;
sourceTree = "<group>";
};
3752184B1F4B9E980035444B /* Products */ = {
isa = PBXGroup;
children = (
3752184F1F4B9E980035444B /* libRCTCameraRoll.a */,
);
name = Products;
sourceTree = "<group>";
};
3779BE7C1EB1235400D081C1 /* Products */ = {
isa = PBXGroup;
children = (
@ -656,6 +703,14 @@
name = Products;
sourceTree = "<group>";
};
37ABD3981F4CE13B001FDE6B /* Products */ = {
isa = PBXGroup;
children = (
37ABD39C1F4CE13B001FDE6B /* libART.a */,
);
name = Products;
sourceTree = "<group>";
};
37D8FEBE1E80B5230091F3BD /* Products */ = {
isa = PBXGroup;
children = (
@ -783,6 +838,8 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */,
3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */,
7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */,
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */,
7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */,
@ -809,6 +866,7 @@
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */,
27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */,
50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */,
546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@ -921,6 +979,10 @@
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 37ABD3981F4CE13B001FDE6B /* Products */;
ProjectRef = 37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */;
},
{
ProductGroup = 37D8FEBE1E80B5230091F3BD /* Products */;
ProjectRef = 7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */;
@ -941,6 +1003,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = 3752184B1F4B9E980035444B /* Products */;
ProjectRef = 3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@ -997,6 +1063,10 @@
ProductGroup = 37DD11071E79EBE1004111BA /* Products */;
ProjectRef = B97AA6F961BB47D3A3297E8E /* RNDeviceInfo.xcodeproj */;
},
{
ProductGroup = 3752181C1F4B9E320035444B /* Products */;
ProjectRef = 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */;
},
{
ProductGroup = 3779BE7C1EB1235400D081C1 /* Products */;
ProjectRef = 85E82BCA0FD245EEA520D106 /* RNImagePicker.xcodeproj */;
@ -1098,6 +1168,20 @@
remoteRef = 374634661E848085005E1244 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
375218411F4B9E320035444B /* libRNFetchBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNFetchBlob.a;
remoteRef = 375218401F4B9E320035444B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3752184F1F4B9E980035444B /* libRCTCameraRoll.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTCameraRoll.a;
remoteRef = 3752184E1F4B9E980035444B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3779BEA01EB1235400D081C1 /* libRNImagePicker.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -1105,6 +1189,13 @@
remoteRef = 3779BE9F1EB1235400D081C1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
37ABD39C1F4CE13B001FDE6B /* libART.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libART.a;
remoteRef = 37ABD39B1F4CE13B001FDE6B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -1481,6 +1572,7 @@
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1520,6 +1612,7 @@
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

View file

@ -22,10 +22,12 @@
"react-native-animatable": "1.2.2",
"react-native-bottom-sheet": "1.0.1",
"react-native-button": "1.8.2",
"react-native-circular-progress": "0.0.8",
"react-native-cookies": "3.1.0",
"react-native-device-info": "0.11.0",
"react-native-drawer": "2.3.0",
"react-native-exception-handler": "1.1.0",
"react-native-fetch-blob": "0.10.8",
"react-native-fast-image": "1.0.0",
"react-native-image-picker": "jp928/react-native-image-picker#6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4",
"react-native-keyboard-aware-scroll-view": "0.2.8",

View file

@ -2631,6 +2631,17 @@ glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob@7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.2"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
@ -4475,6 +4486,12 @@ react-native-button@1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/react-native-button/-/react-native-button-1.8.2.tgz#49e5cadd22ee443adea52a85539d288aa2117ade"
react-native-circular-progress@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/react-native-circular-progress/-/react-native-circular-progress-0.0.8.tgz#53ccdbe57357ce2459f8ab1b67119a3e2a5014b4"
dependencies:
art "^0.10.0"
react-native-cookies@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-native-cookies/-/react-native-cookies-3.1.0.tgz#7816a60be1ae0484c9ddd47380d77f98ec0ffaca"
@ -4495,6 +4512,13 @@ react-native-exception-handler@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-native-exception-handler/-/react-native-exception-handler-1.1.0.tgz#320b7fc9c104e11d66173a2b0daeac97831a1a37"
react-native-fetch-blob@0.10.8:
version "0.10.8"
resolved "https://registry.yarnpkg.com/react-native-fetch-blob/-/react-native-fetch-blob-0.10.8.tgz#4fc256abae0cb5f10e7c41f28c11b3ff330d72a9"
dependencies:
base-64 "0.1.0"
glob "7.0.6"
react-native-fast-image@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-1.0.0.tgz#a88337efa18fd34d530bab9ee05680374f3e2a2d"