chore: Migrate RoomView/List to Hooks (#5207)
This commit is contained in:
parent
ed20c855d1
commit
043f48ae54
|
@ -1,49 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { FlatListProps, StyleSheet } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
import { isIOS } from '../../../lib/methods/helpers';
|
||||
import scrollPersistTaps from '../../../lib/methods/helpers/scrollPersistTaps';
|
||||
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
flex: 1
|
||||
},
|
||||
contentContainer: {
|
||||
paddingTop: 10
|
||||
}
|
||||
});
|
||||
|
||||
export type TListRef = React.RefObject<FlatList & { getNode: () => FlatList }>;
|
||||
|
||||
export interface IListProps extends FlatListProps<any> {
|
||||
listRef: TListRef;
|
||||
}
|
||||
|
||||
const List = ({ listRef, ...props }: IListProps) => (
|
||||
<AnimatedFlatList
|
||||
testID='room-view-messages'
|
||||
ref={listRef}
|
||||
keyExtractor={(item: any) => item.id}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
style={styles.list}
|
||||
inverted={isIOS}
|
||||
removeClippedSubviews={isIOS}
|
||||
initialNumToRender={7}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
{...props}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
);
|
||||
|
||||
List.propTypes = {
|
||||
listRef: PropTypes.object
|
||||
};
|
||||
|
||||
export default List;
|
|
@ -1,75 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import Animated, { call, cond, greaterOrEq, useCode } from 'react-native-reanimated';
|
||||
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { CustomIcon } from '../../../containers/CustomIcon';
|
||||
import { useTheme } from '../../../theme';
|
||||
import Touch from '../../../containers/Touch';
|
||||
import { hasNotch } from '../../../lib/methods/helpers';
|
||||
|
||||
const SCROLL_LIMIT = 200;
|
||||
const SEND_TO_CHANNEL_HEIGHT = 40;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 15
|
||||
},
|
||||
button: {
|
||||
borderRadius: 25
|
||||
},
|
||||
content: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const NavBottomFAB = ({
|
||||
y,
|
||||
onPress,
|
||||
isThread
|
||||
}: {
|
||||
y: Animated.Value<number>;
|
||||
onPress: Function;
|
||||
isThread: boolean;
|
||||
}): React.ReactElement | null => {
|
||||
const { theme } = useTheme();
|
||||
const [show, setShow] = useState(false);
|
||||
const handleOnPress = () => onPress();
|
||||
const toggle = (v: boolean) => setShow(v);
|
||||
|
||||
useCode(
|
||||
() =>
|
||||
cond(
|
||||
greaterOrEq(y, SCROLL_LIMIT),
|
||||
call([y], () => toggle(true)),
|
||||
call([y], () => toggle(false))
|
||||
),
|
||||
[y]
|
||||
);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let bottom = hasNotch ? 100 : 60;
|
||||
if (isThread) {
|
||||
bottom += SEND_TO_CHANNEL_HEIGHT;
|
||||
}
|
||||
return (
|
||||
<Animated.View style={[styles.container, { bottom }]} testID='nav-jump-to-bottom'>
|
||||
<Touch onPress={handleOnPress} style={[styles.button, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<View style={[styles.content, { borderColor: themes[theme].borderColor }]}>
|
||||
<CustomIcon name='chevron-down' color={themes[theme].auxiliaryTintColor} size={36} />
|
||||
</View>
|
||||
</Touch>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavBottomFAB;
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { ImageBackground, StyleSheet } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { useTheme } from '../../../../theme';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
|
@ -11,12 +11,10 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const EmptyRoom = React.memo(({ length, mounted, rid }: { length: number; mounted: boolean; rid: string }) => {
|
||||
export const EmptyRoom = React.memo(({ length, rid }: { length: number; rid: string }) => {
|
||||
const { theme } = useTheme();
|
||||
if ((length === 0 && mounted) || !rid) {
|
||||
if (length === 0 || !rid) {
|
||||
return <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export default EmptyRoom;
|
|
@ -0,0 +1,60 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FlatListProps, StyleSheet } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import Animated, { runOnJS, useAnimatedScrollHandler } from 'react-native-reanimated';
|
||||
|
||||
import { isIOS } from '../../../../lib/methods/helpers';
|
||||
import scrollPersistTaps from '../../../../lib/methods/helpers/scrollPersistTaps';
|
||||
import NavBottomFAB from './NavBottomFAB';
|
||||
import { IListProps } from '../definitions';
|
||||
import { SCROLL_LIMIT } from '../constants';
|
||||
import { TAnyMessageModel } from '../../../../definitions';
|
||||
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent<FlatListProps<TAnyMessageModel>>(FlatList);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
flex: 1
|
||||
},
|
||||
contentContainer: {
|
||||
paddingTop: 10
|
||||
}
|
||||
});
|
||||
|
||||
export const List = ({ listRef, jumpToBottom, isThread, ...props }: IListProps) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onScroll: event => {
|
||||
if (event.contentOffset.y > SCROLL_LIMIT) {
|
||||
runOnJS(setVisible)(true);
|
||||
} else {
|
||||
runOnJS(setVisible)(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatedFlatList
|
||||
testID='room-view-messages'
|
||||
// @ts-ignore createAnimatedComponent is making this fail
|
||||
ref={listRef}
|
||||
keyExtractor={item => item.id}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
style={styles.list}
|
||||
inverted={isIOS}
|
||||
removeClippedSubviews={isIOS}
|
||||
initialNumToRender={7}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
scrollEventThrottle={16}
|
||||
onScroll={scrollHandler}
|
||||
{...props}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
<NavBottomFAB visible={visible} onPress={jumpToBottom} isThread={isThread} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View, Platform } from 'react-native';
|
||||
|
||||
import { CustomIcon } from '../../../../containers/CustomIcon';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import Touch from '../../../../containers/Touch';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 15
|
||||
},
|
||||
button: {
|
||||
borderRadius: 25
|
||||
},
|
||||
content: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const NavBottomFAB = ({
|
||||
visible,
|
||||
onPress,
|
||||
isThread
|
||||
}: {
|
||||
visible: boolean;
|
||||
onPress: Function;
|
||||
isThread: boolean;
|
||||
}): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { RefreshControl as RNRefreshControl, RefreshControlProps, StyleSheet } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../../theme';
|
||||
import { isAndroid } from '../../../lib/methods/helpers';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { isAndroid } from '../../../../lib/methods/helpers';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -17,7 +17,7 @@ interface IRefreshControl extends RefreshControlProps {
|
|||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
const RefreshControl = ({ children, onRefresh, refreshing }: IRefreshControl): React.ReactElement => {
|
||||
export const RefreshControl = ({ children, onRefresh, refreshing }: IRefreshControl): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
if (isAndroid) {
|
||||
return (
|
||||
|
@ -36,5 +36,3 @@ const RefreshControl = ({ children, onRefresh, refreshing }: IRefreshControl): R
|
|||
|
||||
return React.cloneElement(children, { refreshControl });
|
||||
};
|
||||
|
||||
export default RefreshControl;
|
|
@ -0,0 +1,4 @@
|
|||
export * from './NavBottomFAB';
|
||||
export * from './RefreshControl';
|
||||
export * from './EmptyRoom';
|
||||
export * from './List';
|
|
@ -0,0 +1,7 @@
|
|||
export const QUERY_SIZE = 50;
|
||||
|
||||
export const VIEWABILITY_CONFIG = {
|
||||
itemVisiblePercentThreshold: 10
|
||||
};
|
||||
|
||||
export const SCROLL_LIMIT = 200;
|
|
@ -0,0 +1,31 @@
|
|||
import { RefObject } from 'react';
|
||||
import { FlatListProps } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
import { TAnyMessageModel } from '../../../definitions';
|
||||
|
||||
export type TListRef = RefObject<FlatList<TAnyMessageModel>>;
|
||||
|
||||
export type TMessagesIdsRef = RefObject<string[]>;
|
||||
|
||||
export interface IListProps extends FlatListProps<TAnyMessageModel> {
|
||||
listRef: TListRef;
|
||||
jumpToBottom: () => void;
|
||||
isThread: boolean;
|
||||
}
|
||||
|
||||
export interface IListContainerRef {
|
||||
jumpToMessage: (messageId: string) => Promise<void>;
|
||||
cancelJumpToMessage: () => void;
|
||||
}
|
||||
|
||||
export interface IListContainerProps {
|
||||
renderRow: Function;
|
||||
rid: string;
|
||||
tmid?: string;
|
||||
loading: boolean;
|
||||
listRef: TListRef;
|
||||
hideSystemMessages: string[];
|
||||
showMessageInMainThread: boolean;
|
||||
serverVersion: string | null;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './useMessages';
|
||||
export * from './useRefresh';
|
||||
export * from './useScroll';
|
|
@ -0,0 +1,111 @@
|
|||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { TAnyMessageModel, TThreadModel } from '../../../../definitions';
|
||||
import database from '../../../../lib/database';
|
||||
import { getThreadById } from '../../../../lib/database/services/Thread';
|
||||
import { animateNextTransition, compareServerVersion, isIOS, useDebounce } from '../../../../lib/methods/helpers';
|
||||
import { Services } from '../../../../lib/services';
|
||||
import { QUERY_SIZE } from '../constants';
|
||||
|
||||
export const useMessages = ({
|
||||
rid,
|
||||
tmid,
|
||||
showMessageInMainThread,
|
||||
serverVersion,
|
||||
hideSystemMessages
|
||||
}: {
|
||||
rid: string;
|
||||
tmid?: string;
|
||||
showMessageInMainThread: boolean;
|
||||
serverVersion: string | null;
|
||||
hideSystemMessages: string[];
|
||||
}) => {
|
||||
const [messages, setMessages] = useState<TAnyMessageModel[]>([]);
|
||||
const thread = useRef<TThreadModel | null>(null);
|
||||
const count = useRef(0);
|
||||
const subscription = useRef<Subscription | null>(null);
|
||||
const messagesIds = useRef<string[]>([]);
|
||||
|
||||
const fetchMessages = useCallback(async () => {
|
||||
unsubscribe();
|
||||
count.current += QUERY_SIZE;
|
||||
|
||||
if (!rid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const db = database.active;
|
||||
let observable;
|
||||
if (tmid) {
|
||||
if (!thread.current) {
|
||||
thread.current = await getThreadById(tmid);
|
||||
}
|
||||
observable = db
|
||||
.get('thread_messages')
|
||||
.query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(count.current))
|
||||
.observe();
|
||||
} else {
|
||||
const whereClause = [
|
||||
Q.where('rid', rid),
|
||||
Q.experimentalSortBy('ts', Q.desc),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(count.current)
|
||||
] as (Q.WhereDescription | Q.Or)[];
|
||||
if (!showMessageInMainThread) {
|
||||
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
|
||||
}
|
||||
observable = db
|
||||
.get('messages')
|
||||
.query(...whereClause)
|
||||
.observe();
|
||||
}
|
||||
|
||||
subscription.current = observable.subscribe(result => {
|
||||
let newMessages: TAnyMessageModel[] = result;
|
||||
if (tmid && thread.current) {
|
||||
newMessages.push(thread.current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since 3.16.0 server version, the backend don't response with messages if
|
||||
* hide system message is enabled
|
||||
*/
|
||||
if (compareServerVersion(serverVersion, 'lowerThan', '3.16.0') || hideSystemMessages.length) {
|
||||
newMessages = newMessages.filter(m => !m.t || !hideSystemMessages?.includes(m.t));
|
||||
}
|
||||
|
||||
readThread();
|
||||
if (isIOS) {
|
||||
animateNextTransition();
|
||||
}
|
||||
setMessages(newMessages);
|
||||
messagesIds.current = newMessages.map(m => m.id);
|
||||
});
|
||||
}, [rid, tmid, showMessageInMainThread, serverVersion, hideSystemMessages]);
|
||||
|
||||
const readThread = useDebounce(async () => {
|
||||
if (tmid) {
|
||||
try {
|
||||
await Services.readThreads(tmid);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
fetchMessages();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [rid, tmid, showMessageInMainThread, serverVersion, hideSystemMessages, fetchMessages]);
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscription.current?.unsubscribe();
|
||||
};
|
||||
|
||||
return [messages, messagesIds, fetchMessages] as const;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
import moment from 'moment';
|
||||
import { useState } from 'react';
|
||||
|
||||
import log from '../../../../lib/methods/helpers/log';
|
||||
import { loadMissedMessages, loadThreadMessages } from '../../../../lib/methods';
|
||||
|
||||
export const useRefresh = ({ rid, tmid, messagesLength }: { rid: string; tmid?: string; messagesLength: number }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const refresh = async () => {
|
||||
if (messagesLength) {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
if (tmid) {
|
||||
await loadThreadMessages({ tmid, rid });
|
||||
} else {
|
||||
await loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() });
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [refreshing, refresh] as const;
|
||||
};
|
|
@ -0,0 +1,102 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ViewToken, ViewabilityConfigCallbackPairs } from 'react-native';
|
||||
|
||||
import { IListContainerRef, IListProps, TListRef, TMessagesIdsRef } from '../definitions';
|
||||
import { VIEWABILITY_CONFIG } from '../constants';
|
||||
|
||||
export const useScroll = ({ listRef, messagesIds }: { listRef: TListRef; messagesIds: TMessagesIdsRef }) => {
|
||||
const [highlightedMessageId, setHighlightedMessageId] = useState<string | null>(null);
|
||||
const cancelJump = useRef(false);
|
||||
const jumping = useRef(false);
|
||||
const viewableItems = useRef<ViewToken[] | null>(null);
|
||||
const highlightTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (highlightTimeout.current) {
|
||||
clearTimeout(highlightTimeout.current);
|
||||
}
|
||||
});
|
||||
|
||||
const jumpToBottom = () => {
|
||||
listRef.current?.scrollToOffset({ offset: -100 });
|
||||
};
|
||||
|
||||
const onViewableItemsChanged: IListProps['onViewableItemsChanged'] = ({ viewableItems: vi }) => {
|
||||
viewableItems.current = vi;
|
||||
};
|
||||
|
||||
const viewabilityConfigCallbackPairs = useRef<ViewabilityConfigCallbackPairs>([
|
||||
{ onViewableItemsChanged, viewabilityConfig: VIEWABILITY_CONFIG }
|
||||
]);
|
||||
|
||||
const handleScrollToIndexFailed: IListProps['onScrollToIndexFailed'] = params => {
|
||||
listRef.current?.scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
|
||||
};
|
||||
|
||||
const setHighlightTimeout = () => {
|
||||
if (highlightTimeout.current) {
|
||||
clearTimeout(highlightTimeout.current);
|
||||
}
|
||||
highlightTimeout.current = setTimeout(() => {
|
||||
setHighlightedMessageId(null);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const jumpToMessage: IListContainerRef['jumpToMessage'] = messageId =>
|
||||
new Promise<void>(async resolve => {
|
||||
// if jump to message was cancelled, reset variables and stop
|
||||
if (cancelJump.current) {
|
||||
resetJumpToMessage();
|
||||
return resolve();
|
||||
}
|
||||
jumping.current = true;
|
||||
|
||||
// look for the message on the state
|
||||
const index = messagesIds.current?.findIndex(item => item === messageId);
|
||||
|
||||
// if found message, scroll to it
|
||||
if (index && index > -1) {
|
||||
listRef.current?.scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
|
||||
|
||||
// wait for scroll animation to finish
|
||||
await new Promise(res => setTimeout(res, 300));
|
||||
|
||||
// if message is not visible
|
||||
if (!viewableItems.current?.map(vi => vi.key).includes(messageId)) {
|
||||
await setTimeout(() => resolve(jumpToMessage(messageId)), 300);
|
||||
return;
|
||||
}
|
||||
// if message is visible, highlight it
|
||||
setHighlightedMessageId(messageId);
|
||||
setHighlightTimeout();
|
||||
resetJumpToMessage();
|
||||
resolve();
|
||||
} else {
|
||||
// if message not on state yet, scroll to top, so it triggers onEndReached and try again
|
||||
listRef.current?.scrollToEnd();
|
||||
await setTimeout(() => resolve(jumpToMessage(messageId)), 600);
|
||||
}
|
||||
});
|
||||
|
||||
const resetJumpToMessage = () => {
|
||||
cancelJump.current = false;
|
||||
jumping.current = false;
|
||||
};
|
||||
|
||||
const cancelJumpToMessage: IListContainerRef['cancelJumpToMessage'] = () => {
|
||||
if (jumping.current) {
|
||||
cancelJump.current = true;
|
||||
return;
|
||||
}
|
||||
resetJumpToMessage();
|
||||
};
|
||||
|
||||
return {
|
||||
jumpToBottom,
|
||||
jumpToMessage,
|
||||
cancelJumpToMessage,
|
||||
viewabilityConfigCallbackPairs,
|
||||
handleScrollToIndexFailed,
|
||||
highlightedMessageId
|
||||
};
|
||||
};
|
|
@ -1,25 +1,11 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { dequal } from 'dequal';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { FlatListProps, View, ViewToken, StyleSheet, Platform } from 'react-native';
|
||||
import { event, Value } from 'react-native-reanimated';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { View, Platform, StyleSheet } from 'react-native';
|
||||
|
||||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||
import { TAnyMessageModel, TMessageModel, TThreadMessageModel, TThreadModel } from '../../../definitions';
|
||||
import database from '../../../lib/database';
|
||||
import { compareServerVersion, debounce } from '../../../lib/methods/helpers';
|
||||
import { animateNextTransition } from '../../../lib/methods/helpers/layoutAnimation';
|
||||
import log from '../../../lib/methods/helpers/log';
|
||||
import EmptyRoom from '../EmptyRoom';
|
||||
import List, { IListProps, TListRef } from './List';
|
||||
import NavBottomFAB from './NavBottomFAB';
|
||||
import { loadMissedMessages, loadThreadMessages } from '../../../lib/methods';
|
||||
import { Services } from '../../../lib/services';
|
||||
import RefreshControl from './RefreshControl';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
import { useMessages, useRefresh, useScroll } from './hooks';
|
||||
import { useDebounce } from '../../../lib/methods/helpers';
|
||||
import { RefreshControl, EmptyRoom, List } from './components';
|
||||
import { IListContainerProps, IListContainerRef, IListProps } from './definitions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inverted: {
|
||||
|
@ -31,367 +17,64 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const onScroll = ({ y }: { y: Value<number> }) =>
|
||||
event(
|
||||
[
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: { y }
|
||||
}
|
||||
const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
||||
({ rid, tmid, renderRow, showMessageInMainThread, serverVersion, hideSystemMessages, listRef, loading }, ref) => {
|
||||
const [messages, messagesIds, fetchMessages] = useMessages({
|
||||
rid,
|
||||
tmid,
|
||||
showMessageInMainThread,
|
||||
serverVersion,
|
||||
hideSystemMessages
|
||||
});
|
||||
const [refreshing, refresh] = useRefresh({ rid, tmid, messagesLength: messages.length });
|
||||
const {
|
||||
jumpToBottom,
|
||||
jumpToMessage,
|
||||
cancelJumpToMessage,
|
||||
viewabilityConfigCallbackPairs,
|
||||
handleScrollToIndexFailed,
|
||||
highlightedMessageId
|
||||
} = useScroll({ listRef, messagesIds });
|
||||
|
||||
const onEndReached = useDebounce(() => {
|
||||
fetchMessages();
|
||||
}, 300);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
jumpToMessage,
|
||||
cancelJumpToMessage
|
||||
}));
|
||||
|
||||
const renderFooter = () => {
|
||||
if (loading && rid) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
],
|
||||
{ useNativeDriver: true }
|
||||
);
|
||||
|
||||
export { IListProps };
|
||||
|
||||
export interface IListContainerProps {
|
||||
renderRow: Function;
|
||||
rid: string;
|
||||
tmid?: string;
|
||||
loading: boolean;
|
||||
listRef: TListRef;
|
||||
hideSystemMessages?: string[];
|
||||
tunread?: string[];
|
||||
ignored?: string[];
|
||||
navigation: any; // TODO: type me
|
||||
showMessageInMainThread: boolean;
|
||||
serverVersion: string | null;
|
||||
autoTranslateRoom?: boolean;
|
||||
autoTranslateLanguage?: string;
|
||||
}
|
||||
|
||||
interface IListContainerState {
|
||||
messages: TAnyMessageModel[];
|
||||
refreshing: boolean;
|
||||
highlightedMessage: string | null;
|
||||
}
|
||||
|
||||
class ListContainer extends React.Component<IListContainerProps, IListContainerState> {
|
||||
private count = 0;
|
||||
private mounted = false;
|
||||
private animated = false;
|
||||
private jumping = false;
|
||||
private cancelJump = false;
|
||||
private y = new Value(0);
|
||||
private onScroll = onScroll({ y: this.y });
|
||||
private unsubscribeFocus: () => void;
|
||||
private viewabilityConfig = {
|
||||
itemVisiblePercentThreshold: 10
|
||||
};
|
||||
private highlightedMessageTimeout: ReturnType<typeof setTimeout> | undefined | false;
|
||||
private thread?: TThreadModel;
|
||||
private messagesObservable?: Observable<TMessageModel[] | TThreadMessageModel[]>;
|
||||
private messagesSubscription?: Subscription;
|
||||
private viewableItems?: ViewToken[];
|
||||
|
||||
constructor(props: IListContainerProps) {
|
||||
super(props);
|
||||
console.time(`${this.constructor.name} init`);
|
||||
console.time(`${this.constructor.name} mount`);
|
||||
this.state = {
|
||||
messages: [],
|
||||
refreshing: false,
|
||||
highlightedMessage: null
|
||||
return null;
|
||||
};
|
||||
this.query();
|
||||
this.unsubscribeFocus = props.navigation.addListener('focus', () => {
|
||||
this.animated = true;
|
||||
});
|
||||
console.timeEnd(`${this.constructor.name} init`);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
console.timeEnd(`${this.constructor.name} mount`);
|
||||
}
|
||||
const renderItem: IListProps['renderItem'] = ({ item, index }) => (
|
||||
<View style={styles.inverted}>{renderRow(item, messages[index + 1], highlightedMessageId)}</View>
|
||||
);
|
||||
|
||||
shouldComponentUpdate(nextProps: IListContainerProps, nextState: IListContainerState) {
|
||||
const { refreshing, highlightedMessage } = this.state;
|
||||
const { hideSystemMessages, tunread, ignored, loading, autoTranslateLanguage, autoTranslateRoom } = this.props;
|
||||
if (loading !== nextProps.loading) {
|
||||
return true;
|
||||
}
|
||||
if (highlightedMessage !== nextState.highlightedMessage) {
|
||||
return true;
|
||||
}
|
||||
if (refreshing !== nextState.refreshing) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(hideSystemMessages, nextProps.hideSystemMessages)) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(tunread, nextProps.tunread)) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(ignored, nextProps.ignored)) {
|
||||
return true;
|
||||
}
|
||||
if (autoTranslateLanguage !== nextProps.autoTranslateLanguage || autoTranslateRoom !== nextProps.autoTranslateRoom) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: IListContainerProps) {
|
||||
const { hideSystemMessages } = this.props;
|
||||
if (!dequal(hideSystemMessages, prevProps.hideSystemMessages)) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeMessages();
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
this.clearHighlightedMessageTimeout();
|
||||
console.countReset(`${this.constructor.name}.render calls`);
|
||||
}
|
||||
|
||||
// clears previous highlighted message timeout, if exists
|
||||
clearHighlightedMessageTimeout = () => {
|
||||
if (this.highlightedMessageTimeout) {
|
||||
clearTimeout(this.highlightedMessageTimeout);
|
||||
this.highlightedMessageTimeout = false;
|
||||
}
|
||||
};
|
||||
|
||||
query = async () => {
|
||||
this.count += QUERY_SIZE;
|
||||
const { rid, tmid, showMessageInMainThread, serverVersion } = this.props;
|
||||
const db = database.active;
|
||||
|
||||
// handle servers with version < 3.0.0
|
||||
let { hideSystemMessages = [] } = this.props;
|
||||
if (!Array.isArray(hideSystemMessages)) {
|
||||
hideSystemMessages = [];
|
||||
}
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
this.thread = await db.get('threads').find(tmid);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.messagesObservable = db
|
||||
.get('thread_messages')
|
||||
.query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(this.count))
|
||||
.observe();
|
||||
} else if (rid) {
|
||||
const whereClause = [
|
||||
Q.where('rid', rid),
|
||||
Q.experimentalSortBy('ts', Q.desc),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(this.count)
|
||||
] as (Q.WhereDescription | Q.Or)[];
|
||||
if (!showMessageInMainThread) {
|
||||
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
|
||||
}
|
||||
this.messagesObservable = db
|
||||
.get('messages')
|
||||
.query(...whereClause)
|
||||
.observe();
|
||||
}
|
||||
|
||||
if (rid) {
|
||||
this.unsubscribeMessages();
|
||||
this.messagesSubscription = this.messagesObservable?.subscribe(messages => {
|
||||
if (tmid && this.thread) {
|
||||
messages = [...messages, this.thread];
|
||||
}
|
||||
|
||||
/**
|
||||
* Since 3.16.0 server version, the backend don't response with messages if
|
||||
* hide system message is enabled
|
||||
*/
|
||||
if (compareServerVersion(serverVersion, 'lowerThan', '3.16.0') || hideSystemMessages.length) {
|
||||
messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t));
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.messages = messages;
|
||||
}
|
||||
// TODO: move it away from here
|
||||
this.readThreads();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
reload = () => {
|
||||
this.count = 0;
|
||||
this.query();
|
||||
};
|
||||
|
||||
readThreads = debounce(async () => {
|
||||
const { tmid } = this.props;
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
await Services.readThreads(tmid);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
onEndReached = () => this.query();
|
||||
|
||||
onRefresh = () =>
|
||||
this.setState({ refreshing: true }, async () => {
|
||||
const { messages } = this.state;
|
||||
const { rid, tmid } = this.props;
|
||||
|
||||
if (messages.length) {
|
||||
try {
|
||||
if (tmid) {
|
||||
await loadThreadMessages({ tmid, rid });
|
||||
} else {
|
||||
await loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() });
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ refreshing: false });
|
||||
});
|
||||
|
||||
update = () => {
|
||||
if (this.animated) {
|
||||
animateNextTransition();
|
||||
}
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
unsubscribeMessages = () => {
|
||||
if (this.messagesSubscription && this.messagesSubscription.unsubscribe) {
|
||||
this.messagesSubscription.unsubscribe();
|
||||
}
|
||||
};
|
||||
|
||||
getLastMessage = (): TMessageModel | TThreadMessageModel | null => {
|
||||
const { messages } = this.state;
|
||||
if (messages.length > 0) {
|
||||
return messages[0];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
handleScrollToIndexFailed: FlatListProps<any>['onScrollToIndexFailed'] = params => {
|
||||
const { listRef } = this.props;
|
||||
listRef.current?.scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
|
||||
};
|
||||
|
||||
jumpToMessage = (messageId: string) =>
|
||||
new Promise<void>(async resolve => {
|
||||
const { messages } = this.state;
|
||||
const { listRef } = this.props;
|
||||
|
||||
// if jump to message was cancelled, reset variables and stop
|
||||
if (this.cancelJump) {
|
||||
this.resetJumpToMessage();
|
||||
return resolve();
|
||||
}
|
||||
this.jumping = true;
|
||||
|
||||
// look for the message on the state
|
||||
const index = messages.findIndex(item => item.id === messageId);
|
||||
|
||||
// if found message, scroll to it
|
||||
if (index > -1) {
|
||||
listRef.current?.scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
|
||||
|
||||
// wait for scroll animation to finish
|
||||
await new Promise(res => setTimeout(res, 300));
|
||||
|
||||
// if message is not visible
|
||||
if (!this.viewableItems?.map(vi => vi.key).includes(messageId)) {
|
||||
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
||||
return;
|
||||
}
|
||||
// if message is visible, highlight it
|
||||
this.setState({ highlightedMessage: messageId });
|
||||
this.clearHighlightedMessageTimeout();
|
||||
// clears highlighted message after some time
|
||||
this.highlightedMessageTimeout = setTimeout(() => {
|
||||
this.setState({ highlightedMessage: null });
|
||||
}, 5000);
|
||||
this.resetJumpToMessage();
|
||||
resolve();
|
||||
} else {
|
||||
// if message not found, wait for scroll to top and then jump to message
|
||||
listRef.current?.scrollToIndex({ index: messages.length - 1, animated: true });
|
||||
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
||||
}
|
||||
});
|
||||
|
||||
resetJumpToMessage = () => {
|
||||
this.cancelJump = false;
|
||||
this.jumping = false;
|
||||
};
|
||||
|
||||
cancelJumpToMessage = () => {
|
||||
if (this.jumping) {
|
||||
this.cancelJump = true;
|
||||
return;
|
||||
}
|
||||
this.resetJumpToMessage();
|
||||
};
|
||||
|
||||
jumpToBottom = () => {
|
||||
const { listRef } = this.props;
|
||||
listRef.current?.scrollToOffset({ offset: -100 });
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
const { rid, loading } = this.props;
|
||||
if (loading && rid) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderItem: FlatListProps<any>['renderItem'] = ({ item, index }) => {
|
||||
const { messages, highlightedMessage } = this.state;
|
||||
const { renderRow } = this.props;
|
||||
return <View style={styles.inverted}>{renderRow(item, messages[index + 1], highlightedMessage)}</View>;
|
||||
};
|
||||
|
||||
onViewableItemsChanged: FlatListProps<any>['onViewableItemsChanged'] = ({ viewableItems }) => {
|
||||
this.viewableItems = viewableItems;
|
||||
};
|
||||
|
||||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { rid, tmid, listRef } = this.props;
|
||||
const { messages, refreshing } = this.state;
|
||||
return (
|
||||
<>
|
||||
<EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} />
|
||||
<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh}>
|
||||
<EmptyRoom rid={rid} length={messages.length} />
|
||||
<RefreshControl refreshing={refreshing} onRefresh={refresh}>
|
||||
<List
|
||||
onScroll={this.onScroll}
|
||||
scrollEventThrottle={16}
|
||||
listRef={listRef}
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
onEndReached={this.onEndReached}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
onScrollToIndexFailed={this.handleScrollToIndexFailed}
|
||||
onViewableItemsChanged={this.onViewableItemsChanged}
|
||||
viewabilityConfig={this.viewabilityConfig}
|
||||
renderItem={renderItem}
|
||||
onEndReached={onEndReached}
|
||||
ListFooterComponent={renderFooter}
|
||||
onScrollToIndexFailed={handleScrollToIndexFailed}
|
||||
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
|
||||
jumpToBottom={jumpToBottom}
|
||||
isThread={!!tmid}
|
||||
/>
|
||||
</RefreshControl>
|
||||
<NavBottomFAB y={this.y} onPress={this.jumpToBottom} isThread={!!tmid} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type ListContainerType = ListContainer;
|
||||
);
|
||||
|
||||
export default ListContainer;
|
||||
|
|
|
@ -29,7 +29,6 @@ import { showErrorAlert } from '../../lib/methods/helpers/info';
|
|||
import { withTheme } from '../../theme';
|
||||
import {
|
||||
KEY_COMMAND,
|
||||
handleCommandReplyLatest,
|
||||
handleCommandRoomActions,
|
||||
handleCommandScroll,
|
||||
handleCommandSearchMessages,
|
||||
|
@ -56,7 +55,7 @@ import styles from './styles';
|
|||
import JoinCode, { IJoinCode } from './JoinCode';
|
||||
import UploadProgress from './UploadProgress';
|
||||
import ReactionPicker from './ReactionPicker';
|
||||
import List, { ListContainerType } from './List';
|
||||
import List from './List';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import {
|
||||
IApplicationState,
|
||||
|
@ -79,7 +78,6 @@ import {
|
|||
RoomType
|
||||
} from '../../definitions';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
|
||||
import { TListRef } from './List/List';
|
||||
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import {
|
||||
callJitsi,
|
||||
|
@ -102,6 +100,7 @@ import {
|
|||
import { Services } from '../../lib/services';
|
||||
import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet';
|
||||
import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
|
||||
import { IListContainerRef, TListRef } from './List/definitions';
|
||||
|
||||
type TStateAttrsUpdate = keyof IRoomViewState;
|
||||
|
||||
|
@ -154,7 +153,6 @@ const roomAttrsUpdate = [
|
|||
|
||||
interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackParamList, 'RoomView'> {
|
||||
user: Pick<ILoggedUser, 'id' | 'username' | 'token' | 'showMessageInMainThread'>;
|
||||
appState: string;
|
||||
useRealName?: boolean;
|
||||
isAuthenticated: boolean;
|
||||
Message_GroupingPeriod?: number;
|
||||
|
@ -214,8 +212,10 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
private jumpToMessageId?: string;
|
||||
private jumpToThreadId?: string;
|
||||
private messagebox: React.RefObject<MessageBoxType>;
|
||||
private list: React.RefObject<ListContainerType>;
|
||||
private joinCode: React.RefObject<IJoinCode>;
|
||||
// ListContainer component
|
||||
private list: React.RefObject<IListContainerRef>;
|
||||
// FlatList inside ListContainer
|
||||
private flatList: TListRef;
|
||||
private mounted: boolean;
|
||||
private offset = 0;
|
||||
|
@ -224,8 +224,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
private queryUnreads?: Subscription;
|
||||
private retryInit = 0;
|
||||
private retryInitTimeout?: ReturnType<typeof setTimeout>;
|
||||
private retryFindCount = 0;
|
||||
private retryFindTimeout?: ReturnType<typeof setTimeout>;
|
||||
private messageErrorActions?: IMessageErrorActions | null;
|
||||
private messageActions?: IMessageActions | null;
|
||||
private replyInDM?: TAnyMessageModel;
|
||||
|
@ -239,8 +237,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
constructor(props: IRoomViewProps) {
|
||||
super(props);
|
||||
console.time(`${this.constructor.name} init`);
|
||||
console.time(`${this.constructor.name} mount`);
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
/**
|
||||
|
@ -312,7 +308,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
if (this.rid && !this.tmid) {
|
||||
this.sub = new RoomClass(this.rid);
|
||||
}
|
||||
console.timeEnd(`${this.constructor.name} init`);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -345,19 +340,15 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
EventEmitter.addEventListener('ROOM_REMOVED', this.handleRoomRemoved);
|
||||
console.timeEnd(`${this.constructor.name} mount`);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IRoomViewProps, nextState: IRoomViewState) {
|
||||
const { state } = this;
|
||||
const { roomUpdate, member, isOnHold } = state;
|
||||
const { appState, theme, insets, route } = this.props;
|
||||
const { theme, insets, route } = this.props;
|
||||
if (theme !== nextProps.theme) {
|
||||
return true;
|
||||
}
|
||||
if (appState !== nextProps.appState) {
|
||||
return true;
|
||||
}
|
||||
if (member.statusText !== nextState.member.statusText) {
|
||||
return true;
|
||||
}
|
||||
|
@ -379,7 +370,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
componentDidUpdate(prevProps: IRoomViewProps, prevState: IRoomViewState) {
|
||||
const { roomUpdate, joined } = this.state;
|
||||
const { appState, insets, route } = this.props;
|
||||
const { insets, route } = this.props;
|
||||
|
||||
if (route?.params?.jumpToMessageId && route?.params?.jumpToMessageId !== prevProps.route?.params?.jumpToMessageId) {
|
||||
this.jumpToMessage(route?.params?.jumpToMessageId);
|
||||
|
@ -389,12 +380,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
this.navToThread({ tmid: route?.params?.jumpToThreadId });
|
||||
}
|
||||
|
||||
if (appState === 'foreground' && appState !== prevProps.appState && this.rid) {
|
||||
// Fire List.query() just to keep observables working
|
||||
if (this.list && this.list.current && !isIOS) {
|
||||
this.list.current?.query();
|
||||
}
|
||||
}
|
||||
// If it's a livechat room
|
||||
if (this.t === 'l') {
|
||||
if (
|
||||
|
@ -476,7 +461,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
EventEmitter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
EventEmitter.removeListener('ROOM_REMOVED', this.handleRoomRemoved);
|
||||
console.countReset(`${this.constructor.name}.render calls`);
|
||||
}
|
||||
|
||||
canForwardGuest = async () => {
|
||||
|
@ -532,6 +516,18 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
return room.t === 'l';
|
||||
}
|
||||
|
||||
get hideSystemMessages() {
|
||||
const { sysMes } = this.state.room;
|
||||
const { Hide_System_Messages } = this.props;
|
||||
|
||||
// FIXME: handle servers with version < 3.0.0
|
||||
let hideSystemMessages = Array.isArray(sysMes) ? sysMes : Hide_System_Messages;
|
||||
if (!Array.isArray(hideSystemMessages)) {
|
||||
hideSystemMessages = [];
|
||||
}
|
||||
return hideSystemMessages ?? [];
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { room, unreadsCount, roomUserId, joined, canForwardGuest, canReturnQueue, canPlaceLivechatOnHold } = this.state;
|
||||
const { navigation, isMasterDetail, theme, baseUrl, user, route } = this.props;
|
||||
|
@ -1064,9 +1060,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
const { rid } = this.state.room;
|
||||
const { user } = this.props;
|
||||
sendMessage(rid, message, this.tmid || tmid, user, tshow).then(() => {
|
||||
if (this.list && this.list.current) {
|
||||
this.list.current?.update();
|
||||
}
|
||||
this.setLastOpen(null);
|
||||
Review.pushPositiveEvent();
|
||||
});
|
||||
|
@ -1260,13 +1253,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
this.goRoomActionsView();
|
||||
} else if (handleCommandSearchMessages(event)) {
|
||||
this.goRoomActionsView('SearchMessagesView');
|
||||
} else if (handleCommandReplyLatest(event)) {
|
||||
if (this.list && this.list.current) {
|
||||
const message = this.list.current.getLastMessage();
|
||||
if (message) {
|
||||
this.onReplyInit(message, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1531,17 +1517,13 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { room, loading, canAutoTranslate } = this.state;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, serverVersion } = this.props;
|
||||
const { room, loading } = this.state;
|
||||
const { user, baseUrl, theme, width, serverVersion } = this.props;
|
||||
const { rid, t } = room;
|
||||
let sysMes;
|
||||
let bannerClosed;
|
||||
let announcement;
|
||||
let tunread;
|
||||
let ignored;
|
||||
if ('id' in room) {
|
||||
({ sysMes, bannerClosed, announcement, tunread, ignored } = room);
|
||||
({ bannerClosed, announcement } = room);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1553,16 +1535,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
listRef={this.flatList}
|
||||
rid={rid}
|
||||
tmid={this.tmid}
|
||||
tunread={tunread}
|
||||
ignored={ignored}
|
||||
renderRow={this.renderItem}
|
||||
loading={loading}
|
||||
navigation={navigation}
|
||||
hideSystemMessages={Array.isArray(sysMes) ? sysMes : Hide_System_Messages}
|
||||
hideSystemMessages={this.hideSystemMessages}
|
||||
showMessageInMainThread={user.showMessageInMainThread ?? false}
|
||||
serverVersion={serverVersion}
|
||||
autoTranslateRoom={canAutoTranslate && 'id' in room && room.autoTranslate}
|
||||
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
||||
/>
|
||||
{this.renderFooter()}
|
||||
{this.renderActions()}
|
||||
|
@ -1576,7 +1553,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
||||
useRealName: state.settings.UI_Use_Real_Name as boolean,
|
||||
isAuthenticated: state.login.isAuthenticated,
|
||||
Message_GroupingPeriod: state.settings.Message_GroupingPeriod as number,
|
||||
|
|
Loading…
Reference in New Issue