import React from 'react'; import PropTypes from 'prop-types'; import { View, Text, LayoutAnimation, FlatList, ActivityIndicator, Keyboard, BackHandler } from 'react-native'; import { SafeAreaView } from 'react-navigation'; import ShareExtension from 'rn-extensions-share'; import { connect } from 'react-redux'; import RNFetchBlob from 'rn-fetch-blob'; import * as mime from 'react-native-mime-types'; import { isEqual } from 'lodash'; import Navigation from '../../lib/ShareNavigation'; import database from '../../lib/realm'; import { isIOS, isAndroid } from '../../utils/deviceInfo'; import I18n from '../../i18n'; import { CustomIcon } from '../../lib/Icons'; import log from '../../utils/log'; import DirectoryItem, { ROW_HEIGHT } from '../../presentation/DirectoryItem'; import ServerItem from '../../presentation/ServerItem'; import { CloseShareExtensionButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton'; import ShareListHeader from './Header'; import styles from './styles'; import StatusBar from '../../containers/StatusBar'; const LIMIT = 50; const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }); const keyExtractor = item => item.rid; @connect(({ share }) => ({ userId: share.user && share.user.id, token: share.user && share.user.token, server: share.server, baseUrl: share ? share.server : '' })) /** @extends React.Component */ export default class ShareListView extends React.Component { static navigationOptions = ({ navigation }) => { const searching = navigation.getParam('searching'); const initSearch = navigation.getParam('initSearch', () => {}); const cancelSearch = navigation.getParam('cancelSearch', () => {}); const search = navigation.getParam('search', () => {}); if (isIOS) { return { headerTitle: ( ) }; } return { headerBackTitle: null, headerLeft: searching ? ( ) : ( ), headerTitle: , headerRight: ( searching ? null : ( {isAndroid ? : null} ) ) }; } static propTypes = { navigation: PropTypes.object, server: PropTypes.string, baseUrl: PropTypes.string, token: PropTypes.string, userId: PropTypes.string } constructor(props) { super(props); this.data = []; this.state = { showError: false, searching: false, searchText: '', value: '', isMedia: false, mediaLoading: false, fileInfo: null, searchResults: [], chats: [], servers: [], loading: true, serverInfo: null }; this.didFocusListener = props.navigation.addListener('didFocus', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)); this.willBlurListener = props.navigation.addListener('willBlur', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)); } async componentDidMount() { const { navigation, server } = this.props; navigation.setParams({ initSearch: this.initSearch, cancelSearch: this.cancelSearch, search: this.search }); try { const { value, type } = await ShareExtension.data(); let fileInfo = null; const isMedia = (type === 'media'); if (isMedia) { this.setState({ mediaLoading: true }); const data = await RNFetchBlob.fs.stat(this.uriToPath(value)); fileInfo = { name: data.filename, description: '', size: data.size, type: mime.lookup(data.path), store: 'Uploads', path: isIOS ? data.path : `file://${ data.path }` }; } this.setState({ value, fileInfo, isMedia, mediaLoading: false }); } catch (e) { log('err_process_media_share_extension', e); this.setState({ mediaLoading: false }); } this.getSubscriptions(server); } componentWillReceiveProps(nextProps) { const { server } = this.props; if (nextProps.server !== server) { this.getSubscriptions(nextProps.server); } } shouldComponentUpdate(nextProps, nextState) { const { searching } = this.state; if (nextState.searching !== searching) { return true; } const { isMedia } = this.state; if (nextState.isMedia !== isMedia) { this.getSubscriptions(nextProps.server, nextState.fileInfo); return true; } const { server } = this.props; if (server !== nextProps.server) { return true; } const { searchResults } = this.state; if (!isEqual(nextState.searchResults, searchResults)) { return true; } return false; } // eslint-disable-next-line react/sort-comp internalSetState = (...args) => { const { navigation } = this.props; if (isIOS && navigation.isFocused()) { LayoutAnimation.easeInEaseOut(); } this.setState(...args); } getSubscriptions = (server, fileInfo) => { const { serversDB } = database.databases; if (server) { this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true); this.servers = serversDB.objects('servers'); this.chats = this.data.slice(0, LIMIT); const serverInfo = serversDB.objectForPrimaryKey('servers', server); this.internalSetState({ chats: this.chats ? this.chats.slice() : [], servers: this.servers ? this.servers.slice() : [], loading: false, showError: !this.canUploadFile(serverInfo, fileInfo), serverInfo }); this.forceUpdate(); } }; uriToPath = uri => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri); getRoomTitle = (item) => { const { serverInfo } = this.state; const { useRealName } = serverInfo; return ((item.prid || useRealName) && item.fname) || item.name; } shareMessage = (item) => { const { value, isMedia, fileInfo } = this.state; const { navigation } = this.props; navigation.navigate('ShareView', { rid: item.rid, value, isMedia, fileInfo, name: this.getRoomTitle(item) }); } canUploadFile = (serverInfo, fileInfo) => { const { fileInfo: fileData } = this.state; const file = fileInfo || fileData; const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = serverInfo; if (!(file && file.path)) { return true; } if (file.size > FileUpload_MaxFileSize) { return false; } if (!FileUpload_MediaTypeWhiteList) { return false; } const allowedMime = FileUpload_MediaTypeWhiteList.split(','); if (allowedMime.includes(file.type)) { return true; } const wildCardGlob = '/*'; const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0); if (wildCards.includes(file.type.replace(/(\/.*)$/, wildCardGlob))) { return true; } return false; } search = (text) => { const result = database.objects('subscriptions').filtered('name CONTAINS[c] $0', text); this.internalSetState({ searchResults: result.slice(0, LIMIT), searchText: text }); } initSearch = () => { const { chats } = this.state; const { navigation } = this.props; this.setState({ searching: true, searchResults: chats }); navigation.setParams({ searching: true }); } cancelSearch = () => { const { navigation } = this.props; this.internalSetState({ searching: false, searchResults: [], searchText: '' }); navigation.setParams({ searching: false }); Keyboard.dismiss(); } handleBackPress = () => { const { searching } = this.state; if (searching) { this.cancelSearch(); return true; } return false; } renderSectionHeader = (header) => { const { searching } = this.state; if (searching) { return null; } return ( {I18n.t(header)} ); } renderItem = ({ item }) => { const { userId, token, baseUrl } = this.props; return ( this.shareMessage(item)} testID={`share-extension-item-${ item.name }`} /> ); } renderSeparator = () => ; renderBorderBottom = () => ; renderSelectServer = () => { const { servers } = this.state; const { server } = this.props; const currentServer = servers.find(serverFiltered => serverFiltered.id === server); return currentServer ? ( {this.renderSectionHeader('Select_Server')} Navigation.navigate('SelectServerView')} item={currentServer} /> ) : null; } renderEmptyComponent = () => ( {I18n.t('No_results_found')} ); renderHeader = () => { const { searching } = this.state; return ( { !searching ? ( {this.renderSelectServer()} {this.renderSectionHeader('Chats')} ) : null } ); } renderContent = () => { const { chats, mediaLoading, loading, searchResults, searching, searchText } = this.state; if (mediaLoading || loading) { return ; } return ( ); } renderError = () => { const { fileInfo: file, loading, searching, serverInfo } = this.state; const { FileUpload_MaxFileSize } = serverInfo; const errorMessage = (FileUpload_MaxFileSize < file.size) ? 'error-file-too-large' : 'error-invalid-file-type'; if (loading) { return ; } return ( { !searching ? ( {this.renderSelectServer()} ) : null } {I18n.t(errorMessage)} { file.type } ); } render() { const { showError } = this.state; return ( { showError ? this.renderError() : this.renderContent() } ); } }