Skip to content

Commit 22cd7b3

Browse files
committed
feat: add MarkAsUnread feature to GroupChannel
1 parent feebbb8 commit 22cd7b3

File tree

21 files changed

+444
-44
lines changed

21 files changed

+444
-44
lines changed

docs-validation/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@react-native-firebase/messaging": "^14.7.0",
1616
"@react-navigation/native": "^6.1.17",
1717
"@react-navigation/native-stack": "^6.10.0",
18-
"@sendbird/chat": "^4.16.0",
18+
"@sendbird/chat": "^4.19.2",
1919
"date-fns": "^4.1.0",
2020
"react": "18.2.0",
2121
"react-native": "0.74.3",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
]
9999
},
100100
"resolutions": {
101-
"@sendbird/chat": "4.16.5",
101+
"@sendbird/chat": "4.19.2",
102102
"@types/react": "^18"
103103
}
104104
}

packages/uikit-chat-hooks/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"typescript": "5.2.2"
5656
},
5757
"peerDependencies": {
58-
"@sendbird/chat": "^4.16.0",
58+
"@sendbird/chat": "^4.19.2",
5959
"react": ">=16.13.1"
6060
},
6161
"react-native-builder-bob": {
Loading
Loading
Loading

packages/uikit-react-native-foundation/src/assets/icon/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const IconAssets = {
3535
'gif': require('./icon-gif.png'),
3636
'info': require('./icon-info.png'),
3737
'leave': require('./icon-leave.png'),
38+
'mark-as-unread': require('./icon-mark-as-unread.png'),
3839
'members': require('./icon-members.png'),
3940
'message': require('./icon-message.png'),
4041
'moderation': require('./icon-moderation.png'),

packages/uikit-react-native/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@openspacelabs/react-native-zoomable-view": "^2.1.5",
6363
"@sendbird/uikit-chat-hooks": "3.9.5",
6464
"@sendbird/uikit-react-native-foundation": "3.9.5",
65-
"@sendbird/uikit-tools": "0.0.7",
65+
"@sendbird/uikit-tools": "0.0.10",
6666
"@sendbird/uikit-utils": "3.9.5"
6767
},
6868
"devDependencies": {
@@ -111,7 +111,8 @@
111111
"@react-native-community/netinfo": ">=9.3.0",
112112
"@react-native-documents/picker": ">=10.0.0",
113113
"@react-native-firebase/messaging": ">=14.4.0",
114-
"@sendbird/chat": "^4.16.0",
114+
"@sendbird/chat": "^4.19.2",
115+
"@sendbird/uikit-tools": ">=0.0.10",
115116
"@sendbird/react-native-scrollview-enhancer": "*",
116117
"date-fns": ">=2.28.0",
117118
"expo-av": ">=12.0.4",

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

Lines changed: 55 additions & 0 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;
@@ -50,6 +51,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
5051
channel: T;
5152
messages: SendbirdMessage[];
5253
newMessages: SendbirdMessage[];
54+
unreadFirstMessage?: SendbirdMessage;
5355
searchItem?: { startingPoint: number };
5456

5557
scrolledAwayFromBottom: boolean;
@@ -68,6 +70,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
6870
onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<HandleableMessage | void>;
6971
onPressParentMessage?: (parentMessage: SendbirdMessage, childMessage: HandleableMessage) => void;
7072
onPressMediaMessage?: (message: SendbirdFileMessage, deleteMessage: () => Promise<void>, uri: string) => void;
73+
onPressMarkAsUnreadMessage?: (message: HandleableMessage) => void;
7174

7275
renderMessage: (props: {
7376
focused: boolean;
@@ -84,14 +87,18 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
8487
enableMessageGrouping: ChannelMessageListProps<T>['enableMessageGrouping'];
8588
bottomSheetItem?: BottomSheetItem;
8689
isFirstItem: boolean;
90+
isFirstUnreadMessage?: boolean;
8791
hideParentMessage?: boolean;
8892
}) => React.ReactElement | null;
8993
renderNewMessagesButton:
9094
| null
9195
| ((props: { visible: boolean; onPress: () => void; newMessages: SendbirdMessage[] }) => React.ReactElement | null);
9296
renderScrollToBottomButton: null | ((props: { visible: boolean; onPress: () => void }) => React.ReactElement | null);
97+
renderUnreadMessagesFloating?: null | ((props: UnreadMessagesFloatingProps) => React.ReactElement | null);
98+
unreadMessagesFloatingProps?: UnreadMessagesFloatingProps;
9399
flatListComponent?: React.ComponentType<FlatListProps<SendbirdMessage>>;
94100
flatListProps?: Omit<FlatListProps<SendbirdMessage>, 'data' | 'renderItem'>;
101+
onViewableItemsChanged?: FlatListProps<SendbirdMessage>['onViewableItemsChanged'];
95102
} & {
96103
ref?: Ref<FlatList<SendbirdMessage>> | undefined;
97104
};
@@ -108,21 +115,26 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
108115
onResendFailedMessage,
109116
onPressMediaMessage,
110117
onPressParentMessage,
118+
onPressMarkAsUnreadMessage,
111119
currentUserId,
120+
renderUnreadMessagesFloating,
112121
renderNewMessagesButton,
113122
renderScrollToBottomButton,
114123
renderMessage,
115124
messages,
116125
newMessages,
126+
unreadFirstMessage,
117127
enableMessageGrouping,
118128
onScrolledAwayFromBottom,
119129
scrolledAwayFromBottom,
120130
onBottomReached,
121131
onTopReached,
122132
flatListComponent,
123133
flatListProps,
134+
onViewableItemsChanged,
124135
onPressNewMessagesButton,
125136
onPressScrollToBottomButton,
137+
unreadMessagesFloatingProps,
126138
}: ChannelMessageListProps<T>,
127139
ref: React.ForwardedRef<FlatList<SendbirdMessage>>,
128140
) => {
@@ -139,6 +151,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
139151
onDeleteMessage,
140152
onResendFailedMessage,
141153
onPressMediaMessage,
154+
onPressMarkAsUnreadMessage,
142155
});
143156

