[FIX] Messages not loading on some edge cases (#4801)
This commit is contained in:
parent
a1580811ed
commit
5387d31a68
|
@ -28,7 +28,9 @@ export const ROOM = createRequestTypes('ROOM', [
|
|||
'DELETE',
|
||||
'REMOVED',
|
||||
'FORWARD',
|
||||
'USER_TYPING'
|
||||
'USER_TYPING',
|
||||
'HISTORY_REQUEST',
|
||||
'HISTORY_FINISHED'
|
||||
]);
|
||||
export const INQUIRY = createRequestTypes('INQUIRY', [
|
||||
...defaultTypes,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { ERoomType } from '../definitions/ERoomType';
|
||||
import { ERoomType, RoomType } from '../definitions';
|
||||
import { ROOM } from './actionsTypes';
|
||||
|
||||
// TYPE RETURN RELATED
|
||||
|
@ -44,7 +44,24 @@ interface IUserTyping extends Action {
|
|||
status: boolean;
|
||||
}
|
||||
|
||||
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
|
||||
export interface IRoomHistoryRequest extends Action {
|
||||
rid: string;
|
||||
t: RoomType;
|
||||
loaderId: string;
|
||||
}
|
||||
|
||||
export interface IRoomHistoryFinished extends Action {
|
||||
loaderId: string;
|
||||
}
|
||||
|
||||
export type TActionsRoom = TSubscribeRoom &
|
||||
TUnsubscribeRoom &
|
||||
ILeaveRoom &
|
||||
IDeleteRoom &
|
||||
IForwardRoom &
|
||||
IUserTyping &
|
||||
IRoomHistoryRequest &
|
||||
IRoomHistoryFinished;
|
||||
|
||||
export function subscribeRoom(rid: string): TSubscribeRoom {
|
||||
return {
|
||||
|
@ -99,3 +116,19 @@ export function userTyping(rid: string, status = true): IUserTyping {
|
|||
status
|
||||
};
|
||||
}
|
||||
|
||||
export function roomHistoryRequest({ rid, t, loaderId }: { rid: string; t: RoomType; loaderId: string }): IRoomHistoryRequest {
|
||||
return {
|
||||
type: ROOM.HISTORY_REQUEST,
|
||||
rid,
|
||||
t,
|
||||
loaderId
|
||||
};
|
||||
}
|
||||
|
||||
export function roomHistoryFinished({ loaderId }: { loaderId: string }): IRoomHistoryFinished {
|
||||
return {
|
||||
type: ROOM.HISTORY_FINISHED,
|
||||
loaderId
|
||||
};
|
||||
}
|
||||
|
|
|
@ -407,7 +407,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
threadBadgeColor,
|
||||
toggleFollowThread,
|
||||
replies
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{/* @ts-ignore*/}
|
||||
<Message
|
||||
id={id}
|
||||
|
|
|
@ -29,6 +29,7 @@ export * from './ISearch';
|
|||
export * from './TUserStatus';
|
||||
export * from './IProfile';
|
||||
export * from './IReaction';
|
||||
export * from './ERoomType';
|
||||
|
||||
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
||||
navigation: StackNavigationProp<T, S>;
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { SubscriptionType, TAnyMessageModel } from '../../../definitions';
|
||||
import { loadNextMessages, loadMessagesForRoom } from '../../../lib/methods';
|
||||
import { MessageTypeLoad } from '../../../lib/constants';
|
||||
import { SubscriptionType, TAnyMessageModel } from '../../definitions';
|
||||
import { loadNextMessages, loadMessagesForRoom } from '.';
|
||||
import { MessageTypeLoad } from '../constants';
|
||||
|
||||
const getMoreMessages = ({
|
||||
rid,
|
||||
t,
|
||||
tmid,
|
||||
loaderItem
|
||||
}: {
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
tmid?: string;
|
||||
loaderItem: TAnyMessageModel;
|
||||
}): Promise<void> => {
|
||||
if ([MessageTypeLoad.MORE, MessageTypeLoad.PREVIOUS_CHUNK].includes(loaderItem.t as MessageTypeLoad)) {
|
||||
|
@ -25,7 +23,6 @@ const getMoreMessages = ({
|
|||
if (loaderItem.t === MessageTypeLoad.NEXT_CHUNK) {
|
||||
return loadNextMessages({
|
||||
rid,
|
||||
tmid,
|
||||
ts: loaderItem.ts as Date,
|
||||
loaderItem
|
||||
});
|
|
@ -16,6 +16,7 @@ export * from './getSingleMessage';
|
|||
export * from './getSlashCommands';
|
||||
export * from './getThreadName';
|
||||
export * from './getUsersPresence';
|
||||
export * from './getMoreMessages';
|
||||
export * from './loadMessagesForRoom';
|
||||
export * from './loadMissedMessages';
|
||||
export * from './loadNextMessages';
|
||||
|
|
|
@ -15,7 +15,6 @@ const COUNT = 50;
|
|||
interface ILoadNextMessages {
|
||||
rid: string;
|
||||
ts: Date;
|
||||
tmid?: string;
|
||||
loaderItem: TMessageModel;
|
||||
}
|
||||
|
||||
|
@ -32,7 +31,6 @@ export function loadNextMessages(args: ILoadNextMessages): Promise<void> {
|
|||
const loadMoreItem = {
|
||||
_id: generateLoadMoreId(lastMessage._id),
|
||||
rid: lastMessage.rid,
|
||||
tmid: args.tmid,
|
||||
ts: moment(lastMessage.ts).add(1, 'millisecond'),
|
||||
t: MessageTypeLoad.NEXT_CHUNK
|
||||
};
|
||||
|
|
|
@ -19,9 +19,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
|
|||
let messages: IMessage[] = EJSON.fromJSONValue(data?.messages);
|
||||
messages = orderBy(messages, 'ts');
|
||||
|
||||
const message = messages.find(m => m._id === messageId);
|
||||
const tmid = message?.tmid;
|
||||
|
||||
if (messages?.length) {
|
||||
if (data?.moreBefore) {
|
||||
const firstMessage = messages[0];
|
||||
|
@ -30,7 +27,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
|
|||
const loadMoreItem = {
|
||||
_id: generateLoadMoreId(firstMessage._id),
|
||||
rid: firstMessage.rid,
|
||||
tmid,
|
||||
ts: moment(firstMessage.ts).subtract(1, 'millisecond').toDate(),
|
||||
t: MessageTypeLoad.PREVIOUS_CHUNK,
|
||||
msg: firstMessage.msg
|
||||
|
@ -46,7 +42,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
|
|||
const loadMoreItem = {
|
||||
_id: generateLoadMoreId(lastMessage._id),
|
||||
rid: lastMessage.rid,
|
||||
tmid,
|
||||
ts: moment(lastMessage.ts).add(1, 'millisecond').toDate(),
|
||||
t: MessageTypeLoad.NEXT_CHUNK,
|
||||
msg: lastMessage.msg
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { deleteRoom, forwardRoom, leaveRoom, removedRoom, subscribeRoom, unsubscribeRoom } from '../actions/room';
|
||||
import {
|
||||
deleteRoom,
|
||||
forwardRoom,
|
||||
leaveRoom,
|
||||
removedRoom,
|
||||
roomHistoryFinished,
|
||||
roomHistoryRequest,
|
||||
subscribeRoom,
|
||||
unsubscribeRoom
|
||||
} from '../actions/room';
|
||||
import { ERoomType } from '../definitions/ERoomType';
|
||||
import { mockedStore } from './mockedStore';
|
||||
import { initialState } from './room';
|
||||
|
@ -48,4 +57,16 @@ describe('test room reducer', () => {
|
|||
const { isDeleting } = mockedStore.getState().room;
|
||||
expect(isDeleting).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return historyLoaders with one item after call historyRequest', () => {
|
||||
mockedStore.dispatch(roomHistoryRequest({ rid: 'GENERAL', t: 'c', loaderId: 'loader' }));
|
||||
const { historyLoaders } = mockedStore.getState().room;
|
||||
expect(historyLoaders).toEqual(['loader']);
|
||||
});
|
||||
|
||||
it('should return historyLoaders with empty array after call historyFinished', () => {
|
||||
mockedStore.dispatch(roomHistoryFinished({ loaderId: 'loader' }));
|
||||
const { historyLoaders } = mockedStore.getState().room;
|
||||
expect(historyLoaders).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,12 +7,14 @@ export interface IRoom {
|
|||
rid: string;
|
||||
isDeleting: boolean;
|
||||
subscribedRoom: string;
|
||||
historyLoaders: string[];
|
||||
}
|
||||
|
||||
export const initialState: IRoom = {
|
||||
rid: '',
|
||||
isDeleting: false,
|
||||
subscribedRoom: ''
|
||||
subscribedRoom: '',
|
||||
historyLoaders: []
|
||||
};
|
||||
|
||||
export default function (state = initialState, action: TActionsRoom): IRoom {
|
||||
|
@ -56,6 +58,16 @@ export default function (state = initialState, action: TActionsRoom): IRoom {
|
|||
...state,
|
||||
isDeleting: false
|
||||
};
|
||||
case ROOM.HISTORY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
historyLoaders: [...state.historyLoaders, action.loaderId]
|
||||
};
|
||||
case ROOM.HISTORY_FINISHED:
|
||||
return {
|
||||
...state,
|
||||
historyLoaders: state.historyLoaders.filter(loaderId => loaderId !== action.loaderId)
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Alert } from 'react-native';
|
||||
import { delay, put, race, select, take, takeLatest } from 'redux-saga/effects';
|
||||
import { delay, put, race, select, take, takeLatest, actionChannel } from 'redux-saga/effects';
|
||||
|
||||
import EventEmitter from '../lib/methods/helpers/events';
|
||||
import Navigation from '../lib/navigation/appNavigation';
|
||||
|
@ -10,6 +10,26 @@ import I18n from '../i18n';
|
|||
import { showErrorAlert } from '../lib/methods/helpers/info';
|
||||
import { LISTENER } from '../containers/Toast';
|
||||
import { Services } from '../lib/services';
|
||||
import getMoreMessages from '../lib/methods/getMoreMessages';
|
||||
import { getMessageById } from '../lib/database/services/Message';
|
||||
|
||||
function* watchHistoryRequests() {
|
||||
const requestChan = yield actionChannel(types.ROOM.HISTORY_REQUEST);
|
||||
while (true) {
|
||||
const { rid, t, tmid, loaderId } = yield take(requestChan);
|
||||
|
||||
const loaderItem = yield getMessageById(loaderId);
|
||||
if (loaderItem) {
|
||||
try {
|
||||
yield getMoreMessages({ rid, t, tmid, loaderItem });
|
||||
} catch (e) {
|
||||
log(e);
|
||||
} finally {
|
||||
yield put({ type: types.ROOM.HISTORY_FINISHED, loaderId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
|
@ -132,5 +152,6 @@ const root = function* root() {
|
|||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||
yield takeLatest(types.ROOM.DELETE, handleDeleteRoom);
|
||||
yield takeLatest(types.ROOM.FORWARD, handleForwardRoom);
|
||||
yield watchHistoryRequests();
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -66,7 +66,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
|
|||
*/
|
||||
.filter(sub => subscribedRoom !== sub.rid)
|
||||
.map(sub => sub.lastMessage && buildMessage(sub.lastMessage))
|
||||
.filter(lm => lm);
|
||||
.filter(lm => lm && lm._id && lm.rid);
|
||||
const lastMessagesIds = lastMessages.map(lm => lm._id).filter(lm => lm);
|
||||
const existingMessages = yield messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch();
|
||||
const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id));
|
||||
|
|
|
@ -11,28 +11,28 @@ export default {
|
|||
title: 'RoomView/LoadMore'
|
||||
};
|
||||
|
||||
const load = () => new Promise(res => setTimeout(res, 1000));
|
||||
|
||||
const LoadMore = ({ ...props }) => <LoadMoreComponent type={MessageTypeLoad.MORE} load={load} runOnRender={false} {...props} />;
|
||||
const LoadMore = ({ ...props }) => (
|
||||
<LoadMoreComponent rid='rid' t='c' loaderId='loaderId' type={MessageTypeLoad.MORE} runOnRender={false} {...props} />
|
||||
);
|
||||
|
||||
export const Basic = () => (
|
||||
<>
|
||||
<LoadMore />
|
||||
<LoadMore runOnRender />
|
||||
<LoadMore type={MessageTypeLoad.PREVIOUS_CHUNK} />
|
||||
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} />
|
||||
<LoadMore loaderId='1' />
|
||||
<LoadMore loaderId='2' runOnRender />
|
||||
<LoadMore loaderId='3' type={MessageTypeLoad.PREVIOUS_CHUNK} />
|
||||
<LoadMore loaderId='4' type={MessageTypeLoad.NEXT_CHUNK} />
|
||||
</>
|
||||
);
|
||||
|
||||
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
||||
<ThemeContext.Provider value={{ theme, colors: themes[theme] }}>
|
||||
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<LoadMore type={MessageTypeLoad.PREVIOUS_CHUNK} />
|
||||
<LoadMore loaderId='5' type={MessageTypeLoad.PREVIOUS_CHUNK} />
|
||||
<Message msg='Hey!' theme={theme} />
|
||||
<Message msg={longText} theme={theme} isHeader={false} />
|
||||
<Message msg='Older message' theme={theme} isHeader={false} />
|
||||
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} />
|
||||
<LoadMore type={MessageTypeLoad.MORE} />
|
||||
<LoadMore loaderId='6' type={MessageTypeLoad.NEXT_CHUNK} />
|
||||
<LoadMore loaderId='7' type={MessageTypeLoad.MORE} />
|
||||
<Message msg={longText} theme={theme} />
|
||||
<Message msg='This is the third message' isHeader={false} theme={theme} />
|
||||
<Message msg='This is the second message' isHeader={false} theme={theme} />
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ActivityIndicator, StyleSheet, Text } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { MessageTypeLoad, themes } from '../../../lib/constants';
|
||||
import { MessageType } from '../../../definitions';
|
||||
import { MessageTypeLoad } from '../../../lib/constants';
|
||||
import { MessageType, RoomType } from '../../../definitions';
|
||||
import { useTheme } from '../../../theme';
|
||||
import Touch from '../../../containers/Touch';
|
||||
import sharedStyles from '../../Styles';
|
||||
import I18n from '../../../i18n';
|
||||
import { roomHistoryRequest } from '../../../actions/room';
|
||||
import { useAppSelector } from '../../../lib/hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
|
@ -20,29 +23,25 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const LoadMore = ({
|
||||
load,
|
||||
const LoadMore = React.memo(
|
||||
({
|
||||
rid,
|
||||
t,
|
||||
loaderId,
|
||||
type,
|
||||
runOnRender
|
||||
}: {
|
||||
load: Function;
|
||||
}: {
|
||||
rid: string;
|
||||
t: RoomType;
|
||||
loaderId: string;
|
||||
type: MessageType;
|
||||
runOnRender: boolean;
|
||||
}): React.ReactElement => {
|
||||
const { theme } = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
}): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const loading = useAppSelector(state => state.room.historyLoaders.some(historyLoader => historyLoader === loaderId));
|
||||
|
||||
const handleLoad = useCallback(async () => {
|
||||
try {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
await load();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [loading]);
|
||||
const handleLoad = () => dispatch(roomHistoryRequest({ rid, t, loaderId }));
|
||||
|
||||
useEffect(() => {
|
||||
if (runOnRender) {
|
||||
|
@ -61,12 +60,13 @@ const LoadMore = ({
|
|||
return (
|
||||
<Touch onPress={handleLoad} style={styles.button} enabled={!loading}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={themes[theme].auxiliaryText} />
|
||||
<ActivityIndicator color={colors.auxiliaryText} />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text>
|
||||
<Text style={[styles.text, { color: colors.titleText }]}>{I18n.t(text)}</Text>
|
||||
)}
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default LoadMore;
|
||||
|
|
|
@ -75,7 +75,8 @@ import {
|
|||
TThreadModel,
|
||||
ICustomEmojis,
|
||||
IEmoji,
|
||||
TGetCustomEmoji
|
||||
TGetCustomEmoji,
|
||||
RoomType
|
||||
} from '../../definitions';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
|
||||
import { TListRef } from './List/List';
|
||||
|
@ -685,7 +686,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
await loadThreadMessages({ tmid: this.tmid, rid: this.rid });
|
||||
} else {
|
||||
const newLastOpen = new Date();
|
||||
await RoomServices.getMessages(room);
|
||||
await RoomServices.getMessages({
|
||||
rid: room.rid,
|
||||
lastOpen: 'lastOpen' in room ? room.lastOpen : undefined,
|
||||
t: room.t as RoomType
|
||||
});
|
||||
|
||||
// if room is joined
|
||||
if (joined && 'id' in room) {
|
||||
|
@ -1301,16 +1306,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
return false;
|
||||
};
|
||||
|
||||
onLoadMoreMessages = (loaderItem: TAnyMessageModel) => {
|
||||
const { room } = this.state;
|
||||
return RoomServices.getMoreMessages({
|
||||
rid: room.rid,
|
||||
tmid: this.tmid,
|
||||
t: room.t as any,
|
||||
loaderItem
|
||||
});
|
||||
};
|
||||
|
||||
goToCannedResponses = () => {
|
||||
const { room } = this.state;
|
||||
Navigation.navigate('CannedResponsesListView', { rid: room.rid });
|
||||
|
@ -1337,7 +1332,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
if (item.t && MESSAGE_TYPE_ANY_LOAD.includes(item.t as MessageTypeLoad)) {
|
||||
content = (
|
||||
<LoadMore
|
||||
load={() => this.onLoadMoreMessages(item)}
|
||||
rid={room.rid}
|
||||
t={room.t as RoomType}
|
||||
loaderId={item.id}
|
||||
type={item.t}
|
||||
runOnRender={item.t === MessageTypeLoad.MORE && !previousItem}
|
||||
/>
|
||||
|
@ -1502,7 +1499,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
return (
|
||||
<>
|
||||
<MessageActions
|
||||
ref={ref => this.messageActions = ref}
|
||||
ref={ref => (this.messageActions = ref)}
|
||||
tmid={this.tmid}
|
||||
room={room}
|
||||
user={user}
|
||||
|
@ -1512,7 +1509,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
onReactionPress={this.onReactionPress}
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
<MessageErrorActions ref={ref => this.messageErrorActions = ref} tmid={this.tmid} />
|
||||
<MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import { loadMessagesForRoom, loadMissedMessages } from '../../../lib/methods';
|
||||
import { loadMessagesForRoom, loadMissedMessages, RoomTypes } from '../../../lib/methods';
|
||||
|
||||
// TODO: clarify latest vs lastOpen
|
||||
const getMessages = ({
|
||||
rid,
|
||||
t,
|
||||
latest,
|
||||
lastOpen,
|
||||
loaderItem
|
||||
}: {
|
||||
interface IBaseParams {
|
||||
rid: string;
|
||||
t?: string;
|
||||
latest?: Date;
|
||||
lastOpen?: Date;
|
||||
loaderItem?: any; // TODO: type this
|
||||
}): Promise<void> => {
|
||||
if (lastOpen) {
|
||||
return loadMissedMessages({ rid, lastOpen });
|
||||
}
|
||||
|
||||
interface ILoadMessagesForRoomParams extends IBaseParams {
|
||||
t: RoomTypes;
|
||||
}
|
||||
|
||||
interface ILoadMissedMessagesParams extends IBaseParams {
|
||||
lastOpen: Date;
|
||||
}
|
||||
|
||||
const getMessages = (params: ILoadMissedMessagesParams | ILoadMessagesForRoomParams): Promise<void> => {
|
||||
if ('lastOpen' in params) {
|
||||
return loadMissedMessages(params);
|
||||
}
|
||||
return loadMessagesForRoom({ rid, t: t as any, latest, loaderItem });
|
||||
return loadMessagesForRoom(params);
|
||||
};
|
||||
|
||||
export default getMessages;
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import getMessages from './getMessages';
|
||||
import getMoreMessages from './getMoreMessages';
|
||||
import getMessageInfo from './getMessageInfo';
|
||||
|
||||
export default {
|
||||
getMessages,
|
||||
getMoreMessages,
|
||||
getMessageInfo
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue