2018-07-17 19:10:27 +00:00
|
|
|
import React, { Component } from 'react';
|
2018-09-25 19:28:42 +00:00
|
|
|
import {
|
2019-01-29 19:52:56 +00:00
|
|
|
View, Text, StyleSheet, Image, ScrollView, TouchableHighlight
|
2018-09-25 19:28:42 +00:00
|
|
|
} from 'react-native';
|
2019-07-18 17:25:18 +00:00
|
|
|
import { connect } from 'react-redux';
|
2018-07-17 19:10:27 +00:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import Modal from 'react-native-modal';
|
|
|
|
import { responsive } from 'react-native-responsive-ui';
|
|
|
|
import equal from 'deep-equal';
|
|
|
|
|
|
|
|
import TextInput from '../TextInput';
|
|
|
|
import Button from '../Button';
|
|
|
|
import I18n from '../../i18n';
|
2018-11-19 18:18:15 +00:00
|
|
|
import sharedStyles from '../../views/Styles';
|
2019-01-29 19:52:56 +00:00
|
|
|
import { isIOS } from '../../utils/deviceInfo';
|
2019-07-18 17:25:18 +00:00
|
|
|
import {
|
|
|
|
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_DANGER
|
|
|
|
} from '../../constants/colors';
|
2019-07-18 17:07:37 +00:00
|
|
|
import { CustomIcon } from '../../lib/Icons';
|
2018-07-17 19:10:27 +00:00
|
|
|
|
2019-03-29 19:36:07 +00:00
|
|
|
const cancelButtonColor = COLOR_BACKGROUND_CONTAINER;
|
2018-07-17 19:10:27 +00:00
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2018-11-19 18:18:15 +00:00
|
|
|
modal: {
|
|
|
|
alignItems: 'center'
|
|
|
|
},
|
2018-07-17 19:10:27 +00:00
|
|
|
titleContainer: {
|
|
|
|
flexDirection: 'row',
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
paddingTop: 16
|
|
|
|
},
|
|
|
|
title: {
|
2019-03-29 19:36:07 +00:00
|
|
|
fontSize: 14,
|
|
|
|
...sharedStyles.textColorTitle,
|
2018-11-19 18:18:15 +00:00
|
|
|
...sharedStyles.textBold
|
2018-07-17 19:10:27 +00:00
|
|
|
},
|
|
|
|
container: {
|
2018-11-19 18:18:15 +00:00
|
|
|
height: 430,
|
2019-03-29 19:36:07 +00:00
|
|
|
backgroundColor: COLOR_WHITE,
|
2018-07-17 19:10:27 +00:00
|
|
|
flexDirection: 'column'
|
|
|
|
},
|
|
|
|
scrollView: {
|
|
|
|
flex: 1,
|
|
|
|
padding: 16
|
|
|
|
},
|
|
|
|
image: {
|
|
|
|
height: 150,
|
|
|
|
flex: 1,
|
|
|
|
marginBottom: 16,
|
|
|
|
resizeMode: 'contain'
|
|
|
|
},
|
|
|
|
buttonContainer: {
|
|
|
|
flexDirection: 'row',
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
padding: 16,
|
2019-03-29 19:36:07 +00:00
|
|
|
backgroundColor: COLOR_BACKGROUND_CONTAINER
|
2018-07-17 19:10:27 +00:00
|
|
|
},
|
2018-11-19 18:18:15 +00:00
|
|
|
button: {
|
|
|
|
marginBottom: 0
|
|
|
|
},
|
|
|
|
androidButton: {
|
|
|
|
paddingHorizontal: 15,
|
|
|
|
justifyContent: 'center',
|
|
|
|
height: 48,
|
|
|
|
borderRadius: 2
|
|
|
|
},
|
|
|
|
androidButtonText: {
|
|
|
|
fontSize: 18,
|
|
|
|
textAlign: 'center'
|
2019-07-18 17:07:37 +00:00
|
|
|
},
|
|
|
|
fileIcon: {
|
|
|
|
color: COLOR_PRIMARY,
|
|
|
|
margin: 20,
|
|
|
|
flex: 1,
|
|
|
|
textAlign: 'center'
|
|
|
|
},
|
2019-07-18 17:25:18 +00:00
|
|
|
errorIcon: {
|
|
|
|
color: COLOR_DANGER
|
|
|
|
},
|
|
|
|
fileMime: {
|
|
|
|
...sharedStyles.textColorTitle,
|
|
|
|
...sharedStyles.textBold,
|
|
|
|
textAlign: 'center',
|
|
|
|
fontSize: 20,
|
|
|
|
marginBottom: 20
|
|
|
|
},
|
|
|
|
errorContainer: {
|
|
|
|
margin: 20,
|
|
|
|
flex: 1,
|
|
|
|
textAlign: 'center',
|
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center'
|
|
|
|
},
|
2019-07-18 17:07:37 +00:00
|
|
|
video: {
|
|
|
|
flex: 1,
|
|
|
|
borderRadius: 4,
|
|
|
|
height: 150,
|
|
|
|
backgroundColor: '#1f2329',
|
|
|
|
marginBottom: 6,
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'center'
|
2018-07-17 19:10:27 +00:00
|
|
|
}
|
2018-11-19 18:18:15 +00:00
|
|
|
|
2018-07-17 19:10:27 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
@responsive
|
2019-07-18 17:25:18 +00:00
|
|
|
@connect(state => ({
|
|
|
|
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
|
|
|
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
|
|
|
|
}))
|
2018-07-17 19:10:27 +00:00
|
|
|
export default class UploadModal extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
isVisible: PropTypes.bool,
|
|
|
|
file: PropTypes.object,
|
|
|
|
close: PropTypes.func,
|
|
|
|
submit: PropTypes.func,
|
2019-07-18 17:25:18 +00:00
|
|
|
window: PropTypes.object,
|
|
|
|
FileUpload_MediaTypeWhiteList: PropTypes.string,
|
|
|
|
FileUpload_MaxFileSize: PropTypes.number
|
2018-07-17 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
state = {
|
|
|
|
name: '',
|
|
|
|
description: '',
|
|
|
|
file: {}
|
|
|
|
};
|
|
|
|
|
|
|
|
static getDerivedStateFromProps(props, state) {
|
|
|
|
if (!equal(props.file, state.file) && props.file && props.file.path) {
|
|
|
|
return {
|
|
|
|
file: props.file,
|
|
|
|
name: props.file.filename || 'Filename',
|
|
|
|
description: ''
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-12-21 10:55:35 +00:00
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
|
|
const { name, description, file } = this.state;
|
|
|
|
const { window, isVisible } = this.props;
|
|
|
|
|
|
|
|
if (nextState.name !== name) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (nextState.description !== description) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (nextProps.isVisible !== isVisible) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (nextProps.window.width !== window.width) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!equal(nextState.file, file)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-07-18 17:25:18 +00:00
|
|
|
canUploadFile = () => {
|
|
|
|
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, file } = this.props;
|
|
|
|
if (!(file && file.path)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (file.size > FileUpload_MaxFileSize) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-29 16:48:40 +00:00
|
|
|
// if white list is empty, all media types are enabled
|
2019-07-18 17:25:18 +00:00
|
|
|
if (!FileUpload_MediaTypeWhiteList) {
|
2019-07-29 16:48:40 +00:00
|
|
|
return true;
|
2019-07-18 17:25:18 +00:00
|
|
|
}
|
|
|
|
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
|
|
|
|
if (allowedMime.includes(file.mime)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const wildCardGlob = '/*';
|
|
|
|
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
|
|
|
|
if (wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-19 18:18:15 +00:00
|
|
|
submit = () => {
|
2018-07-17 19:10:27 +00:00
|
|
|
const { file, submit } = this.props;
|
|
|
|
const { name, description } = this.state;
|
|
|
|
submit({ ...file, name, description });
|
|
|
|
}
|
|
|
|
|
2019-07-18 17:25:18 +00:00
|
|
|
renderError = () => {
|
|
|
|
const { file, FileUpload_MaxFileSize, close } = this.props;
|
|
|
|
const { window: { width } } = this.props;
|
|
|
|
const errorMessage = (FileUpload_MaxFileSize < file.size)
|
|
|
|
? 'error-file-too-large'
|
|
|
|
: 'error-invalid-file-type';
|
|
|
|
return (
|
|
|
|
<View style={[styles.container, { width: width - 32 }]}>
|
|
|
|
<View style={styles.titleContainer}>
|
|
|
|
<Text style={styles.title}>{I18n.t(errorMessage)}</Text>
|
|
|
|
</View>
|
|
|
|
<View style={styles.errorContainer}>
|
|
|
|
<CustomIcon name='circle-cross' size={120} style={styles.errorIcon} />
|
|
|
|
</View>
|
|
|
|
<Text style={styles.fileMime}>{ file.mime }</Text>
|
|
|
|
<View style={styles.buttonContainer}>
|
|
|
|
{
|
|
|
|
(isIOS)
|
|
|
|
? (
|
|
|
|
<Button
|
|
|
|
title={I18n.t('Cancel')}
|
|
|
|
type='secondary'
|
|
|
|
backgroundColor={cancelButtonColor}
|
|
|
|
style={styles.button}
|
|
|
|
onPress={close}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
: (
|
|
|
|
<TouchableHighlight
|
|
|
|
onPress={close}
|
|
|
|
style={[styles.androidButton, { backgroundColor: cancelButtonColor }]}
|
|
|
|
underlayColor={cancelButtonColor}
|
|
|
|
activeOpacity={0.5}
|
|
|
|
>
|
|
|
|
<Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: COLOR_PRIMARY }]}>{I18n.t('Cancel')}</Text>
|
|
|
|
</TouchableHighlight>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-19 18:18:15 +00:00
|
|
|
renderButtons = () => {
|
|
|
|
const { close } = this.props;
|
2019-01-29 19:52:56 +00:00
|
|
|
if (isIOS) {
|
2018-11-19 18:18:15 +00:00
|
|
|
return (
|
|
|
|
<View style={styles.buttonContainer}>
|
|
|
|
<Button
|
|
|
|
title={I18n.t('Cancel')}
|
|
|
|
type='secondary'
|
|
|
|
backgroundColor={cancelButtonColor}
|
|
|
|
style={styles.button}
|
|
|
|
onPress={close}
|
|
|
|
/>
|
|
|
|
<Button
|
|
|
|
title={I18n.t('Send')}
|
|
|
|
type='primary'
|
|
|
|
style={styles.button}
|
|
|
|
onPress={this.submit}
|
|
|
|
/>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// FIXME: RNGH don't work well on Android modals: https://github.com/kmagiera/react-native-gesture-handler/issues/139
|
|
|
|
return (
|
|
|
|
<View style={styles.buttonContainer}>
|
|
|
|
<TouchableHighlight
|
|
|
|
onPress={close}
|
|
|
|
style={[styles.androidButton, { backgroundColor: cancelButtonColor }]}
|
|
|
|
underlayColor={cancelButtonColor}
|
|
|
|
activeOpacity={0.5}
|
|
|
|
>
|
2019-03-29 19:36:07 +00:00
|
|
|
<Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: COLOR_PRIMARY }]}>{I18n.t('Cancel')}</Text>
|
2018-11-19 18:18:15 +00:00
|
|
|
</TouchableHighlight>
|
|
|
|
<TouchableHighlight
|
|
|
|
onPress={this.submit}
|
2019-03-29 19:36:07 +00:00
|
|
|
style={[styles.androidButton, { backgroundColor: COLOR_PRIMARY }]}
|
|
|
|
underlayColor={COLOR_PRIMARY}
|
2018-11-19 18:18:15 +00:00
|
|
|
activeOpacity={0.5}
|
|
|
|
>
|
2019-03-29 19:36:07 +00:00
|
|
|
<Text style={[styles.androidButtonText, { ...sharedStyles.textMedium, color: COLOR_WHITE }]}>{I18n.t('Send')}</Text>
|
2018-11-19 18:18:15 +00:00
|
|
|
</TouchableHighlight>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-07-18 17:07:37 +00:00
|
|
|
renderPreview() {
|
|
|
|
const { file } = this.props;
|
|
|
|
if (file.mime && file.mime.match(/image/)) {
|
|
|
|
return (<Image source={{ isStatic: true, uri: file.path }} style={styles.image} />);
|
|
|
|
}
|
|
|
|
if (file.mime && file.mime.match(/video/)) {
|
|
|
|
return (
|
|
|
|
<View style={styles.video}>
|
|
|
|
<CustomIcon name='play' size={72} color={COLOR_WHITE} />
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (<CustomIcon name='file-generic' size={72} style={styles.fileIcon} />);
|
|
|
|
}
|
|
|
|
|
2018-07-17 19:10:27 +00:00
|
|
|
render() {
|
|
|
|
const { window: { width }, isVisible, close } = this.props;
|
2019-07-18 17:07:37 +00:00
|
|
|
const { name, description } = this.state;
|
2019-07-18 17:25:18 +00:00
|
|
|
const showError = !this.canUploadFile();
|
2018-07-17 19:10:27 +00:00
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
isVisible={isVisible}
|
2018-11-19 18:18:15 +00:00
|
|
|
style={styles.modal}
|
|
|
|
onBackdropPress={close}
|
|
|
|
onBackButtonPress={close}
|
2018-07-17 19:10:27 +00:00
|
|
|
animationIn='fadeIn'
|
|
|
|
animationOut='fadeOut'
|
|
|
|
useNativeDriver
|
|
|
|
hideModalContentWhileAnimating
|
2019-05-20 20:43:50 +00:00
|
|
|
avoidKeyboard
|
2018-07-17 19:10:27 +00:00
|
|
|
>
|
2019-07-18 17:25:18 +00:00
|
|
|
{(showError) ? this.renderError()
|
|
|
|
: (
|
|
|
|
<View style={[styles.container, { width: width - 32 }]}>
|
|
|
|
<View style={styles.titleContainer}>
|
|
|
|
<Text style={styles.title}>{I18n.t('Upload_file_question_mark')}</Text>
|
|
|
|
</View>
|
|
|
|
|
|
|
|
<ScrollView style={styles.scrollView}>
|
|
|
|
{this.renderPreview()}
|
|
|
|
<TextInput
|
|
|
|
placeholder={I18n.t('File_name')}
|
|
|
|
value={name}
|
|
|
|
onChangeText={value => this.setState({ name: value })}
|
|
|
|
/>
|
|
|
|
<TextInput
|
|
|
|
placeholder={I18n.t('File_description')}
|
|
|
|
value={description}
|
|
|
|
onChangeText={value => this.setState({ description: value })}
|
|
|
|
/>
|
|
|
|
</ScrollView>
|
|
|
|
{this.renderButtons()}
|
|
|
|
</View>
|
|
|
|
)}
|
2018-07-17 19:10:27 +00:00
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|