Skip to content

Commit 0c03992

Browse files
authored
fix: export hooks and add message info read and delivery UI (#3291)
1 parent dffdffa commit 0c03992

File tree

7 files changed

+164
-19
lines changed

7 files changed

+164
-19
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useMemo } from 'react';
2+
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
3+
import { BottomSheetView } from '@gorhom/bottom-sheet';
4+
import {
5+
Avatar,
6+
useChatContext,
7+
useMessageDeliveredData,
8+
useMessageReadData,
9+
useTheme,
10+
} from 'stream-chat-react-native';
11+
import { LocalMessage, UserResponse } from 'stream-chat';
12+
import { StyleSheet, Text, View } from 'react-native';
13+
14+
const renderUserItem = ({ item }: { item: UserResponse }) => (
15+
<View style={styles.userItem}>
16+
<Avatar image={item.image} name={item.name ?? item.id} size={32} />
17+
<Text style={styles.userName}>{item.name ?? item.id}</Text>
18+
</View>
19+
);
20+
21+
const renderEmptyText = ({ text }: { text: string }) => (
22+
<Text style={styles.emptyText}>{text}</Text>
23+
);
24+
25+
export const MessageInfoBottomSheet = ({
26+
message,
27+
ref,
28+
}: {
29+
message?: LocalMessage;
30+
ref: React.RefObject<BottomSheet | null>;
31+
}) => {
32+
const {
33+
theme: { colors },
34+
} = useTheme();
35+
const { client } = useChatContext();
36+
const deliveredStatus = useMessageDeliveredData({ message });
37+
const readStatus = useMessageReadData({ message });
38+
39+
const otherDeliveredToUsers = useMemo(() => {
40+
return deliveredStatus.filter((user: UserResponse) => user.id !== client?.user?.id);
41+
}, [deliveredStatus, client?.user?.id]);
42+
43+
const otherReadUsers = useMemo(() => {
44+
return readStatus.filter((user: UserResponse) => user.id !== client?.user?.id);
45+
}, [readStatus, client?.user?.id]);
46+
47+
return (
48+
<BottomSheet enablePanDownToClose ref={ref} index={-1} snapPoints={['50%']}>
49+
<BottomSheetView style={[styles.container, { backgroundColor: colors.white_smoke }]}>
50+
<Text style={styles.title}>Read</Text>
51+
<BottomSheetFlatList
52+
data={otherReadUsers}
53+
renderItem={renderUserItem}
54+
keyExtractor={(item) => item.id}
55+
style={styles.flatList}
56+
ListEmptyComponent={renderEmptyText({ text: 'No one has read this message.' })}
57+
/>
58+
<Text style={styles.title}>Delivered</Text>
59+
<BottomSheetFlatList
60+
data={otherDeliveredToUsers}
61+
renderItem={renderUserItem}
62+
keyExtractor={(item) => item.id}
63+
style={styles.flatList}
64+
ListEmptyComponent={renderEmptyText({ text: 'The message was not delivered to anyone.' })}
65+
/>
66+
</BottomSheetView>
67+
</BottomSheet>
68+
);
69+
};
70+
71+
const styles = StyleSheet.create({
72+
container: {
73+
flex: 1,
74+
padding: 24,
75+
justifyContent: 'center',
76+
height: '100%',
77+
},
78+
title: {
79+
fontSize: 16,
80+
fontWeight: 'bold',
81+
marginVertical: 8,
82+
},
83+
flatList: {
84+
borderRadius: 16,
85+
},
86+
userItem: {
87+
flexDirection: 'row',
88+
alignItems: 'center',
89+
padding: 8,
90+
backgroundColor: 'white',
91+
},
92+
userName: {
93+
fontSize: 16,
94+
fontWeight: 'bold',
95+
marginLeft: 16,
96+
},
97+
emptyText: {
98+
fontSize: 16,
99+
marginVertical: 16,
100+
textAlign: 'center',
101+
},
102+
});

