Merge branch 'develop' into feat.watch
This commit is contained in:
commit
142d7a38bc
File diff suppressed because one or more lines are too long
|
@ -30,6 +30,7 @@ import log from '../../lib/methods/helpers/log';
|
|||
import { prepareQuoteMessage } from './helpers';
|
||||
import { RecordAudio } from './components/RecordAudio';
|
||||
import { useKeyboardListener } from './hooks';
|
||||
import { emitter } from '../../lib/methods/helpers/emitter';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -193,6 +194,7 @@ export const MessageComposer = ({
|
|||
|
||||
const onHeightChanged = (height: number) => {
|
||||
setTrackingViewHeight(height);
|
||||
emitter.emit(`setComposerHeight${tmid ? 'Thread' : ''}`, height);
|
||||
};
|
||||
|
||||
const backgroundColor = action === 'edit' ? colors.statusBackgroundWarning2 : colors.surfaceLight;
|
||||
|
|
|
@ -7,16 +7,16 @@ 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';
|
||||
import { emitter } from '../emitter';
|
||||
import { emitter } from '../../../lib/methods/helpers/emitter';
|
||||
import { useRoomContext } from '../../../views/RoomView/context';
|
||||
import { getMessageById } from '../../../lib/database/services/Message';
|
||||
import { generateTriggerId } from '../../../lib/methods';
|
||||
|
@ -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]);
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { ReactElement } from 'react';
|
|||
import { ActionsButton, BaseButton } from '..';
|
||||
import { useMessageComposerApi } from '../../context';
|
||||
import { Gap } from '../Gap';
|
||||
import { emitter } from '../../emitter';
|
||||
import { emitter } from '../../../../lib/methods/helpers/emitter';
|
||||
import { useRoomContext } from '../../../../views/RoomView/context';
|
||||
|
||||
export const Default = (): ReactElement | null => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { BaseButton } from '..';
|
|||
import { useMessageComposerApi } from '../../context';
|
||||
import { Gap } from '../Gap';
|
||||
import { TMarkdownStyle } from '../../interfaces';
|
||||
import { emitter } from '../../emitter';
|
||||
import { emitter } from '../../../../lib/methods/helpers/emitter';
|
||||
|
||||
export const Markdown = (): ReactElement => {
|
||||
const { setMarkdownToolbar } = useMessageComposerApi();
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import mitt from 'mitt';
|
||||
|
||||
import { TMarkdownStyle } from './interfaces';
|
||||
|
||||
type Events = {
|
||||
toolbarMention: undefined;
|
||||
addMarkdown: {
|
||||
style: TMarkdownStyle;
|
||||
};
|
||||
};
|
||||
|
||||
export const emitter = mitt<Events>();
|
||||
|
||||
emitter.on('*', (type, e) => console.log(type, e));
|
|
@ -1,4 +1,5 @@
|
|||
export * from './useAutocomplete';
|
||||
export * from './useAutoSaveDraft';
|
||||
export * from './useCanUploadFile';
|
||||
export * from './useChooseMedia';
|
||||
export * from './useKeyboardListener';
|
||||
|
|
|
@ -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]);
|
||||
};
|
|
@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,26 +1,37 @@
|
|||
import { MutableRefObject, useEffect } from 'react';
|
||||
import { Keyboard } from 'react-native';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
import { useMessageComposerApi } from '../context';
|
||||
import { ITrackingView } from '../interfaces';
|
||||
import { TKeyEmitterEvent, emitter } from '../../../lib/methods/helpers/emitter';
|
||||
import { useRoomContext } from '../../../views/RoomView/context';
|
||||
|
||||
export const useKeyboardListener = (ref: MutableRefObject<ITrackingView>) => {
|
||||
const { setKeyboardHeight } = useMessageComposerApi();
|
||||
const { tmid } = useRoomContext();
|
||||
const isFocused = useIsFocused();
|
||||
useEffect(() => {
|
||||
if (!isFocused) {
|
||||
return;
|
||||
}
|
||||
const keyboardEvent: TKeyEmitterEvent = `setKeyboardHeight${tmid ? 'Thread' : ''}`;
|
||||
const showListener = Keyboard.addListener('keyboardWillShow', async () => {
|
||||
if (ref?.current) {
|
||||
const props = await ref.current.getNativeProps();
|
||||
setKeyboardHeight(props.keyboardHeight);
|
||||
emitter.emit(keyboardEvent, props.keyboardHeight);
|
||||
}
|
||||
});
|
||||
|
||||
const hideListener = Keyboard.addListener('keyboardWillHide', () => {
|
||||
setKeyboardHeight(0);
|
||||
emitter.emit(keyboardEvent, 0);
|
||||
});
|
||||
|
||||
return () => {
|
||||
showListener.remove();
|
||||
hideListener.remove();
|
||||
};
|
||||
}, [ref, setKeyboardHeight]);
|
||||
}, [ref, setKeyboardHeight, tmid, isFocused]);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Tasks as TasksProps } from '@rocket.chat/message-parser';
|
|||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { CustomIcon } from '../../CustomIcon';
|
||||
|
||||
interface ITasksProps {
|
||||
value: TasksProps['value'];
|
||||
|
@ -16,7 +17,14 @@ const TaskList = ({ value = [] }: ITasksProps) => {
|
|||
<View>
|
||||
{value.map(item => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, { color: colors.bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||
<Text style={[styles.text, { color: colors.bodyText }]}>
|
||||
<CustomIcon
|
||||
testID={item.status ? 'task-list-checked' : 'task-list-unchecked'}
|
||||
name={item.status ? 'checkbox-checked' : 'checkbox-unchecked'}
|
||||
size={24}
|
||||
color={colors.taskBoxColor}
|
||||
/>
|
||||
</Text>
|
||||
<Text style={[styles.inline, { color: colors.bodyText }]}>
|
||||
<Inline value={item.value} />
|
||||
</Text>
|
||||
|
|
|
@ -17,8 +17,8 @@ const UnorderedList = ({ value }: IUnorderedListProps) => {
|
|||
<View>
|
||||
{value.map(item => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, styles.listPrefix, { color: themes[theme].bodyText }]}>- </Text>
|
||||
<Text style={[styles.text, styles.inline, { color: themes[theme].bodyText }]}>
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{'\u2022'}</Text>
|
||||
<Text style={[styles.inline, { color: themes[theme].bodyText }]}>
|
||||
<Inline value={item.value} />
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -289,6 +289,7 @@ export const colors = {
|
|||
statusBackgroundWarning: '#FFECAD',
|
||||
statusFontOnWarning: '#B88D00',
|
||||
overlayColor: '#1F2329CC',
|
||||
taskBoxColor: '#9297a2',
|
||||
...mentions,
|
||||
...callButtons
|
||||
},
|
||||
|
@ -370,6 +371,7 @@ export const colors = {
|
|||
statusBackgroundWarning: '#FFECAD',
|
||||
statusFontOnWarning: '#B88D00',
|
||||
overlayColor: '#1F2329CC',
|
||||
taskBoxColor: '#9297a2',
|
||||
...mentions,
|
||||
...callButtons
|
||||
},
|
||||
|
@ -451,6 +453,7 @@ export const colors = {
|
|||
statusBackgroundWarning: '#FFECAD',
|
||||
statusFontOnWarning: '#B88D00',
|
||||
overlayColor: '#1F2329CC',
|
||||
taskBoxColor: '#9297a2',
|
||||
...mentions,
|
||||
...callButtons
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import mitt from 'mitt';
|
||||
|
||||
import { TMarkdownStyle } from '../../../containers/MessageComposer/interfaces';
|
||||
|
||||
export type TEmitterEvents = {
|
||||
toolbarMention: undefined;
|
||||
addMarkdown: {
|
||||
style: TMarkdownStyle;
|
||||
};
|
||||
setKeyboardHeight: number;
|
||||
setKeyboardHeightThread: number;
|
||||
setComposerHeight: number;
|
||||
setComposerHeightThread: number;
|
||||
};
|
||||
|
||||
export type TKeyEmitterEvent = keyof TEmitterEvents;
|
||||
|
||||
export const emitter = mitt<TEmitterEvents>();
|
||||
|
||||
emitter.on('*', (type, e) => console.log(type, e));
|
|
@ -42,6 +42,9 @@ class EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use lib/methods/helpers/emitter.ts
|
||||
*/
|
||||
emit(event: string, ...args: TEventEmitterEmmitArgs[]) {
|
||||
if (typeof this.events[event] === 'object') {
|
||||
this.events[event].forEach((listener: Function) => {
|
||||
|
|
|
@ -16,3 +16,5 @@ export * from './isValidEmail';
|
|||
export * from './random';
|
||||
export * from './image';
|
||||
export * from './askAndroidMediaPermissions';
|
||||
export * from './emitter';
|
||||
export * from './parseJson';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const parseJson = (json: string) => {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (ex) {
|
||||
return json;
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View, Platform } from 'react-native';
|
||||
import React, { memo } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import { CustomIcon } from '../../../../containers/CustomIcon';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import Touch from '../../../../containers/Touch';
|
||||
import { useNavBottomStyle } from '../hooks';
|
||||
import { EDGE_DISTANCE } from '../constants';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 15
|
||||
right: EDGE_DISTANCE
|
||||
},
|
||||
button: {
|
||||
borderRadius: 25
|
||||
|
@ -23,46 +25,25 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const NavBottomFAB = ({
|
||||
visible,
|
||||
onPress,
|
||||
isThread
|
||||
}: {
|
||||
visible: boolean;
|
||||
onPress: Function;
|
||||
isThread: boolean;
|
||||
}): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const NavBottomFAB = memo(
|
||||
({ visible, onPress, isThread }: { visible: boolean; onPress: Function; isThread: boolean }): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const positionStyle = useNavBottomStyle(isThread);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, positionStyle]} testID='nav-jump-to-bottom'>
|
||||
<Touch onPress={() => onPress()} style={[styles.button, { backgroundColor: colors.backgroundColor }]}>
|
||||
<View style={[styles.content, { borderColor: colors.borderColor }]}>
|
||||
<CustomIcon name='chevron-down' color={colors.auxiliaryTintColor} size={36} />
|
||||
</View>
|
||||
</Touch>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
...Platform.select({
|
||||
ios: {
|
||||
bottom: 100 + (isThread ? 40 : 0)
|
||||
},
|
||||
android: {
|
||||
top: 15,
|
||||
scaleY: -1
|
||||
}
|
||||
})
|
||||
}
|
||||
]}
|
||||
testID='nav-jump-to-bottom'
|
||||
>
|
||||
<Touch onPress={() => onPress()} style={[styles.button, { backgroundColor: colors.backgroundColor }]}>
|
||||
<View style={[styles.content, { borderColor: colors.borderColor }]}>
|
||||
<CustomIcon name='chevron-down' color={colors.auxiliaryTintColor} size={36} />
|
||||
</View>
|
||||
</Touch>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default NavBottomFAB;
|
||||
|
|
|
@ -5,3 +5,5 @@ export const VIEWABILITY_CONFIG = {
|
|||
};
|
||||
|
||||
export const SCROLL_LIMIT = 200;
|
||||
|
||||
export const EDGE_DISTANCE = 15;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './useNavBottomStyle';
|
||||
export * from './useMessages';
|
||||
export * from './useScroll';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './useNavBottomStyle';
|
|
@ -0,0 +1,8 @@
|
|||
import { ViewStyle } from 'react-native';
|
||||
|
||||
import { EDGE_DISTANCE } from '../../constants';
|
||||
|
||||
export const useNavBottomStyle = (): ViewStyle => ({
|
||||
top: EDGE_DISTANCE,
|
||||
scaleY: -1
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { ViewStyle } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { TKeyEmitterEvent, emitter } from '../../../../../lib/methods/helpers';
|
||||
import { EDGE_DISTANCE } from '../../constants';
|
||||
|
||||
export const useNavBottomStyle = (isThread: boolean): ViewStyle => {
|
||||
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
||||
const [composerHeight, setComposerHeight] = useState(0);
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
|
||||
useEffect(() => {
|
||||
const keyboardEvent: TKeyEmitterEvent = `setKeyboardHeight${isThread ? 'Thread' : ''}`;
|
||||
const composerEvent: TKeyEmitterEvent = `setComposerHeight${isThread ? 'Thread' : ''}`;
|
||||
emitter.on(keyboardEvent, height => {
|
||||
if (height !== keyboardHeight) {
|
||||
setKeyboardHeight(height);
|
||||
}
|
||||
});
|
||||
emitter.on(composerEvent, height => {
|
||||
if (height !== composerHeight) {
|
||||
setComposerHeight(height);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
emitter.off(keyboardEvent);
|
||||
emitter.off(composerEvent);
|
||||
};
|
||||
}, [isThread, keyboardHeight, composerHeight]);
|
||||
|
||||
return {
|
||||
bottom: keyboardHeight + composerHeight + (keyboardHeight ? 0 : bottom) + EDGE_DISTANCE
|
||||
};
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ViewToken, ViewabilityConfigCallbackPairs } from 'react-native';
|
||||
|
||||
import { IListContainerRef, IListProps, TListRef, TMessagesIdsRef } from '../definitions';
|
||||
|
@ -20,9 +20,9 @@ export const useScroll = ({ listRef, messagesIds }: { listRef: TListRef; message
|
|||
[]
|
||||
);
|
||||
|
||||
const jumpToBottom = () => {
|
||||
const jumpToBottom = useCallback(() => {
|
||||
listRef.current?.scrollToOffset({ offset: -100 });
|
||||
};
|
||||
}, [listRef]);
|
||||
|
||||
const onViewableItemsChanged: IListProps['onViewableItemsChanged'] = ({ viewableItems: vi }) => {
|
||||
viewableItems.current = vi;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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}>
|
||||
|
|
Loading…
Reference in New Issue