Skip to content

Commit fb3256b

Browse files
committed
chore: refactor unread messages handling and improve floating UI component
1 parent d8532b7 commit fb3256b

File tree

8 files changed

+103
-64
lines changed

8 files changed

+103
-64
lines changed

packages/uikit-react-native/src/components/ChannelMessageList/index.tsx

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
import SBUUtils from '../../libs/SBUUtils';
4141
import ChatFlatList from '../ChatFlatList';
4242
import { ReactionAddons } from '../ReactionAddons';
43+
import { UnreadMessagesFloatingProps } from '../UnreadMessagesFloating';
4344

4445
type PressActions = { onPress?: () => void; onLongPress?: () => void; bottomSheetItem?: BottomSheetItem };
4546
type HandleableMessage = SendbirdUserMessage | SendbirdFileMessage;
@@ -51,7 +52,6 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
5152
messages: SendbirdMessage[];
5253
newMessages: SendbirdMessage[];
5354
unreadFirstMessage?: SendbirdMessage;
54-
unreadMessageCount: number;
5555
searchItem?: { startingPoint: number };
5656

5757
scrolledAwayFromBottom: boolean;
@@ -62,7 +62,6 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
6262

6363
onPressNewMessagesButton: (animated?: boolean) => void;
6464
onPressScrollToBottomButton: (animated?: boolean) => void;
65-
onPressUnreadMessagesFloatingCloseButton: () => void;
6665