examples/SampleApp/src/screens/ChannelScreen.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect, useState } from 'react';
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
22
import type { LocalMessage, Channel as StreamChatChannel } from 'stream-chat';
33
import { RouteProp, useFocusEffect, useNavigation } from '@react-navigation/native';
44
import {
@@ -33,6 +33,8 @@ import { channelMessageActions } from '../utils/messageActions.tsx';
3333
import { MessageLocation } from '../components/LocationSharing/MessageLocation.tsx';
3434
import { useStreamChatContext } from '../context/StreamChatContext.tsx';
3535
import { CustomAttachmentPickerSelectionBar } from '../components/AttachmentPickerSelectionBar.tsx';
36+
import BottomSheet from '@gorhom/bottom-sheet';
37+
import { MessageInfoBottomSheet } from '../components/MessageInfoBottomSheet.tsx';
3638

3739
export type ChannelScreenNavigationProp = NativeStackNavigationProp<
3840
StackNavigatorParamList,
@@ -115,19 +117,20 @@ const ChannelHeader: React.FC<ChannelHeaderProps> = ({ channel }) => {
115117

116118
// Either provide channel or channelId.
117119
export const ChannelScreen: React.FC<ChannelScreenProps> = ({
120+
navigation,
118121
route: {
119122
params: { channel: channelFromProp, channelId, messageId },
120123
},
121124
}) => {
122125
const { chatClient, messageListImplementation, messageListMode, messageListPruning } =
123126
useAppContext();
124-
const navigation = useNavigation();
125127
const { bottom } = useSafeAreaInsets();
126128
const {
127129
theme: { colors },
128130
} = useTheme();
129131
const { t } = useTranslationContext();
130132
const { setThread } = useStreamChatContext();
133+
const [selectedMessage, setSelectedMessage] = useState<LocalMessage | undefined>(undefined);
131134

132135
const [channel, setChannel] = useState<StreamChatChannel | undefined>(channelFromProp);
133136

@@ -170,6 +173,9 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
170173

171174
const onThreadSelect = useCallback(
172175
(thread: LocalMessage | null) => {
176+
if (!thread || !channel) {
177+
return;
178+
}
173179
setSelectedThread(thread);
174180
setThread(thread);
175181
navigation.navigate('ThreadScreen', {
@@ -180,6 +186,16 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
180186
[channel, navigation, setThread],
181187
);
182188

189+
const messageInfoBottomSheetRef = useRef<BottomSheet>(null);
190+
191+
const handleMessageInfo = useCallback(
192+
(message: LocalMessage) => {
193+
setSelectedMessage(message);
194+
messageInfoBottomSheetRef.current?.snapToIndex(1);
195+
},
196+
[messageInfoBottomSheetRef],
197+
);
198+
183199
const messageActions = useCallback(
184200
(params: MessageActionsParams) => {
185201
if (!chatClient) {
@@ -190,9 +206,10 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
190206
chatClient,
191207
t,
192208
colors,
209+
handleMessageInfo,
193210
});
194211
},
195-
[chatClient, colors, t],
212+
[chatClient, colors, t, handleMessageInfo],
196213
);
197214

198215
if (!channel || !chatClient) {
@@ -232,6 +249,7 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
232249
)}
233250
<AITypingIndicatorView channel={channel} />
234251
<MessageInput />
252+
<MessageInfoBottomSheet message={selectedMessage} ref={messageInfoBottomSheetRef} />
235253
</Channel>
236254
</View>
237255
);

examples/SampleApp/src/utils/messageActions.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import React from 'react';
12
import { Alert } from 'react-native';
2-
import { StreamChat } from 'stream-chat';
3+
import { LocalMessage, StreamChat } from 'stream-chat';
34
import {
45
Colors,
56
Delete,
7+
Eye,
68
messageActions,
79
MessageActionsParams,
810
Time,
@@ -15,11 +17,13 @@ export function channelMessageActions({
1517
chatClient,
1618
colors,
1719
t,
20+
handleMessageInfo,
1821
}: {
1922
params: MessageActionsParams;
2023
chatClient: StreamChat;
2124
t: TranslationContextValue['t'];
2225
colors?: typeof Colors;
26+
handleMessageInfo: (message: LocalMessage) => void;
2327
}) {
2428
const { dismissOverlay, deleteForMeMessage } = params;
2529
const actions = messageActions(params);
@@ -111,5 +115,15 @@ export function channelMessageActions({
111115
title: t('Delete for me'),
112116
});
113117

118+
actions.push({
119+
action: () => {
120+
dismissOverlay();
121+
handleMessageInfo(params.message);
122+
},
123+
actionType: 'messageInfo',
124+
icon: <Eye height={24} width={24} pathFill={colors?.grey} />,
125+
title: 'Message Info',
126+
});
127+
114128
return actions;
115129
}

package/src/components/Message/Message.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ export const Message = (props: MessageProps) => {
982982
const { openThread } = useThreadContext();
983983
const { t } = useTranslationContext();
984984
const readBy = useMessageReadData({ message });
985-
const deliveredToCount = useMessageDeliveredData({ message });
985+
const deliveredTo = useMessageDeliveredData({ message });
986986
const { setQuotedMessage, setEditingState } = useMessageComposerAPIContext();
987987

988988
return (
@@ -991,13 +991,13 @@ export const Message = (props: MessageProps) => {
991991
{...{
992992
channel,
993993
chatContext,
994-
deliveredToCount,
994+
deliveredToCount: deliveredTo.length,
995995
dismissKeyboard,
996996
enforceUniqueReaction,
997997
members,
998998
messagesContext,
999999
openThread,
1000-
readBy,
1000+
readBy: readBy.length,
10011001
setEditingState,
10021002
setQuotedMessage,
10031003
t,

package/src/components/Message/hooks/useMessageDeliveryData.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
import { useCallback, useEffect, useState } from 'react';
22

3-
import { Event, LocalMessage } from 'stream-chat';
3+
import { Event, LocalMessage, UserResponse } from 'stream-chat';
44

55
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
66
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
77

8-
export const useMessageDeliveredData = ({ message }: { message: LocalMessage }) => {
8+
export const useMessageDeliveredData = ({ message }: { message?: LocalMessage }) => {
99
const { channel } = useChannelContext();
1010
const { client } = useChatContext();
1111
const calculate = useCallback(() => {
12-
if (!message.created_at) {
13-
return 0;
12+
if (!message?.created_at) {
13+
return [];
1414
}
1515
const messageRef = {
1616
msgId: message.id,
1717
timestampMs: new Date(message.created_at).getTime(),
1818
};
19-
return channel.messageReceiptsTracker.deliveredForMessage(messageRef).length;
19+
return channel.messageReceiptsTracker.deliveredForMessage(messageRef);
2020
}, [channel, message]);
2121

22-
const [deliveredToCount, setDeliveredToCount] = useState<number>(calculate());
22+
const [deliveredToCount, setDeliveredToCount] = useState<UserResponse[]>([]);
23+
24+
useEffect(() => {
25+
setDeliveredToCount(calculate());
26+
}, [calculate]);
2327

2428
useEffect(() => {
2529
const { unsubscribe } = channel.on('message.delivered', (event: Event) => {

package/src/components/Message/hooks/useMessageReadData.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
import { useCallback, useEffect, useState } from 'react';
22

3-
import { Event, LocalMessage } from 'stream-chat';
3+
import { Event, LocalMessage, UserResponse } from 'stream-chat';
44

55
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
66
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
77

8-
export const useMessageReadData = ({ message }: { message: LocalMessage }) => {
8+
export const useMessageReadData = ({ message }: { message?: LocalMessage }) => {
99
const { channel } = useChannelContext();
1010
const { client } = useChatContext();
1111
const calculate = useCallback(() => {
12-
if (!message.created_at) {
13-
return 0;
12+
if (!message?.created_at) {
13+
return [];
1414
}
1515
const messageRef = {
1616
msgId: message.id,
1717
timestampMs: new Date(message.created_at).getTime(),
1818
};
1919

20-
return channel.messageReceiptsTracker.readersForMessage(messageRef).length;
20+
return channel.messageReceiptsTracker.readersForMessage(messageRef);
2121
}, [channel, message]);
2222

23-
const [readBy, setReadBy] = useState<number>(calculate());
23+
const [readBy, setReadBy] = useState<UserResponse[]>([]);
24+
25+
useEffect(() => {
26+
setReadBy(calculate());
27+
}, [calculate]);
2428

2529
useEffect(() => {
2630
const { unsubscribe } = channel.on('message.read', (event: Event) => {

package/src/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export * from './ChannelPreview/hooks/useChannelPreviewDisplayPresence';
6464
export * from './ChannelPreview/hooks/useLatestMessagePreview';
6565
export * from './ChannelPreview/hooks/useChannelPreviewData';
6666
export * from './ChannelPreview/hooks/useIsChannelMuted';
67+
export * from './ChannelPreview/hooks/useMessageDeliveryStatus';
6768

6869
export * from './Chat/Chat';
6970
export * from './Chat/hooks/useCreateChatClient';
@@ -93,6 +94,8 @@ export * from './Message/hooks/useCreateMessageContext';
9394
export * from './Message/hooks/useMessageActions';
9495
export * from './Message/hooks/useMessageActionHandlers';
9596
export * from './Message/hooks/useStreamingMessage';
97+
export * from './Message/hooks/useMessageDeliveryData';
98+
export * from './Message/hooks/useMessageReadData';
9699
export * from './Message/Message';
97100
export * from './Message/MessageSimple/MessageAvatar';
98101
export * from './Message/MessageSimple/MessageBounce';

0 commit comments

Comments
 (0)