feat: add draft and quote on shareview support (#5540)

This commit is contained in:
Gleidson Daniel Silva 2024-02-07 15:57:22 -03:00 committed by GitHub
parent 27c716c267
commit b81e22c934
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 146 additions and 45 deletions

View File

@ -7,12 +7,12 @@ import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
import I18n from '../../../i18n';
import { IAutocompleteItemProps, IComposerInput, IComposerInputProps, IInputSelection, TSetInput } from '../interfaces';
import { useAutocompleteParams, useFocused, useMessageComposerApi } from '../context';
import { loadDraftMessage, saveDraftMessage, fetchIsAllOrHere, getMentionRegexp } from '../helpers';
import { useSubscription } from '../hooks';
import { loadDraftMessage, fetchIsAllOrHere, getMentionRegexp } from '../helpers';
import { useSubscription, useAutoSaveDraft } from '../hooks';
import sharedStyles from '../../../views/Styles';
import { useTheme } from '../../../theme';
import { userTyping } from '../../../actions/room';
import { getRoomTitle } from '../../../lib/methods/helpers';
import { getRoomTitle, parseJson } from '../../../lib/methods/helpers';
import { MAX_HEIGHT, MIN_HEIGHT, NO_CANNED_RESPONSES, MARKDOWN_STYLES } from '../constants';
import database from '../../../lib/database';
import Navigation from '../../../lib/navigation/appNavigation';
@ -30,11 +30,12 @@ const defaultSelection: IInputSelection = { start: 0, end: 0 };
export const ComposerInput = memo(
forwardRef<IComposerInput, IComposerInputProps>(({ inputRef }, ref) => {
const { colors, theme } = useTheme();
const { rid, tmid, sharing, action, selectedMessages } = useRoomContext();
const { rid, tmid, sharing, action, selectedMessages, setQuotesAndText } = useRoomContext();
const focused = useFocused();
const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi();
const autocompleteType = useAutocompleteParams()?.type;
const textRef = React.useRef('');
const firstRender = React.useRef(false);
const selectionRef = React.useRef<IInputSelection>(defaultSelection);
const dispatch = useDispatch();
const subscription = useSubscription(rid);
@ -47,29 +48,29 @@ export const ComposerInput = memo(
const usedCannedResponse = route.params?.usedCannedResponse;
const prevAction = usePrevious(action);
useAutoSaveDraft(textRef.current);
// Draft/Canned Responses
useEffect(() => {
const setDraftMessage = async () => {
const draftMessage = await loadDraftMessage({ rid, tmid });
if (draftMessage) {
setInput(draftMessage);
const parsedDraft = parseJson(draftMessage);
if (parsedDraft?.msg || parsedDraft?.quotes) {
setQuotesAndText?.(parsedDraft.msg, parsedDraft.quotes);
} else {
setInput(draftMessage);
}
}
};
if (sharing) return;
if (usedCannedResponse) {
setInput(usedCannedResponse);
} else if (action !== 'edit') {
if (usedCannedResponse) setInput(usedCannedResponse);
if (action !== 'edit' && !firstRender.current) {
firstRender.current = true;
setDraftMessage();
}
return () => {
if (action !== 'edit') {
saveDraftMessage({ rid, tmid, draftMessage: textRef.current });
}
};
}, [action, rid, tmid, usedCannedResponse]);
}, [action, rid, tmid, usedCannedResponse, firstRender.current]);
// Edit/quote
useEffect(() => {
@ -91,7 +92,7 @@ export const ComposerInput = memo(
fetchMessageAndSetInput();
return;
}
if (action === 'quote' && selectedMessages.length === 1) {
if (action === 'quote' && selectedMessages.length) {
focus();
}
}, [action, selectedMessages]);

View File

@ -1,4 +1,5 @@
export * from './useAutocomplete';
export * from './useAutoSaveDraft';
export * from './useCanUploadFile';
export * from './useChooseMedia';
export * from './useKeyboardListener';

View File

@ -0,0 +1,35 @@
import { useCallback, useEffect, useRef } from 'react';
import { saveDraftMessage } from '../helpers';
import { useRoomContext } from '../../../views/RoomView/context';
import { useFocused } from '../context';
export const useAutoSaveDraft = (text = '') => {
const { rid, tmid, action, selectedMessages } = useRoomContext();
const focused = useFocused();
const oldText = useRef('');
const intervalRef = useRef();
const saveMessageDraft = useCallback(() => {
if (action === 'edit') return;
const draftMessage = selectedMessages?.length ? JSON.stringify({ quotes: selectedMessages, msg: text }) : text;
if (oldText.current !== draftMessage) {
oldText.current = draftMessage;
saveDraftMessage({ rid, tmid, draftMessage });
}
}, [action, rid, tmid, text, selectedMessages?.length]);
useEffect(() => {
if (focused) {
intervalRef.current = setInterval(saveMessageDraft, 3000) as any;
} else {
clearInterval(intervalRef.current);
}
return () => {
clearInterval(intervalRef.current);
saveMessageDraft();
};
}, [focused, saveMessageDraft]);
};

View File

@ -23,7 +23,7 @@ export const useChooseMedia = ({
permissionToUpload: boolean;
}) => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = useAppSelector(state => state.settings);
const { action, selectedMessages } = useRoomContext();
const { action, setQuotesAndText, selectedMessages, getText } = useRoomContext();
const allowList = FileUpload_MediaTypeWhiteList as string;
const maxFileSize = FileUpload_MaxFileSize as number;
const libPickerLabels = {
@ -115,6 +115,16 @@ export const useChooseMedia = ({
}
};
const startShareView = () => {
const text = getText?.() || '';
return {
selectedMessages,
text
};
};
const finishShareView = (text = '', quotes = []) => setQuotesAndText?.(text, quotes);
const openShareView = async (attachments: any) => {
if (!rid) return;
const room = await getSubscriptionByRoomId(rid);
@ -129,7 +139,8 @@ export const useChooseMedia = ({
thread,
attachments,
action,
selectedMessages
finishShareView,
startShareView
});
}
};

View File

@ -16,3 +16,4 @@ export * from './isValidEmail';
export * from './random';
export * from './image';
export * from './askAndroidMediaPermissions';
export * from './parseJson';

View File

@ -0,0 +1,7 @@
export const parseJson = (json: string) => {
try {
return JSON.parse(json);
} catch (ex) {
return json;
}
};

View File

@ -274,7 +274,8 @@ export type InsideStackParamList = {
room: TSubscriptionModel;
thread: TThreadModel;
action: TMessageAction;
selectedMessages: string[];
finishShareView: (text?: string, selectedMessages?: string[]) => void | undefined;
startShareView: () => { text: string; selectedMessages: string[] };
};
ModalBlockView: {
data: any; // TODO: Change;

View File

@ -9,11 +9,12 @@ export interface IRoomContext {
sharing?: boolean;
action?: TMessageAction;
selectedMessages: string[];
editCancel?: () => void;
editRequest?: (message: any) => void;
onRemoveQuoteMessage?: (messageId: string) => void;
onSendMessage?: Function;
setQuotesAndText?: (text: string, quotes: string[]) => void;
getText?: () => string | undefined;
}
export const RoomContext = createContext<IRoomContext>({} as IRoomContext);

View File

@ -1250,6 +1250,17 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}
};
setQuotesAndText = (text: string, quotes: string[]) => {
if (quotes.length) {
this.setState({ selectedMessages: quotes, action: 'quote' });
} else {
this.setState({ action: null, selectedMessages: [] });
}
this.messageComposerRef.current?.setInput(text || '');
};
getText = () => this.messageComposerRef.current?.getText();
renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => {
const { room, lastOpen, canAutoTranslate } = this.state;
const {
@ -1454,7 +1465,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
onRemoveQuoteMessage: this.onRemoveQuoteMessage,
editCancel: this.onEditCancel,
editRequest: this.onEditRequest,
onSendMessage: this.handleSendMessage
onSendMessage: this.handleSendMessage,
setQuotesAndText: this.setQuotesAndText,
getText: this.getText
}}
>
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='room-view'>

View File

@ -23,7 +23,15 @@ 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 {
IApplicationState,
IServer,
IShareAttachment,
IUser,
TMessageAction,
TSubscriptionModel,
TThreadModel
} from '../../definitions';
import { sendFileMessage, sendMessage } from '../../lib/methods';
import { hasPermission, isAndroid, canUploadFile, isReadOnly, isBlocked } from '../../lib/methods/helpers';
import { RoomContext } from '../RoomView/context';
@ -38,6 +46,8 @@ interface IShareViewState {
thread: TThreadModel;
maxFileSize?: number;
mediaAllowList?: string;
selectedMessages: string[];
action: TMessageAction;
}
interface IShareViewProps {
@ -59,7 +69,8 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
private files: any[];
private isShareExtension: boolean;
private serverInfo: IServer;
private closeReply?: Function;
private finishShareView: (text?: string, selectedMessages?: string[]) => void;
private sentMessage: boolean;
constructor(props: IShareViewProps) {
super(props);
@ -67,6 +78,8 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
this.files = props.route.params?.attachments ?? [];
this.isShareExtension = props.route.params?.isShareExtension;
this.serverInfo = props.route.params?.serverInfo ?? {};
this.finishShareView = props.route.params?.finishShareView;
this.sentMessage = false;
this.state = {
selected: {} as IShareAttachment,
@ -77,7 +90,11 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
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
mediaAllowList: this.isShareExtension
? this.serverInfo?.FileUpload_MediaTypeWhiteList
: props.FileUpload_MediaTypeWhiteList,
selectedMessages: [],
action: props.route.params?.action
};
this.getServerInfo();
}
@ -86,16 +103,15 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
const readOnly = await this.getReadOnly();
const { attachments, selected } = await this.getAttachments();
this.setState({ readOnly, attachments, selected }, () => this.setHeader());
this.startShareView();
};
componentWillUnmount = () => {
console.countReset(`${this.constructor.name}.render calls`);
// close reply from the RoomView
setTimeout(() => {
if (this.closeReply) {
this.closeReply();
}
}, 300);
if (this.finishShareView && !this.sentMessage) {
const text = this.messageComposerRef.current?.getText();
this.finishShareView(text, this.state.selectedMessages);
}
};
setHeader = () => {
@ -196,21 +212,23 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
};
};
send = async () => {
const { loading, selected } = this.state;
if (loading) {
return;
startShareView = () => {
const startShareView = this.props.route.params?.startShareView;
if (startShareView) {
const { selectedMessages, text } = startShareView();
this.messageComposerRef.current?.setInput(text);
this.setState({ selectedMessages });
}
};
send = async () => {
if (this.state.loading) return;
const { attachments, room, text, thread, action, selected, selectedMessages } = this.state;
const { navigation, server, user } = this.props;
// update state
await this.selectFile(selected);
const { attachments, room, text, thread } = this.state;
const { navigation, server, user, route } = this.props;
const action = route.params?.action;
const selectedMessages = route.params?.selectedMessages ?? [];
// if it's share extension this should show loading
if (this.isShareExtension) {
this.setState({ loading: true });
@ -218,6 +236,8 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
// if it's not share extension this can close
} else {
this.sentMessage = true;
this.finishShareView('', []);
navigation.pop();
}
@ -257,7 +277,10 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
await sendMessage(room.rid, text, thread?.id, { id: user.id, token: user.token } as IUser);
}
} catch {
// Do nothing
if (!this.isShareExtension) {
const text = this.messageComposerRef.current?.getText();
this.finishShareView(text, this.state.selectedMessages);
}
}
// if it's share extension this should close
@ -303,8 +326,14 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
this.setState({ text });
};
onRemoveQuoteMessage = (messageId: string) => {
const { selectedMessages } = this.state;
const newSelectedMessages = selectedMessages.filter(item => item !== messageId);
this.setState({ selectedMessages: newSelectedMessages, action: newSelectedMessages.length ? 'quote' : null });
};
renderContent = () => {
const { attachments, selected, text, room, thread } = this.state;
const { attachments, selected, text, room, thread, selectedMessages } = this.state;
const { theme, route } = this.props;
if (attachments.length) {
@ -316,8 +345,9 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
tmid: thread.id,
sharing: true,
action: route.params?.action,
selectedMessages: route.params?.selectedMessages,
onSendMessage: this.send
selectedMessages,
onSendMessage: this.send,
onRemoveQuoteMessage: this.onRemoveQuoteMessage
}}
>
<View style={styles.container}>