144157
const renderItem: ListRenderItem<SendbirdMessage> = useFreshCallback(({ item, index }) => {
@@ -147,6 +160,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
147160
message: item,
148161
prevMessage: messages[index + 1],
149162
nextMessage: messages[index - 1],
163+
isFirstUnreadMessage: unreadFirstMessage?.messageId === item.messageId,
150164
onPress,
151165
onLongPress,
152166
onPressParentMessage,
@@ -166,9 +180,21 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
166180
{channel.isFrozen && (
167181
<ChannelFrozenBanner style={styles.frozenBanner} text={STRINGS.LABELS.CHANNEL_MESSAGE_LIST_FROZEN} />
168182
)}
183+
{renderUnreadMessagesFloating && (
184+
<View
185+
style={[channel.isFrozen ? styles.unreadMsgFloatingWhenFrozen : styles.unreadMsgFloating, safeAreaLayout]}
186+
>
187+
{renderUnreadMessagesFloating({
188+
visible: unreadMessagesFloatingProps?.visible ?? false,
189+
onPressClose: () => unreadMessagesFloatingProps?.onPressClose(),
190+
unreadMessageCount: unreadMessagesFloatingProps?.unreadMessageCount ?? 0,
191+
})}
192+
</View>
193+
)}
169194
<ChatFlatList
170195
flatListComponent={flatListComponent}
171196
{...flatListProps}
197+
onViewableItemsChanged={onViewableItemsChanged}
172198
onTopReached={onTopReached}
173199
onBottomReached={onBottomReached}
174200
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
@@ -212,6 +238,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
212238
onReplyInThreadMessage,
213239
onDeleteMessage,
214240
onPressMediaMessage,
241+
onPressMarkAsUnreadMessage,
215242
}: Pick<
216243
ChannelMessageListProps<T>,
217244
| 'channel'
@@ -222,6 +249,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
222249
| 'onDeleteMessage'
223250
| 'onResendFailedMessage'
224251
| 'onPressMediaMessage'
252+
| 'onPressMarkAsUnreadMessage'
225253
>): CreateMessagePressActions => {
226254
const handlers = useSBUHandlers();
227255
const { colors } = useUIKitTheme();
@@ -281,6 +309,10 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
281309
}
282310
};
283311

