diff --git a/NOTICE.txt b/NOTICE.txt index 8f55842b0..00a0259ab 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -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. diff --git a/android/app/build.gradle b/android/app/build.gradle index 7987a1f7c..a8f7d7e9a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8e1e59741..be5c57796 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java index b23c2201f..c0ba9f3db 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -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() ); } diff --git a/android/settings.gradle b/android/settings.gradle index 0cb14b138..98bd4a3e2 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -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' diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index c3c6be728..03485a910 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -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; } diff --git a/app/screens/image_preview/downloader.android.js b/app/screens/image_preview/downloader.android.js new file mode 100644 index 000000000..006cc04b3 --- /dev/null +++ b/app/screens/image_preview/downloader.android.js @@ -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 ( + + + + + + ); + } +} + +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 + } +}); diff --git a/app/screens/image_preview/downloader.ios.js b/app/screens/image_preview/downloader.ios.js new file mode 100644 index 000000000..db50dc587 --- /dev/null +++ b/app/screens/image_preview/downloader.ios.js @@ -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 = ( + + ); + } else { + component = ( + + + {`${fill.toFixed(0)}%`} + + + + + + ); + } + + return ( + + {component} + + {(realFill < 100 || this.state.didCancel) ? + : + + } + + + ); + } + + render() { + const {show} = this.props; + const {didCancel, progress} = this.state; + + const trueProgress = didCancel ? 0 : progress; + + const containerHeight = show ? deviceHeight : 0; + return ( + + + + + {this.renderProgress} + + + + + ); + } +} + +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 + } +}); diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index d2a4cb0b2..e58889df5 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -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 = ( + + ); + } else if (file.has_preview_image) { + action = this.showDownloadOptions; + icon = ( + + ); + } + } + + return ( + + {icon} + + ); + } + render() { const maxImageHeight = this.state.deviceHeight - STATUSBAR_HEIGHT; @@ -303,16 +407,7 @@ export default class ImagePreview extends PureComponent { {`${this.state.currentFile + 1}/${this.props.files.length}`} - true} - style={style.headerIcon} - > - {/**/} - + {this.renderDownloadButton()} + ); } diff --git a/app/screens/image_preview/index.js b/app/screens/image_preview/index.js index 26fc9bc55..5fb65f808 100644 --- a/app/screens/image_preview/index.js +++ b/app/screens/image_preview/index.js @@ -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), diff --git a/app/screens/options_modal/options_modal.js b/app/screens/options_modal/options_modal.js index d2af074ae..99b2cd3f8 100644 --- a/app/screens/options_modal/options_modal.js +++ b/app/screens/options_modal/options_modal.js @@ -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 { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 66dc91ea0..b256d0b38 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -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}!", diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 024e38532..b3ba705ca 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; + 3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = "../node_modules/react-native/Libraries/CameraRoll/RCTCameraRoll.xcodeproj"; sourceTree = ""; }; + 37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ART.xcodeproj; path = "../node_modules/react-native/Libraries/ART/ART.xcodeproj"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; + 4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; }; + 3752181C1F4B9E320035444B /* Products */ = { + isa = PBXGroup; + children = ( + 375218411F4B9E320035444B /* libRNFetchBlob.a */, + ); + name = Products; + sourceTree = ""; + }; + 3752184B1F4B9E980035444B /* Products */ = { + isa = PBXGroup; + children = ( + 3752184F1F4B9E980035444B /* libRCTCameraRoll.a */, + ); + name = Products; + sourceTree = ""; + }; 3779BE7C1EB1235400D081C1 /* Products */ = { isa = PBXGroup; children = ( @@ -656,6 +703,14 @@ name = Products; sourceTree = ""; }; + 37ABD3981F4CE13B001FDE6B /* Products */ = { + isa = PBXGroup; + children = ( + 37ABD39C1F4CE13B001FDE6B /* libART.a */, + ); + name = Products; + sourceTree = ""; + }; 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 = ""; @@ -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"; diff --git a/package.json b/package.json index 08d6ec208..255ef0060 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 2c8c0442e..17e239892 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"