Rocket.Chat.ReactNative/app/views/ShareView/index.tsx

381 lines
11 KiB
TypeScript
Raw Normal View History

import React, { Component } from 'react';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { NativeModules, Text, View } from 'react-native';
2019-07-18 17:44:02 +00:00
import { connect } from 'react-redux';
import ShareExtension from 'rn-extensions-share';
import { Q } from '@nozbe/watermelondb';
2019-07-18 17:44:02 +00:00
import { InsideStackParamList } from '../../stacks/types';
import { themes } from '../../lib/constants';
2019-07-18 17:44:02 +00:00
import I18n from '../../i18n';
import Loading from '../../containers/Loading';
import * as HeaderButton from '../../containers/HeaderButton';
import { isBlocked } from '../../lib/methods/helpers/room';
import { isReadOnly } from '../../lib/methods/helpers/isReadOnly';
import { TSupportedThemes, withTheme } from '../../theme';
import FormTextInput from '../../containers/TextInput/FormTextInput';
import MessageBox from '../../containers/MessageBox';
import SafeAreaView from '../../containers/SafeAreaView';
import { getUserSelector } from '../../selectors/login';
import StatusBar from '../../containers/StatusBar';
import database from '../../lib/database';
import { canUploadFile } from '../../lib/methods/helpers/media';
import Thumbs from './Thumbs';
import Preview from './Preview';
import Header from './Header';
import styles from './styles';
import { IApplicationState, IServer, IShareAttachment, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
import { sendFileMessage, sendMessage } from '../../lib/methods';
import { hasPermission, isAndroid } from '../../lib/methods/helpers';
interface IShareViewState {
selected: IShareAttachment;
loading: boolean;
readOnly: boolean;
attachments: IShareAttachment[];
text: string;
room: TSubscriptionModel;
thread: TThreadModel;
maxFileSize?: number;
mediaAllowList?: string;
}
interface IShareViewProps {
navigation: StackNavigationProp<InsideStackParamList, 'ShareView'>;
route: RouteProp<InsideStackParamList, 'ShareView'>;
theme: TSupportedThemes;
user: {
id: string;
username: string;
token: string;
};
server: string;
FileUpload_MediaTypeWhiteList?: string;
FileUpload_MaxFileSize?: number;
}
2019-07-18 17:44:02 +00:00
interface IMessageBoxShareView {
text: string;
forceUpdate(): void;
}
class ShareView extends Component<IShareViewProps, IShareViewState> {
private messagebox: React.RefObject<IMessageBoxShareView>;
private files: any[];
private isShareExtension: boolean;
private serverInfo: IServer;
constructor(props: IShareViewProps) {
2019-07-18 17:44:02 +00:00
super(props);
this.messagebox = React.createRef();
this.files = props.route.params?.attachments ?? [];
this.isShareExtension = props.route.params?.isShareExtension;
this.serverInfo = props.route.params?.serverInfo ?? {};
2019-07-18 17:44:02 +00:00
this.state = {
selected: {} as IShareAttachment,
2019-07-18 17:44:02 +00:00
loading: false,
readOnly: false,
attachments: [],
text: props.route.params?.text ?? '',
room: props.route.params?.room ?? {},
thread: props.route.params?.thread ?? {},
maxFileSize: this.isShareExtension ? this.serverInfo?.FileUpload_MaxFileSize : props.FileUpload_MaxFileSize,
mediaAllowList: this.isShareExtension ? this.serverInfo?.FileUpload_MediaTypeWhiteList : props.FileUpload_MediaTypeWhiteList
2019-07-18 17:44:02 +00:00
};
this.getServerInfo();
}
componentDidMount = async () => {
const readOnly = await this.getReadOnly();
const { attachments, selected } = await this.getAttachments();
this.setState({ readOnly, attachments, selected }, () => this.setHeader());
};
componentWillUnmount = () => {
console.countReset(`${this.constructor.name}.render calls`);
};
2019-07-18 17:44:02 +00:00
[CHORE] Update react-navigation to v5 (#2154) * react-navigation v5 installed * compiling * Outside working * InsideStack compiling * Switch stack * Starting room * RoomView header * SafeAreaView * Slide from right stack animation * stash * Fix params * Create channel * inapp notification * Custom status * Add server working * Refactor appStart * Attachment * in-app notification * AuthLoadingView * Remove compat * Navigation * Outside animations * Fix new server icon * block modal * AttachmentView header * Remove unnecessary code * SelectedUsersView header * StatusView * CreateDiscussionView * RoomInfoView * RoomInfoEditView style * RoomMembersView * RoomsListView header * RoomView header * Share extension * getParam * Focus/blur * Trying to fix inapp * Lint * Simpler app container * Update libs * Revert "Simpler app container" This reverts commit 1e49d80bb49481c34f415831b9da5e9d53e66057. * Load messages faster * Fix safearea on ReactionsModal * Update safe area to v3 * lint * Fix transition * stash - drawer replace working * stash - modal nav * RoomActionsView as tablet modal * RoomStack * Stop showing RoomView header when there's no room * Custom Header and different navigation based on stack * Refactor setHeader * MasterDetailContext * RoomView header * Fix isMasterDetail rule * KeyCommands kind of working * Create channel on tablet * RoomView sCU * Remove withSplit * Settings opening as modal * Settings * StatusView headerLeft * Admin panel * TwoFactor style * DirectoryView * ServerDropdown and SortDropdown animations * ThreadMessagesView * Navigate to empty RoomView on server switch when in master detail * ProfileView header * Fix navigation issues * Nav to any room info on tablet * Room info * Refactoring * Fix rooms search * Roomslist commands * SearchMessagesView close modal * Key commands * Fix undefined subscription * Disallow navigate to focused room * isFocused state on RoomsListView * Blur text inputs when focus is lost * Replace animation * Default nav theme * Refactoring * Always open Attachment with close modal button * ModalContainer backdrop following themes * Screen tracking * Refactor get active route for in-app notification * Only mark room as focused when in master detail layout * Lint * Open modals as fade from bottom on Android * typo * Fixing tests * Fix in-app update * Fixing goRoom issues * Refactor stack names * Fix unreadsCount * Fix stack * Fix header animation * Refactor ShareNavigation * Refactor navigation theme * Make sure title is set * Fix create discussion navigation * Remove unused variable * Create discussions from actions fixed * Layout animation * Screen lock on share extension * Unnecessary change * Admin border * Set header after state callback * Fix key commands on outside stack * Fix back button pressed * Remove layout animations from Android * Tweak animations on Android * Disable swipe gesture to open drawer * Fix current item on RoomsListView * Fix add server * Fix drawer * Fix broadcast * LayoutAnimation instead of Transitions * Fix onboarding back press * Fix assorted tests * Create discussion fix * RoomInfoView header * Drawer active item
2020-06-15 14:00:46 +00:00
setHeader = () => {
const { room, thread, readOnly, attachments } = this.state;
const { navigation, theme } = this.props;
const options: StackNavigationOptions = {
headerTitle: () => <Header room={room} thread={thread} />,
headerTitleAlign: 'left',
headerTintColor: themes[theme].previewTintColor
};
// if is share extension show default back button
if (!this.isShareExtension) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
}
if (!attachments.length && !readOnly) {
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Send')} onPress={this.send} />
</HeaderButton.Container>
);
}
options.headerBackground = () => <View style={[styles.container, { backgroundColor: themes[theme].previewBackground }]} />;
navigation.setOptions(options);
};
// fetch server info
getServerInfo = async () => {
const { server } = this.props;
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
try {
this.serverInfo = await serversCollection.find(server);
} catch (error) {
// Do nothing
}
};
getPermissionMobileUpload = async () => {
const { room } = this.state;
const db = database.active;
const permissionsCollection = db.get('permissions');
const uploadFilePermissionFetch = await permissionsCollection.query(Q.where('id', Q.like('mobile-upload-file'))).fetch();
const uploadFilePermission = uploadFilePermissionFetch[0]?.roles;
const permissionToUpload = await hasPermission([uploadFilePermission], room.rid);
// uploadFilePermission as undefined is considered that there isn't this permission, so all can upload file.
return !uploadFilePermission || permissionToUpload[0];
};
getReadOnly = async () => {
2019-07-18 17:44:02 +00:00
const { room } = this.state;
[CHORE] Update react-navigation to v5 (#2154) * react-navigation v5 installed * compiling * Outside working * InsideStack compiling * Switch stack * Starting room * RoomView header * SafeAreaView * Slide from right stack animation * stash * Fix params * Create channel * inapp notification * Custom status * Add server working * Refactor appStart * Attachment * in-app notification * AuthLoadingView * Remove compat * Navigation * Outside animations * Fix new server icon * block modal * AttachmentView header * Remove unnecessary code * SelectedUsersView header * StatusView * CreateDiscussionView * RoomInfoView * RoomInfoEditView style * RoomMembersView * RoomsListView header * RoomView header * Share extension * getParam * Focus/blur * Trying to fix inapp * Lint * Simpler app container * Update libs * Revert "Simpler app container" This reverts commit 1e49d80bb49481c34f415831b9da5e9d53e66057. * Load messages faster * Fix safearea on ReactionsModal * Update safe area to v3 * lint * Fix transition * stash - drawer replace working * stash - modal nav * RoomActionsView as tablet modal * RoomStack * Stop showing RoomView header when there's no room * Custom Header and different navigation based on stack * Refactor setHeader * MasterDetailContext * RoomView header * Fix isMasterDetail rule * KeyCommands kind of working * Create channel on tablet * RoomView sCU * Remove withSplit * Settings opening as modal * Settings * StatusView headerLeft * Admin panel * TwoFactor style * DirectoryView * ServerDropdown and SortDropdown animations * ThreadMessagesView * Navigate to empty RoomView on server switch when in master detail * ProfileView header * Fix navigation issues * Nav to any room info on tablet * Room info * Refactoring * Fix rooms search * Roomslist commands * SearchMessagesView close modal * Key commands * Fix undefined subscription * Disallow navigate to focused room * isFocused state on RoomsListView * Blur text inputs when focus is lost * Replace animation * Default nav theme * Refactoring * Always open Attachment with close modal button * ModalContainer backdrop following themes * Screen tracking * Refactor get active route for in-app notification * Only mark room as focused when in master detail layout * Lint * Open modals as fade from bottom on Android * typo * Fixing tests * Fix in-app update * Fixing goRoom issues * Refactor stack names * Fix unreadsCount * Fix stack * Fix header animation * Refactor ShareNavigation * Refactor navigation theme * Make sure title is set * Fix create discussion navigation * Remove unused variable * Create discussions from actions fixed * Layout animation * Screen lock on share extension * Unnecessary change * Admin border * Set header after state callback * Fix key commands on outside stack * Fix back button pressed * Remove layout animations from Android * Tweak animations on Android * Disable swipe gesture to open drawer * Fix current item on RoomsListView * Fix add server * Fix drawer * Fix broadcast * LayoutAnimation instead of Transitions * Fix onboarding back press * Fix assorted tests * Create discussion fix * RoomInfoView header * Drawer active item
2020-06-15 14:00:46 +00:00
const { user } = this.props;
const readOnly = await isReadOnly(room, user.username);
return readOnly;
};
2019-07-18 17:44:02 +00:00
getAttachments = async () => {
const { mediaAllowList, maxFileSize } = this.state;
const permissionToUploadFile = await this.getPermissionMobileUpload();
const items = await Promise.all(
this.files.map(async item => {
// Check server settings
const { success: canUpload, error } = canUploadFile({
file: item,
allowList: mediaAllowList,
maxFileSize,
permissionToUploadFile
});
item.canUpload = canUpload;
item.error = error;
// get video thumbnails
if (isAndroid && this.files.length > 1 && item.mime?.match?.(/video/)) {
try {
const VideoThumbnails = require('expo-video-thumbnails');
const { uri } = await VideoThumbnails.getThumbnailAsync(item.path);
item.uri = uri;
} catch {
// Do nothing
}
}
// Set a filename, if there isn't any
if (!item.filename) {
item.filename = `${new Date().toISOString()}.jpg`;
}
return item;
})
);
return {
attachments: items,
selected: items[0]
};
};
2019-07-18 17:44:02 +00:00
send = async () => {
const { loading, selected } = this.state;
if (loading) {
return;
}
2019-07-18 17:44:02 +00:00
// update state
await this.selectFile(selected);
const { attachments, room, text, thread } = this.state;
const { navigation, server, user } = this.props;
// if it's share extension this should show loading
if (this.isShareExtension) {
this.setState({ loading: true });
// if it's not share extension this can close
2019-07-18 17:44:02 +00:00
} else {
navigation.pop();
2019-07-18 17:44:02 +00:00
}
try {
// Send attachment
if (attachments.length) {
await Promise.all(
attachments.map(({ filename: name, mime: type, description, size, path, canUpload }) => {
if (canUpload) {
return sendFileMessage(
room.rid,
{
name,
description,
size,
type,
path,
store: 'Uploads'
},
thread?.id,
server,
{ id: user.id, token: user.token }
);
}
return Promise.resolve();
})
);
// Send text message
} else if (text.length) {
await sendMessage(room.rid, text, thread?.id, { id: user.id, token: user.token } as IUser);
2019-07-18 17:44:02 +00:00
}
} catch {
// Do nothing
2019-07-18 17:44:02 +00:00
}
// if it's share extension this should close
if (this.isShareExtension) {
ShareExtension.close();
2019-07-18 17:44:02 +00:00
}
};
selectFile = (item: IShareAttachment) => {
const { attachments, selected } = this.state;
if (attachments.length > 0) {
const text = this.messagebox.current?.text;
const newAttachments = attachments.map(att => {
if (att.path === selected.path) {
att.description = text;
}
return att;
});
return this.setState({ attachments: newAttachments, selected: item });
}
};
2019-07-18 17:44:02 +00:00
removeFile = (item: IShareAttachment) => {
const { selected, attachments } = this.state;
let newSelected;
if (item.path === selected.path) {
const selectedIndex = attachments.findIndex(att => att.path === selected.path);
// Selects the next one, if available
if (attachments[selectedIndex + 1]?.path) {
newSelected = attachments[selectedIndex + 1];
// If it's the last thumb, selects the previous one
} else {
newSelected = attachments[selectedIndex - 1] || {};
}
}
this.setState({ attachments: attachments.filter(att => att.path !== item.path), selected: newSelected ?? selected }, () => {
this.messagebox?.current?.forceUpdate?.();
});
};
2019-07-18 17:44:02 +00:00
onChangeText = (text: string) => {
this.setState({ text });
};
2019-07-18 17:44:02 +00:00
renderContent = () => {
const { attachments, selected, room, text } = this.state;
const { theme, navigation } = this.props;
if (attachments.length) {
return (
<View style={styles.container}>
<Preview
// using key just to reset zoom/move after change selected
key={selected?.path}
item={selected}
length={attachments.length}
2019-12-04 16:39:53 +00:00
theme={theme}
isShareExtension={this.isShareExtension}
2019-07-18 17:44:02 +00:00
/>
<MessageBox
showSend
sharing
ref={this.messagebox}
rid={room.rid}
roomType={room.t}
2019-12-04 16:39:53 +00:00
theme={theme}
onSubmit={this.send}
message={{ msg: selected?.description ?? '' }}
navigation={navigation}
isFocused={navigation.isFocused}
iOSScrollBehavior={NativeModules.KeyboardTrackingViewManager?.KeyboardTrackingScrollBehaviorNone}
isActionsEnabled={false}>
<Thumbs
attachments={attachments}
theme={theme}
isShareExtension={this.isShareExtension}
onPress={this.selectFile}
onRemove={this.removeFile}
/>
</MessageBox>
2019-07-18 17:44:02 +00:00
</View>
);
}
2019-07-18 17:44:02 +00:00
return (
<FormTextInput
containerStyle={styles.inputContainer}
inputStyle={[styles.input, styles.textInput, { backgroundColor: themes[theme].focusedBackground }]}
2019-07-18 17:44:02 +00:00
placeholder=''
onChangeText={this.onChangeText}
defaultValue=''
2019-07-18 17:44:02 +00:00
multiline
textAlignVertical='top'
autoFocus
2019-12-04 16:39:53 +00:00
theme={theme}
value={text}
2019-07-18 17:44:02 +00:00
/>
);
};
2019-07-18 17:44:02 +00:00
render() {
console.count(`${this.constructor.name}.render calls`);
const { readOnly, room, loading } = this.state;
const { theme } = this.props;
if (readOnly || isBlocked(room)) {
return (
<View style={[styles.container, styles.centered, { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.title, { color: themes[theme].titleText }]}>
{isBlocked(room) ? I18n.t('This_room_is_blocked') : I18n.t('This_room_is_read_only')}
2019-07-18 17:44:02 +00:00
</Text>
</View>
);
}
return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} />
{this.renderContent()}
<Loading visible={loading} />
</SafeAreaView>
2019-07-18 17:44:02 +00:00
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
server: state.share.server.server || state.server.server,
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList as string,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize as number
});
2019-12-04 16:39:53 +00:00
export default connect(mapStateToProps)(withTheme(ShareView));