312+
const onMarkAsUnread = (message: HandleableMessage) => {
313+
onPressMarkAsUnreadMessage?.(message);
314+
};
315+
284316
const openSheetForFailedMessage = (message: HandleableMessage) => {
285317
openSheet({
286318
sheetItems: [
@@ -323,6 +355,11 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
323355
title: STRINGS.LABELS.CHANNEL_MESSAGE_COPY,
324356
onPress: () => onCopyText(message),
325357
}),
358+
markAsUnread: (message: HandleableMessage) => ({
359+
icon: 'mark-as-unread' as const,
360+
title: STRINGS.LABELS.CHANNEL_MESSAGE_MARK_AS_UNREAD,
361+
onPress: () => onMarkAsUnread(message),
362+
}),
326363
edit: (message: HandleableMessage) => ({
327364
icon: 'edit' as const,
328365
title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT,
@@ -356,6 +393,9 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
356393
if (message.isUserMessage()) {
357394
sheetItems.push(menu.copy(message));
358395
if (!channel.isEphemeral) {
396+
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
397+
sheetItems.push(menu.markAsUnread(message));
398+
}
359399
if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') {
360400
sheetItems.push(menu.edit(message));
361401
sheetItems.push(menu.delete(message));
@@ -384,6 +424,9 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
384424
} else if (sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
385425
sheetItems.push(menu.reply(message));
386426
}
427+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
428+
sheetItems.push(menu.markAsUnread(message));
429+
}
387430
}
388431
}
389432
}
@@ -446,6 +489,18 @@ const styles = createStyleSheet({
446489
frozenListPadding: {
447490
paddingBottom: 32,
448491
},
492+
unreadMsgFloating: {
493+
position: 'absolute',
494+
zIndex: 999,
495+
top: 12,
496+
alignSelf: 'center',
497+
},
498+
unreadMsgFloatingWhenFrozen: {
499+
position: 'absolute',
500+
zIndex: 999,
501+
top: 40,
502+
alignSelf: 'center',
503+
},
449504
newMsgButton: {
450505
position: 'absolute',
451506
zIndex: 999,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import { StyleSheet, View } from 'react-native';
3+
4+
import { Box, Text, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
5+
6+
import { useLocalization } from '../../hooks/useContext';
7+
8+
type Props = {
9+
shouldRenderNewLine?: boolean;
10+
};
11+
12+
const GroupChannelMessageNewLine = ({ shouldRenderNewLine }: Props) => {
13+
if (!shouldRenderNewLine) return null;
14+
15+
const { select, palette } = useUIKitTheme();
16+
const { STRINGS } = useLocalization();
17+
18+
return (
19+
<View style={styles.container}>
20+
<Box backgroundColor={select({ light: palette.primary300, dark: palette.primary200 })} style={styles.line} />
21+
<Text caption3 color={select({ light: palette.primary300, dark: palette.primary200 })} style={styles.label}>
22+
{STRINGS.GROUP_CHANNEL.LIST_NEW_LINE}
23+
</Text>
24+
<Box backgroundColor={select({ light: palette.primary300, dark: palette.primary200 })} style={styles.line} />
25+
</View>
26+
);
27+
};
28+
29+
const styles = StyleSheet.create({
30+
container: {
31+
width: '100%',
32+
height: 12,
33+
flexDirection: 'row',
34+
alignItems: 'center',
35+
marginBottom: 16,
36+
},
37+
line: {
38+
flex: 1,
39+
height: 1,
40+
},
41+
label: {
42+
marginHorizontal: 4,
43+
},
44+
});
45+
46+
export default React.memo(GroupChannelMessageNewLine);

0 commit comments

Comments
 (0)