6766
onEditMessage: (message: HandleableMessage) => void;
6867
onReplyMessage?: (message: HandleableMessage) => void; // only available on group channel
@@ -77,7 +76,6 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
7776
message: SendbirdMessage;
7877
prevMessage?: SendbirdMessage;
7978
nextMessage?: SendbirdMessage;
80-
unreadFirstMessage?: SendbirdMessage;
8179
onPress?: () => void;
8280
onLongPress?: () => void;
8381
onPressParentMessage?: ChannelMessageListProps<T>['onPressParentMessage'];
@@ -88,21 +86,18 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
8886
enableMessageGrouping: ChannelMessageListProps<T>['enableMessageGrouping'];
8987
bottomSheetItem?: BottomSheetItem;
9088
isFirstItem: boolean;
89+
isFirstUnreadMessage: boolean;
9190
hideParentMessage?: boolean;
9291
}) => React.ReactElement | null;
9392
renderNewMessagesButton:
9493
| null
9594
| ((props: { visible: boolean; onPress: () => void; newMessages: SendbirdMessage[] }) => React.ReactElement | null);
9695
renderScrollToBottomButton: null | ((props: { visible: boolean; onPress: () => void }) => React.ReactElement | null);
97-
renderUnreadMessagesFloating:
98-
| null
99-
| ((props: {
100-
visible: boolean;
101-
onPressClose: () => void;
102-
unreadMessageCount: number;
103-
}) => React.ReactElement | null);
96+
renderUnreadMessagesFloating: null | ((props: UnreadMessagesFloatingProps) => React.ReactElement | null);
97+
unreadMessagesFloatingProps?: UnreadMessagesFloatingProps;
10498
flatListComponent?: React.ComponentType<FlatListProps<SendbirdMessage>>;
10599
flatListProps?: Omit<FlatListProps<SendbirdMessage>, 'data' | 'renderItem'>;
100+
onViewableItemsChanged?: FlatListProps<SendbirdMessage>['onViewableItemsChanged'];
106101
} & {
107102
ref?: Ref<FlatList<SendbirdMessage>> | undefined;
108103
};
@@ -127,17 +122,17 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
127122
messages,
128123
newMessages,
129124
unreadFirstMessage,
130-
unreadMessageCount,
131125
enableMessageGrouping,
132126
onScrolledAwayFromBottom,
133127
scrolledAwayFromBottom,
134128
onBottomReached,
135129
onTopReached,
136130
flatListComponent,
137131
flatListProps,
132+
onViewableItemsChanged,
138133
onPressNewMessagesButton,
139134
onPressScrollToBottomButton,
140-
onPressUnreadMessagesFloatingCloseButton,
135+
unreadMessagesFloatingProps,
141136
}: ChannelMessageListProps<T>,
142137
ref: React.ForwardedRef<FlatList<SendbirdMessage>>,
143138
) => {
@@ -162,7 +157,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
162157
message: item,
163158
prevMessage: messages[index + 1],
164159
nextMessage: messages[index - 1],
165-
unreadFirstMessage: unreadFirstMessage,
160+
isFirstUnreadMessage: unreadFirstMessage?.messageId === item.messageId,
166161
onPress,
167162
onLongPress,
168163
onPressParentMessage,
@@ -183,17 +178,20 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
183178
<ChannelFrozenBanner style={styles.frozenBanner} text={STRINGS.LABELS.CHANNEL_MESSAGE_LIST_FROZEN} />
184179
)}
185180
{renderUnreadMessagesFloating && (
186-
<View style={[!channel.isFrozen ? styles.unreadMsgButtonWhenFrozen : styles.unreadMsgButton, safeAreaLayout]}>
181+
<View
182+
style={[!channel.isFrozen ? styles.unreadMsgFloatingWhenFrozen : styles.unreadMsgFloating, safeAreaLayout]}
183+
>
187184
{renderUnreadMessagesFloating({
188-
visible: unreadMessageCount > 0,
189-
onPressClose: () => onPressUnreadMessagesFloatingCloseButton(),
190-
unreadMessageCount,
185+
visible: unreadMessagesFloatingProps?.visible ?? false,
186+
onPressClose: () => unreadMessagesFloatingProps?.onPressClose(),
187+
unreadMessageCount: unreadMessagesFloatingProps?.unreadMessageCount ?? 0,
191188
})}
192189
</View>
193190
)}
194191
<ChatFlatList
195192
flatListComponent={flatListComponent}
196193
{...flatListProps}
194+
onViewableItemsChanged={onViewableItemsChanged}
197195
onTopReached={onTopReached}
198196
onBottomReached={onBottomReached}
199197
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
@@ -488,13 +486,13 @@ const styles = createStyleSheet({
488486
frozenListPadding: {
489487
paddingBottom: 32,
490488
},
491-
unreadMsgButton: {
489+
unreadMsgFloating: {
492490
position: 'absolute',
493491
zIndex: 999,
494492
top: 12,
495493
alignSelf: 'center',
496494
},
497-
unreadMsgButtonWhenFrozen: {
495+
unreadMsgFloatingWhenFrozen: {
498496
position: 'absolute',
499497
zIndex: 999,
500498
top: 40,

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/GroupChannelMessageNewMessageSeparator.tsx renamed to packages/uikit-react-native/src/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { Box, Text, useUIKitTheme } from '@sendbird/uikit-react-native-foundatio
66
import { useLocalization } from '../../hooks/useContext';
77

88
type Props = {
9-
shouldRenderMessageSeparator?: boolean;
9+
shouldRenderNewLine?: boolean;
1010
};
1111

12-
const GroupChannelMessageNewMessageSeparator = ({ shouldRenderMessageSeparator }: Props) => {
13-
if (!shouldRenderMessageSeparator) return null;
12+
const GroupChannelMessageNewLine = ({ shouldRenderNewLine }: Props) => {
13+
if (!shouldRenderNewLine) return null;
1414

1515
const { select, palette } = useUIKitTheme();
1616
const { STRINGS } = useLocalization();
@@ -43,4 +43,4 @@ const styles = StyleSheet.create({
4343
},
4444
});
4545

46-
export default React.memo(GroupChannelMessageNewMessageSeparator);
46+
export default React.memo(GroupChannelMessageNewLine);

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/index.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { TypingIndicatorType } from '../../types';
3030
import { ReactionAddons } from '../ReactionAddons';
3131
import GroupChannelMessageDateSeparator from './GroupChannelMessageDateSeparator';
3232
import GroupChannelMessageFocusAnimation from './GroupChannelMessageFocusAnimation';
33-
import GroupChannelMessageNewMessageSeparator from './GroupChannelMessageNewMessageSeparator';
33+
import GroupChannelMessageNewLine from './GroupChannelMessageNewLine';
3434
import GroupChannelMessageOutgoingStatus from './GroupChannelMessageOutgoingStatus';
3535
import GroupChannelMessageParentMessage from './GroupChannelMessageParentMessage';
3636
import GroupChannelMessageReplyInfo from './GroupChannelMessageReplyInfo';
@@ -47,7 +47,7 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
4747
focused,
4848
prevMessage,
4949
nextMessage,
50-
unreadFirstMessage,
50+
isFirstUnreadMessage,
5151
hideParentMessage,
5252
}) => {
5353
const handlers = useSBUHandlers();
@@ -312,17 +312,14 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
312312
}
313313
});
314314

315-
const shouldRenderMessageSeparator = useMemo(() => {
316-
if (sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread && unreadFirstMessage) {
317-
return message.messageId === unreadFirstMessage.messageId;
318-
}
319-
return false;
320-
}, [sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread, unreadFirstMessage?.messageId]);
315+
const shouldRenderNewLine = useMemo(() => {
316+
return sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread && isFirstUnreadMessage;
317+
}, [sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread, isFirstUnreadMessage]);
321318

322319
return (
323320
<Box paddingHorizontal={16} marginBottom={messageGap}>
324321
<GroupChannelMessageDateSeparator message={message} prevMessage={prevMessage} />
325-
<GroupChannelMessageNewMessageSeparator shouldRenderMessageSeparator={shouldRenderMessageSeparator} />
322+
<GroupChannelMessageNewLine shouldRenderNewLine={shouldRenderNewLine} />
326323
<GroupChannelMessageFocusAnimation focused={focused}>{renderMessage()}</GroupChannelMessageFocusAnimation>
327324
</Box>
328325
);

packages/uikit-react-native/src/components/UnreadMessagesFloating.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { Icon, Text, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-rea
55

66
import { useLocalization } from '../hooks/useContext';
77

8-
type Props = {
8+
export type UnreadMessagesFloatingProps = {
99
unreadMessageCount: number;
1010
visible: boolean;
1111
onPressClose: () => void;
1212
};
13-
const UnreadMessagesFloating = ({ unreadMessageCount, visible, onPressClose }: Props) => {
13+
const UnreadMessagesFloating = ({ unreadMessageCount, visible, onPressClose }: UnreadMessagesFloatingProps) => {
1414
const { STRINGS } = useLocalization();
1515
const { select, palette } = useUIKitTheme();
1616
if (unreadMessageCount <= 0 || !visible) return null;

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, { useContext, useEffect, useMemo, useRef } from 'react';
1+
import type { ViewToken } from '@react-native/virtualized-lists';
2+
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
23

34
import { useToast } from '@sendbird/uikit-react-native-foundation';
45
import { useGroupChannelHandler } from '@sendbird/uikit-tools';
@@ -11,6 +12,7 @@ import {
1112
} from '@sendbird/uikit-utils';
1213

1314
import ChannelMessageList from '../../../components/ChannelMessageList';
15+
import { UnreadMessagesFloatingProps } from '../../../components/UnreadMessagesFloating';
1416
import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../../constants';
1517
import { GroupChannelFragmentOptionsPubSubContextPayload } from '../../../contexts/SendbirdChatCtx';
1618
import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
@@ -23,9 +25,15 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
2325
const { sdk, sbOptions, groupChannelFragmentOptions } = useSendbirdChat();
2426
const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
2527
const { subscribe } = useContext(GroupChannelContexts.PubSub);
26-
const { flatListRef, lazyScrollToBottom, lazyScrollToIndex, onPressReplyMessageInThread } = useContext(
27-
GroupChannelContexts.MessageList,
28-
);
28+
const {
29+
flatListRef,
30+
lazyScrollToBottom,
31+
lazyScrollToIndex,
32+
onPressReplyMessageInThread,
33+
setHasSeenNewLine,
34+
isNewLineVisible,
35+
setIsNewLineVisible,
36+
} = useContext(GroupChannelContexts.MessageList);
2937

3038
const isFirstMount = useIsFirstMount();
3139

@@ -155,35 +163,70 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
155163
},
156164
);
157165

158-
const shouldRenderUnreadMessagesFloatingRef = useRef(props.channel.unreadMessageCount > 0);
159-
const onPressUnreadMessagesCloseButton = useFreshCallback(async () => {
160-
shouldRenderUnreadMessagesFloatingRef.current = false;
161-
});
166+
const onPressUnreadMessagesFloatingCloseButton = useCallback(() => {
167+
setHasSeenNewLine?.(true);
168+
}, [setHasSeenNewLine]);
169+
170+
const unreadMessagesFloatingProps: UnreadMessagesFloatingProps = useMemo(() => {
171+
return {
172+
visible:
173+
sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread &&
174+
0 < props.channel.unreadMessageCount &&
175+
!!isNewLineVisible,
176+
onPressClose: onPressUnreadMessagesFloatingCloseButton,
177+
unreadMessageCount: props.channel.unreadMessageCount,
178+
};
179+
}, [
180+
isNewLineVisible,
181+
props.channel.unreadMessageCount,
182+
sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread,
183+
]);
162184

163185
const unreadFirstMessageRef = useRef<SendbirdMessage | undefined>(undefined);
164-
const unreadFirstMessage = useMemo(() => {
186+
useEffect(() => {
165187
if (!unreadFirstMessageRef.current) {
166-
const foundUnreadMessage = props.messages.find((msg) => msg.createdAt - 1 === props.channel.myLastRead);
167-
if (foundUnreadMessage) {
168-
unreadFirstMessageRef.current = foundUnreadMessage;
169-
}
188+
unreadFirstMessageRef.current = props.messages.find((msg, index) => {
189+
const isMarkedAsUnreadMessage = props.channel.myLastRead === msg.createdAt - 1;
190+
let isFirstUnreadAfterReadMessages = false;
191+
if (index > 0) {
192+
const prevMessage = props.messages[index - 1];
193+
isFirstUnreadAfterReadMessages =
194+
prevMessage.createdAt <= props.channel.myLastRead && props.channel.myLastRead < msg.createdAt;
195+
}
196+
197+
return isMarkedAsUnreadMessage || isFirstUnreadAfterReadMessages;
198+
});
170199
}
171-
return unreadFirstMessageRef.current;
172200
}, [props.messages, props.channel.myLastRead]);
173201

202+
const onViewableItemsChanged = useCallback(
203+
(info: { viewableItems: Array<ViewToken<SendbirdMessage>>; changed: Array<ViewToken<SendbirdMessage>> }) => {
204+
const foundViewableUnreadFirstMessage = info.viewableItems.find(
205+
(token) => token.item.createdAt === unreadFirstMessageRef.current?.createdAt,
206+
);
207+
if (foundViewableUnreadFirstMessage) {
208+
setIsNewLineVisible?.(true);
209+
setHasSeenNewLine?.(true);
210+
} else {
211+
setIsNewLineVisible?.(false);
212+
}
213+
},
214+
[],
215+
);
216+
174217
return (
175218
<ChannelMessageList
176219
{...props}
177220
ref={flatListRef}
178221
onReplyMessage={setMessageToReply}
179222
onReplyInThreadMessage={setMessageToReply}
180223
onEditMessage={setMessageToEdit}
224+
onViewableItemsChanged={onViewableItemsChanged}
181225
onPressParentMessage={onPressParentMessage}
182226
onPressNewMessagesButton={scrollToBottom}
183227
onPressScrollToBottomButton={scrollToBottom}
184-
onPressUnreadMessagesFloatingCloseButton={onPressUnreadMessagesCloseButton}
185-
unreadFirstMessage={unreadFirstMessage}
186-
unreadMessageCount={props.channel.unreadMessageCount}
228+
unreadFirstMessage={unreadFirstMessageRef.current}
229+
unreadMessagesFloatingProps={unreadMessagesFloatingProps}
187230
/>
188231
);
189232
};

packages/uikit-react-native/src/domain/groupChannel/module/moduleContext.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export const GroupChannelContexts: GroupChannelContextsType = {
4646
lazyScrollToIndex: () => {
4747
// noop
4848
},
49+
hasSeenNewLine: false,
50+
setHasSeenNewLine: () => NOOP,
51+
isNewLineVisible: false,
52+
setIsNewLineVisible: () => NOOP,
4953
} as MessageListContextValue),
5054
};
5155

@@ -67,6 +71,8 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
6771
const [typingUsers, setTypingUsers] = useState<SendbirdUser[]>([]);
6872
const [messageToEdit, setMessageToEdit] = useState<SendbirdUserMessage | SendbirdFileMessage>();
6973
const [messageToReply, setMessageToReply] = useState<SendbirdUserMessage | SendbirdFileMessage>();
74+
const [hasSeenNewLine, setHasSeenNewLine] = useState<boolean>();
75+
const [isNewLineVisible, setIsNewLineVisible] = useState<boolean>();
7076

7177
const { flatListRef, lazyScrollToIndex, lazyScrollToBottom, scrollToMessage } = useScrollActions({
7278
messages,
@@ -144,6 +150,10 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
144150
lazyScrollToIndex,
145151
lazyScrollToBottom,
146152
onPressReplyMessageInThread,
153+
hasSeenNewLine,
154+
setHasSeenNewLine,
155+
isNewLineVisible,
156+
setIsNewLineVisible,
147157
}}
148158
>
149159
{children}

packages/uikit-react-native/src/domain/groupChannel/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ export interface GroupChannelContextsType {
180180
}) => void;
181181

182182
onPressReplyMessageInThread?: (parentMessage: SendbirdSendableMessage, startingPoint?: number) => void;
183+
hasSeenNewLine?: boolean;
184+
setHasSeenNewLine?: (hasSeenNewLine: boolean) => void;
185+
isNewLineVisible?: boolean;
186+
setIsNewLineVisible?: (isNewLineVisible: boolean) => void;
183187
}>;
184188
}
185189
export interface GroupChannelModule {

packages/uikit-react-native/src/fragments/createGroupChannelFragment.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { ReplyType } from '@sendbird/chat/message';
66
import { Box, useToast } from '@sendbird/uikit-react-native-foundation';
77
import { useGroupChannelMessages } from '@sendbird/uikit-tools';
88
import {
9-
Logger,
109
SendbirdFileMessage,
1110
SendbirdGroupChannel,
1211
SendbirdSendableMessage,
@@ -113,18 +112,6 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
113112
onMessagesUpdated(messages) {
114113
groupChannelPubSub.publish({ type: 'MESSAGES_UPDATED', data: { messages } });
115114
},
116-
onChannelUpdated(_) {
117-
if (sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread) {
118-
Logger.log(
119-
'onChannelUpdated channel.unreadMessageCount:',
120-
channel.unreadMessageCount,
121-
'channel.totalUnreadReplyCount:',
122-
channel.totalUnreadReplyCount,
123-
'channel.myLastRead:',
124-
channel.myLastRead,
125-
);
126-
}
127-
},
128115
onChannelDeleted,
129116
onCurrentUserBanned: onChannelDeleted,
130117
collectionCreator: getCollectionCreator(channel, messageListQueryParams, collectionCreator),

0 commit comments

Comments
 (0)