import React, { Component } from 'react'; import { View, Text, StyleSheet, Image, ScrollView, TouchableHighlight } from 'react-native'; import { connect } from 'react-redux'; 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'; import sharedStyles from '../../views/Styles'; import { isIOS } from '../../utils/deviceInfo'; import { canUploadFile } from '../../utils/media'; import { COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_DANGER } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; const cancelButtonColor = COLOR_BACKGROUND_CONTAINER; const styles = StyleSheet.create({ modal: { alignItems: 'center' }, titleContainer: { flexDirection: 'row', paddingHorizontal: 16, paddingTop: 16 }, title: { fontSize: 14, ...sharedStyles.textColorTitle, ...sharedStyles.textBold }, container: { height: 430, backgroundColor: COLOR_WHITE, flexDirection: 'column' }, scrollView: { flex: 1, padding: 16 }, image: { height: 150, flex: 1, marginBottom: 16, resizeMode: 'contain' }, buttonContainer: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, backgroundColor: COLOR_BACKGROUND_CONTAINER }, button: { marginBottom: 0 }, androidButton: { paddingHorizontal: 15, justifyContent: 'center', height: 48, borderRadius: 2 }, androidButtonText: { fontSize: 18, textAlign: 'center' }, fileIcon: { color: COLOR_PRIMARY, margin: 20, flex: 1, textAlign: 'center' }, 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' }, video: { flex: 1, borderRadius: 4, height: 150, backgroundColor: '#1f2329', marginBottom: 6, alignItems: 'center', justifyContent: 'center' } }); class UploadModal extends Component { static propTypes = { isVisible: PropTypes.bool, file: PropTypes.object, close: PropTypes.func, submit: PropTypes.func, window: PropTypes.object, FileUpload_MediaTypeWhiteList: PropTypes.string, FileUpload_MaxFileSize: PropTypes.number } 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; } 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; } canUploadFile = () => { const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, file } = this.props; if (!(file && file.path)) { return true; } if (file.size > FileUpload_MaxFileSize) { return false; } // if white list is empty, all media types are enabled if (!FileUpload_MediaTypeWhiteList) { return true; } 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; } submit = () => { const { file, submit } = this.props; const { name, description } = this.state; submit({ ...file, name, description }); } 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> ); } renderButtons = () => { const { close } = this.props; if (isIOS) { 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} > <Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: COLOR_PRIMARY }]}>{I18n.t('Cancel')}</Text> </TouchableHighlight> <TouchableHighlight onPress={this.submit} style={[styles.androidButton, { backgroundColor: COLOR_PRIMARY }]} underlayColor={COLOR_PRIMARY} activeOpacity={0.5} > <Text style={[styles.androidButtonText, { ...sharedStyles.textMedium, color: COLOR_WHITE }]}>{I18n.t('Send')}</Text> </TouchableHighlight> </View> ); } 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} />); } render() { const { window: { width }, isVisible, close, file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props; const { name, description } = this.state; const showError = !canUploadFile(file, { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize }); return ( <Modal isVisible={isVisible} style={styles.modal} onBackdropPress={close} onBackButtonPress={close} animationIn='fadeIn' animationOut='fadeOut' useNativeDriver hideModalContentWhileAnimating avoidKeyboard > {(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> )} </Modal> ); } } const mapStateToProps = state => ({ FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize }); export default responsive(connect(mapStateToProps)(UploadModal));