Skip to content

Commit

Permalink
Add ability to view conversation history past 50 messages (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
duogenesis authored Oct 13, 2023
1 parent d92ad09 commit 923f844
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 20 deletions.
72 changes: 56 additions & 16 deletions components/conversation-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ import {
import { getRandomString } from '../random/string';
import { api } from '../api/api';

// TODO: Re-add the ability to load old messages past the first page

const ConversationScreen = ({navigation, route}) => {
const [messageFetchTimeout, setMessageFetchTimeout] = useState(false);
const [messages, setMessages] = useState<Message[] | null>(null);
const [lastMessageStatus, setLastMessageStatus] = useState<
MessageStatus | null
>(null);
const hasScrolled = useRef(false);
const hasFetchedAll = useRef(false);
const isFetchingNextPage = useRef(false);

const personId: number = route?.params?.personId;
const name: string = route?.params?.name;
Expand All @@ -54,9 +55,21 @@ const ConversationScreen = ({navigation, route}) => {

const listRef = useRef<any>(null)

const lastMamId = (() => {
if (!messages) return '';
if (!messages.length) return '';

const mamId = messages[0].mamId;

if (!mamId) return '';

return mamId;
})();

const scrollToEnd = useCallback(() => {
if (listRef.current) {
if (listRef.current && !hasScrolled.current) {
listRef.current.scrollToEnd({animated: true});
hasScrolled.current = true;
}
}, [listRef.current]);

Expand All @@ -81,6 +94,7 @@ const ConversationScreen = ({navigation, route}) => {
);

if (messageStatus === 'sent') {
hasScrolled.current = false;
setMessages(messages => [...(messages ?? []), message]);

// TODO: Ideally, you wouldn't have to mark messages as sent in this way;
Expand All @@ -103,26 +117,49 @@ const ConversationScreen = ({navigation, route}) => {
);
}, [personId, name]);

const _fetchConversation = useCallback(async () => {
const _messages = await fetchConversation(personId);
setMessageFetchTimeout(_messages === 'timeout');
if (_messages !== 'timeout') {
setMessages(existingMessages =>
[...(existingMessages ?? []), ...(_messages ?? [])]
);
const maybeLoadNextPage = useCallback(async () => {
if (hasFetchedAll.current) {
return;
}
if (isFetchingNextPage.current) {
return;
}

isFetchingNextPage.current = true;
const fetchedMessages = await fetchConversation(personId, lastMamId);

isFetchingNextPage.current = false;

setMessageFetchTimeout(fetchedMessages === 'timeout');
if (fetchedMessages !== 'timeout') {
// Prevents the list from moving up to the newly added speech bubbles and
// triggering another fetch
if (listRef.current) listRef.current.scrollTo({y: 1});

setMessages([...(fetchedMessages ?? []), ...(messages ?? [])]);

hasFetchedAll.current = !(fetchedMessages && fetchedMessages.length);
}
}, [messages, lastMamId]);

const _onReceiveMessage = useCallback((msg) => {
hasScrolled.current = false;
setMessages(msgs => [...(msgs ?? []), msg]);
}, []);

const _onReceiveMessage = useCallback(
(msg) => setMessages(msgs => [...(msgs ?? []), msg]),
[]
);
const isCloseToTop = ({contentOffset}) => contentOffset.y < 20;

const onScroll = useCallback(({nativeEvent}) => {
if (isCloseToTop(nativeEvent)) {
maybeLoadNextPage();
}
}, [maybeLoadNextPage]);

useEffect(() => {
_fetchConversation();
maybeLoadNextPage();

return onReceiveMessage(_onReceiveMessage, personId);
}, [_onReceiveMessage, personId]);
}, []);

return (
<>
Expand Down Expand Up @@ -247,8 +284,11 @@ const ConversationScreen = ({navigation, route}) => {
ref={listRef}
onLayout={scrollToEnd}
onContentSizeChange={scrollToEnd}
onScroll={onScroll}
scrollEventThrottle={0}
contentContainerStyle={{
paddingTop: 10,
paddingBottom: 20,
maxWidth: 600,
width: '100%',
alignSelf: 'center',
Expand Down
17 changes: 13 additions & 4 deletions xmpp/xmpp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Message = {
to: string
fromCurrentUser: boolean
id: string
mamId?: string | undefined
timestamp: Date
};

Expand Down Expand Up @@ -560,7 +561,7 @@ const onReceiveMessage = (
from: from.toString(),
to: to.toString(),
id: id.toString(),
timestamp: new Date(stamp.toString()),
timestamp: stamp.toString() ? new Date(stamp.toString()) : new Date(),
fromCurrentUser: jidToPersonId(from.toString()) == signedInUser?.personId,
};

Expand Down Expand Up @@ -606,6 +607,7 @@ const moveToChats = async (personId: number) => {
const _fetchConversation = async (
withPersonId: number,
callback: (messages: Message[] | 'timeout') => void,
beforeId: string = '',
) => {
if (!_xmpp) return callback('timeout');

Expand All @@ -624,7 +626,7 @@ const _fetchConversation = async (
</x>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>50</max>
<before/>
<before>${beforeId}</before>
</set>
</query>
</iq>
Expand All @@ -650,6 +652,10 @@ const _fetchConversation = async (
const from = xpath.select1(`string(./@from)`, node);
const to = xpath.select1(`string(./@to)`, node);
const id = xpath.select1(`string(./@id)`, node);
const mamId = xpath.select1(
`string(.//ancestor::*[name()='result']/@id)`,
node
);
const stamp = xpath.select1(
`string(./preceding-sibling::*/@stamp | ./following-sibling::*/@stamp)`,
node
Expand All @@ -659,6 +665,7 @@ const _fetchConversation = async (
if (from === null) return;
if (to === null) return;
if (id === null) return;
if (mamId === null) return;
if (stamp === null) return;
if (bodyText === null) return;

Expand All @@ -670,6 +677,7 @@ const _fetchConversation = async (
from: from.toString(),
to: to.toString(),
id: id.toString(),
mamId: mamId ? mamId.toString() : undefined,
timestamp: new Date(stamp.toString()),
fromCurrentUser: fromCurrentUser,
});
Expand Down Expand Up @@ -706,11 +714,12 @@ const _fetchConversation = async (
};

const fetchConversation = async (
withPersonId: number
withPersonId: number,
beforeId: string = '',
): Promise<Message[] | undefined | 'timeout'> => {
const __fetchConversation = new Promise(
(resolve: (messages: Message[] | undefined | 'timeout') => void) =>
_fetchConversation(withPersonId, resolve)
_fetchConversation(withPersonId, resolve, beforeId)
);

return await withTimeout(5000, __fetchConversation);
Expand Down

0 comments on commit 923f844

Please sign in to comment.