@sendbird/uikit-react-native 3.9.6 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/ChannelInput/index.js +13 -2
- package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
- package/lib/commonjs/components/ChannelMessageList/index.js +57 -9
- package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -1
- package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.js +53 -0
- package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.js.map +1 -0
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js +5 -0
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/commonjs/components/UnreadMessagesFloating.js +77 -0
- package/lib/commonjs/components/UnreadMessagesFloating.js.map +1 -0
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +156 -7
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
- package/lib/commonjs/fragments/createGroupChannelFragment.js +57 -3
- package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/commonjs/localization/StringSet.type.js.map +1 -1
- package/lib/commonjs/localization/createBaseStringSet.js +16 -2
- package/lib/commonjs/localization/createBaseStringSet.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/ChannelInput/index.js +13 -2
- package/lib/module/components/ChannelInput/index.js.map +1 -1
- package/lib/module/components/ChannelMessageList/index.js +57 -9
- package/lib/module/components/ChannelMessageList/index.js.map +1 -1
- package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.js +46 -0
- package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.js.map +1 -0
- package/lib/module/components/GroupChannelMessageRenderer/index.js +5 -0
- package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/module/components/UnreadMessagesFloating.js +70 -0
- package/lib/module/components/UnreadMessagesFloating.js.map +1 -0
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +158 -9
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/module/domain/groupChannel/types.js.map +1 -1
- package/lib/module/fragments/createGroupChannelFragment.js +59 -5
- package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/module/localization/StringSet.type.js.map +1 -1
- package/lib/module/localization/createBaseStringSet.js +16 -2
- package/lib/module/localization/createBaseStringSet.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/components/ChannelMessageList/index.d.ts +8 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.d.ts +6 -0
- package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +1 -0
- package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +1 -0
- package/lib/typescript/src/components/UnreadMessagesFloating.d.ts +8 -0
- package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +6 -1
- package/lib/typescript/src/domain/groupChannel/types.d.ts +13 -1
- package/lib/typescript/src/domain/openChannel/component/OpenChannelHeader.d.ts +1 -1
- package/lib/typescript/src/hooks/useChannelInputItems.d.ts +1 -1
- package/lib/typescript/src/localization/StringSet.type.d.ts +3 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +8 -7
- package/src/components/ChannelInput/index.tsx +17 -6
- package/src/components/ChannelMessageList/index.tsx +71 -5
- package/src/components/GroupChannelMessageRenderer/GroupChannelMessageNewLine.tsx +45 -0
- package/src/components/GroupChannelMessageRenderer/index.tsx +5 -0
- package/src/components/UnreadMessagesFloating.tsx +60 -0
- package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +204 -5
- package/src/domain/groupChannel/types.ts +15 -0
- package/src/fragments/createGroupChannelFragment.tsx +67 -4
- package/src/localization/StringSet.type.ts +3 -0
- package/src/localization/createBaseStringSet.ts +16 -4
- package/src/version.ts +1 -1
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ViewToken } from '@react-native/virtualized-lists';
|
|
2
|
+
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
2
3
|
|
|
3
4
|
import { useToast } from '@sendbird/uikit-react-native-foundation';
|
|
4
5
|
import { useGroupChannelHandler } from '@sendbird/uikit-tools';
|
|
5
6
|
import {
|
|
6
7
|
SendbirdMessage,
|
|
7
8
|
SendbirdSendableMessage,
|
|
9
|
+
confirmAndMarkAsRead,
|
|
8
10
|
isDifferentChannel,
|
|
9
11
|
useFreshCallback,
|
|
10
12
|
useIsFirstMount,
|
|
11
13
|
} from '@sendbird/uikit-utils';
|
|
12
14
|
|
|
13
15
|
import ChannelMessageList from '../../../components/ChannelMessageList';
|
|
16
|
+
import { UnreadMessagesFloatingProps } from '../../../components/UnreadMessagesFloating';
|
|
14
17
|
import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../../constants';
|
|
15
18
|
import { GroupChannelFragmentOptionsPubSubContextPayload } from '../../../contexts/SendbirdChatCtx';
|
|
16
19
|
import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
|
|
@@ -22,13 +25,42 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
22
25
|
const { STRINGS } = useLocalization();
|
|
23
26
|
const { sdk, sbOptions, groupChannelFragmentOptions } = useSendbirdChat();
|
|
24
27
|
const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
|
|
25
|
-
const
|
|
28
|
+
const groupChannelPubSub = useContext(GroupChannelContexts.PubSub);
|
|
26
29
|
const { flatListRef, lazyScrollToBottom, lazyScrollToIndex, onPressReplyMessageInThread } = useContext(
|
|
27
30
|
GroupChannelContexts.MessageList,
|
|
28
31
|
);
|
|
29
32
|
|
|
30
33
|
const isFirstMount = useIsFirstMount();
|
|
31
34
|
|
|
35
|
+
const hasSeenNewLineRef = useRef(false);
|
|
36
|
+
const isNewLineInViewportRef = useRef(false);
|
|
37
|
+
const isNewLineExistInChannelRef = useRef(false);
|
|
38
|
+
const scrolledAwayFromBottomRef = useRef(false);
|
|
39
|
+
const [isVisibleUnreadMessageFloating, setIsVisibleUnreadMessageFloating] = useState(false);
|
|
40
|
+
const viewableMessages = useRef<SendbirdMessage[]>();
|
|
41
|
+
const hasUserMarkedAsUnreadRef = useRef(false);
|
|
42
|
+
const [unreadFirstMessage, setUnreadFirstMessage] = useState<SendbirdMessage | undefined>(undefined);
|
|
43
|
+
|
|
44
|
+
const updateHasSeenNewLine = useCallback(
|
|
45
|
+
(hasSeenNewLine: boolean) => {
|
|
46
|
+
if (hasSeenNewLineRef.current !== hasSeenNewLine) {
|
|
47
|
+
hasSeenNewLineRef.current = hasSeenNewLine;
|
|
48
|
+
props.onNewLineSeenChange?.(hasSeenNewLine);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[props.onNewLineSeenChange],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const updateHasUserMarkedAsUnread = useCallback(
|
|
55
|
+
(hasUserMarkedAsUnread: boolean) => {
|
|
56
|
+
if (hasUserMarkedAsUnreadRef.current !== hasUserMarkedAsUnread) {
|
|
57
|
+
hasUserMarkedAsUnreadRef.current = hasUserMarkedAsUnread;
|
|
58
|
+
props.onUserMarkedAsUnreadChange?.(hasUserMarkedAsUnread);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[props.onUserMarkedAsUnreadChange],
|
|
62
|
+
);
|
|
63
|
+
|
|
32
64
|
const scrollToMessageWithCreatedAt = useFreshCallback(
|
|
33
65
|
(createdAt: number, focusAnimated: boolean, timeout: number): boolean => {
|
|
34
66
|
const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
|
|
@@ -53,19 +85,164 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
53
85
|
},
|
|
54
86
|
);
|
|
55
87
|
|
|
88
|
+
const onScrolledAwayFromBottom = useFreshCallback((value: boolean) => {
|
|
89
|
+
scrolledAwayFromBottomRef.current = value;
|
|
90
|
+
props.onScrolledAwayFromBottom(value);
|
|
91
|
+
});
|
|
92
|
+
|
|
56
93
|
const scrollToBottom = useFreshCallback(async (animated = false) => {
|
|
57
94
|
if (props.hasNext()) {
|
|
58
95
|
props.onUpdateSearchItem(undefined);
|
|
59
|
-
|
|
96
|
+
onScrolledAwayFromBottom(false);
|
|
60
97
|
|
|
61
98
|
await props.onResetMessageList().catch((_) => {});
|
|
62
|
-
|
|
99
|
+
onScrolledAwayFromBottom(false);
|
|
63
100
|
lazyScrollToBottom({ animated });
|
|
64
101
|
} else {
|
|
65
102
|
lazyScrollToBottom({ animated });
|
|
66
103
|
}
|
|
67
104
|
});
|
|
68
105
|
|
|
106
|
+
const onPressUnreadMessagesFloatingCloseButton = useCallback(() => {
|
|
107
|
+
updateHasSeenNewLine(true);
|
|
108
|
+
updateHasUserMarkedAsUnread(false);
|
|
109
|
+
props.resetNewMessages?.();
|
|
110
|
+
confirmAndMarkAsRead([props.channel]);
|
|
111
|
+
}, [updateHasSeenNewLine, updateHasUserMarkedAsUnread, props.channel.url, props.resetNewMessages]);
|
|
112
|
+
|
|
113
|
+
const getPrevNonSilentMessage = useCallback(
|
|
114
|
+
(messages: SendbirdMessage[], prevMessageIndex: number): SendbirdMessage | null => {
|
|
115
|
+
if (messages.length <= prevMessageIndex) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const prevMessage = messages[prevMessageIndex];
|
|
120
|
+
if (prevMessage) {
|
|
121
|
+
if (prevMessage.silent) {
|
|
122
|
+
return getPrevNonSilentMessage(messages, prevMessageIndex + 1);
|
|
123
|
+
} else {
|
|
124
|
+
return prevMessage;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
[],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const findFirstUnreadMessage = useFreshCallback((isNewLineExistInChannel: boolean) => {
|
|
133
|
+
if (!sbOptions.uikit.groupChannel.channel.enableMarkAsUnread || !isNewLineExistInChannel) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return props.messages.find((msg, index) => {
|
|
138
|
+
if (msg.silent) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const isMarkedAsUnreadMessage = props.channel.myLastRead === msg.createdAt - 1;
|
|
143
|
+
if (isMarkedAsUnreadMessage) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const prevNonSilentMessage = getPrevNonSilentMessage(props.messages, index + 1);
|
|
148
|
+
const hasNoPreviousAndNoPrevMessage = !props.hasPrevious?.() && prevNonSilentMessage == null;
|
|
149
|
+
const prevMessageIsRead =
|
|
150
|
+
prevNonSilentMessage != null && prevNonSilentMessage.createdAt <= props.channel.myLastRead;
|
|
151
|
+
const isMessageUnread = props.channel.myLastRead < msg.createdAt;
|
|
152
|
+
return (hasNoPreviousAndNoPrevMessage || prevMessageIsRead) && isMessageUnread;
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!unreadFirstMessage) {
|
|
158
|
+
const foundUnreadFirstMessage = findFirstUnreadMessage(props.isNewLineExistInChannel ?? false);
|
|
159
|
+
if (foundUnreadFirstMessage) {
|
|
160
|
+
processNewLineVisibility(foundUnreadFirstMessage);
|
|
161
|
+
setUnreadFirstMessage(foundUnreadFirstMessage);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, [props.messages, props.channel.myLastRead, sbOptions.uikit.groupChannel.channel.enableMarkAsUnread]);
|
|
165
|
+
|
|
166
|
+
const processNewLineVisibility = useFreshCallback((unreadFirstMsg: SendbirdMessage | undefined) => {
|
|
167
|
+
const isNewLineInViewport = !!viewableMessages.current?.some(
|
|
168
|
+
(message) => message.messageId === unreadFirstMsg?.messageId,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (isNewLineInViewportRef.current !== isNewLineInViewport) {
|
|
172
|
+
isNewLineInViewportRef.current = isNewLineInViewport;
|
|
173
|
+
updateUnreadMessagesFloatingProps();
|
|
174
|
+
if (!isNewLineInViewport || hasSeenNewLineRef.current) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
updateHasSeenNewLine(true);
|
|
179
|
+
if (hasUserMarkedAsUnreadRef.current) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (0 < props.newMessages.length) {
|
|
184
|
+
props.channel.markAsUnread(props.newMessages[0]);
|
|
185
|
+
} else {
|
|
186
|
+
props.channel.markAsRead();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const onViewableItemsChanged = useFreshCallback(
|
|
192
|
+
async (info: { viewableItems: Array<ViewToken<SendbirdMessage>>; changed: Array<ViewToken<SendbirdMessage>> }) => {
|
|
193
|
+
if (!sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
viewableMessages.current = info.viewableItems.filter((token) => token.item).map((token) => token.item);
|
|
198
|
+
processNewLineVisibility(unreadFirstMessage);
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const onPressMarkAsUnreadMessage = useCallback(
|
|
203
|
+
async (message: SendbirdMessage) => {
|
|
204
|
+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread && message) {
|
|
205
|
+
await props.channel.markAsUnread(message);
|
|
206
|
+
updateHasUserMarkedAsUnread(true);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
[sbOptions.uikit.groupChannel.channel.enableMarkAsUnread, updateHasUserMarkedAsUnread],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
isNewLineExistInChannelRef.current = !!props.isNewLineExistInChannel && !!viewableMessages.current;
|
|
214
|
+
}, [props.isNewLineExistInChannel, viewableMessages.current]);
|
|
215
|
+
|
|
216
|
+
const unreadMessagesFloatingPropsRef = useRef<UnreadMessagesFloatingProps>();
|
|
217
|
+
const updateUnreadMessagesFloatingProps = useFreshCallback(() => {
|
|
218
|
+
const canAutoMarkAsRead =
|
|
219
|
+
!scrolledAwayFromBottomRef.current &&
|
|
220
|
+
!hasUserMarkedAsUnreadRef.current &&
|
|
221
|
+
(hasSeenNewLineRef.current || !isNewLineExistInChannelRef.current);
|
|
222
|
+
|
|
223
|
+
unreadMessagesFloatingPropsRef.current = {
|
|
224
|
+
visible:
|
|
225
|
+
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread &&
|
|
226
|
+
!canAutoMarkAsRead &&
|
|
227
|
+
isNewLineExistInChannelRef.current &&
|
|
228
|
+
0 < props.channel.unreadMessageCount &&
|
|
229
|
+
!isNewLineInViewportRef.current,
|
|
230
|
+
onPressClose: onPressUnreadMessagesFloatingCloseButton,
|
|
231
|
+
unreadMessageCount: props.channel.unreadMessageCount,
|
|
232
|
+
};
|
|
233
|
+
if (isVisibleUnreadMessageFloating !== unreadMessagesFloatingPropsRef.current.visible) {
|
|
234
|
+
setIsVisibleUnreadMessageFloating(unreadMessagesFloatingPropsRef.current.visible);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
updateUnreadMessagesFloatingProps();
|
|
240
|
+
}, [
|
|
241
|
+
isNewLineExistInChannelRef.current,
|
|
242
|
+
props.channel.unreadMessageCount,
|
|
243
|
+
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread,
|
|
244
|
+
]);
|
|
245
|
+
|
|
69
246
|
useGroupChannelHandler(sdk, {
|
|
70
247
|
onReactionUpdated(channel, event) {
|
|
71
248
|
if (isDifferentChannel(channel, props.channel)) return;
|
|
@@ -76,10 +253,13 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
76
253
|
lazyScrollToBottom({ animated: true, timeout: 250 });
|
|
77
254
|
}
|
|
78
255
|
},
|
|
256
|
+
onChannelChanged(channel) {
|
|
257
|
+
if (isDifferentChannel(channel, props.channel)) return;
|
|
258
|
+
},
|
|
79
259
|
});
|
|
80
260
|
|
|
81
261
|
useEffect(() => {
|
|
82
|
-
return subscribe(({ type, data }) => {
|
|
262
|
+
return groupChannelPubSub.subscribe(({ type, data }) => {
|
|
83
263
|
switch (type) {
|
|
84
264
|
case 'TYPING_BUBBLE_RENDERED':
|
|
85
265
|
case 'MESSAGES_RECEIVED': {
|
|
@@ -109,6 +289,20 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
109
289
|
scrollToBottom(false);
|
|
110
290
|
break;
|
|
111
291
|
}
|
|
292
|
+
case 'ON_MARKED_AS_READ_BY_CURRENT_USER': {
|
|
293
|
+
updateUnreadMessagesFloatingProps();
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER': {
|
|
297
|
+
isNewLineExistInChannelRef.current = true;
|
|
298
|
+
const foundFirstUnreadMessage = findFirstUnreadMessage(true);
|
|
299
|
+
processNewLineVisibility(foundFirstUnreadMessage);
|
|
300
|
+
setUnreadFirstMessage(foundFirstUnreadMessage);
|
|
301
|
+
if (!props.scrolledAwayFromBottom) {
|
|
302
|
+
scrollToBottom(true);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
112
306
|
}
|
|
113
307
|
});
|
|
114
308
|
}, [props.scrolledAwayFromBottom]);
|
|
@@ -156,12 +350,17 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
156
350
|
<ChannelMessageList
|
|
157
351
|
{...props}
|
|
158
352
|
ref={flatListRef}
|
|
353
|
+
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
|
|
159
354
|
onReplyMessage={setMessageToReply}
|
|
160
355
|
onReplyInThreadMessage={setMessageToReply}
|
|
161
356
|
onEditMessage={setMessageToEdit}
|
|
357
|
+
onViewableItemsChanged={onViewableItemsChanged}
|
|
162
358
|
onPressParentMessage={onPressParentMessage}
|
|
163
359
|
onPressNewMessagesButton={scrollToBottom}
|
|
164
360
|
onPressScrollToBottomButton={scrollToBottom}
|
|
361
|
+
onPressMarkAsUnreadMessage={onPressMarkAsUnreadMessage}
|
|
362
|
+
unreadFirstMessage={unreadFirstMessage}
|
|
363
|
+
unreadMessagesFloatingProps={unreadMessagesFloatingPropsRef.current}
|
|
165
364
|
/>
|
|
166
365
|
);
|
|
167
366
|
};
|
|
@@ -41,6 +41,7 @@ export interface GroupChannelProps {
|
|
|
41
41
|
renderMessage?: GroupChannelProps['MessageList']['renderMessage'];
|
|
42
42
|
renderNewMessagesButton?: GroupChannelProps['MessageList']['renderNewMessagesButton'];
|
|
43
43
|
renderScrollToBottomButton?: GroupChannelProps['MessageList']['renderScrollToBottomButton'];
|
|
44
|
+
renderUnreadMessagesFloating?: GroupChannelProps['MessageList']['renderUnreadMessagesFloating'];
|
|
44
45
|
|
|
45
46
|
enableTypingIndicator?: GroupChannelProps['Provider']['enableTypingIndicator'];
|
|
46
47
|
enableMessageGrouping?: GroupChannelProps['MessageList']['enableMessageGrouping'];
|
|
@@ -85,6 +86,7 @@ export interface GroupChannelProps {
|
|
|
85
86
|
| 'renderMessage'
|
|
86
87
|
| 'renderNewMessagesButton'
|
|
87
88
|
| 'renderScrollToBottomButton'
|
|
89
|
+
| 'renderUnreadMessagesFloating'
|
|
88
90
|
| 'flatListComponent'
|
|
89
91
|
| 'flatListProps'
|
|
90
92
|
| 'hasNext'
|
|
@@ -95,6 +97,11 @@ export interface GroupChannelProps {
|
|
|
95
97
|
|
|
96
98
|
// Changing the search item will trigger the focus animation on messages.
|
|
97
99
|
onUpdateSearchItem: (searchItem?: GroupChannelProps['MessageList']['searchItem']) => void;
|
|
100
|
+
hasPrevious?: () => boolean;
|
|
101
|
+
resetNewMessages?: () => void;
|
|
102
|
+
isNewLineExistInChannel?: boolean;
|
|
103
|
+
onNewLineSeenChange?: (hasSeenNewLine: boolean) => void;
|
|
104
|
+
onUserMarkedAsUnreadChange?: (hasUserMarkedAsUnread: boolean) => void;
|
|
98
105
|
};
|
|
99
106
|
Input: PickPartial<
|
|
100
107
|
ChannelInputProps,
|
|
@@ -208,4 +215,12 @@ export type GroupChannelPubSubContextPayload =
|
|
|
208
215
|
| {
|
|
209
216
|
type: 'TYPING_BUBBLE_RENDERED';
|
|
210
217
|
data?: undefined;
|
|
218
|
+
}
|
|
219
|
+
| {
|
|
220
|
+
type: 'ON_MARKED_AS_READ_BY_CURRENT_USER';
|
|
221
|
+
data?: undefined;
|
|
222
|
+
}
|
|
223
|
+
| {
|
|
224
|
+
type: 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER';
|
|
225
|
+
data?: undefined;
|
|
211
226
|
};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type GroupChannel,
|
|
5
|
+
GroupChannelEventSource,
|
|
6
|
+
MessageCollection,
|
|
7
|
+
MessageFilter,
|
|
8
|
+
} from '@sendbird/chat/groupChannel';
|
|
4
9
|
import { ReplyType } from '@sendbird/chat/message';
|
|
5
10
|
import { Box, useToast } from '@sendbird/uikit-react-native-foundation';
|
|
6
11
|
import { useGroupChannelMessages } from '@sendbird/uikit-tools';
|
|
@@ -27,6 +32,7 @@ import GroupChannelMessageRenderer, {
|
|
|
27
32
|
import NewMessagesButton from '../components/NewMessagesButton';
|
|
28
33
|
import ScrollToBottomButton from '../components/ScrollToBottomButton';
|
|
29
34
|
import StatusComposition from '../components/StatusComposition';
|
|
35
|
+
import UnreadMessagesFloating from '../components/UnreadMessagesFloating';
|
|
30
36
|
import createGroupChannelModule from '../domain/groupChannel/module/createGroupChannelModule';
|
|
31
37
|
import type {
|
|
32
38
|
GroupChannelFragment,
|
|
@@ -43,6 +49,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
43
49
|
|
|
44
50
|
return ({
|
|
45
51
|
searchItem,
|
|
52
|
+
renderUnreadMessagesFloating = (props) => <UnreadMessagesFloating {...props} />,
|
|
46
53
|
renderNewMessagesButton = (props) => <NewMessagesButton {...props} />,
|
|
47
54
|
renderScrollToBottomButton = (props) => <ScrollToBottomButton {...props} />,
|
|
48
55
|
renderMessage,
|
|
@@ -85,6 +92,36 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
85
92
|
}
|
|
86
93
|
});
|
|
87
94
|
|
|
95
|
+
const [isNewLineExistInChannel, setIsNewLineExistInChannel] = useState(false);
|
|
96
|
+
const hasSeenNewLineRef = useRef(false);
|
|
97
|
+
const hasUserMarkedAsUnreadRef = useRef(false);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setIsNewLineExistInChannel(channel.myLastRead < (channel.lastMessage?.createdAt ?? Number.MIN_SAFE_INTEGER));
|
|
101
|
+
}, [channel.url]);
|
|
102
|
+
|
|
103
|
+
const onNewLineSeenChange = useFreshCallback((hasSeenNewLine: boolean) => {
|
|
104
|
+
hasSeenNewLineRef.current = hasSeenNewLine;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const onUserMarkedAsUnreadChange = useFreshCallback((hasUserMarkedAsUnread: boolean) => {
|
|
108
|
+
hasUserMarkedAsUnreadRef.current = hasUserMarkedAsUnread;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const markAsRead = useFreshCallback((channels: GroupChannel[]) => {
|
|
112
|
+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
|
|
113
|
+
if (
|
|
114
|
+
!scrolledAwayFromBottom &&
|
|
115
|
+
!hasUserMarkedAsUnreadRef.current &&
|
|
116
|
+
(hasSeenNewLineRef.current || !isNewLineExistInChannel)
|
|
117
|
+
) {
|
|
118
|
+
confirmAndMarkAsRead(channels, true);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
confirmAndMarkAsRead(channels);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
88
125
|
const {
|
|
89
126
|
loading,
|
|
90
127
|
messages,
|
|
@@ -93,6 +130,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
93
130
|
loadNext,
|
|
94
131
|
loadPrevious,
|
|
95
132
|
hasNext,
|
|
133
|
+
hasPrevious,
|
|
96
134
|
sendFileMessage,
|
|
97
135
|
sendUserMessage,
|
|
98
136
|
updateFileMessage,
|
|
@@ -108,11 +146,23 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
108
146
|
onMessagesUpdated(messages) {
|
|
109
147
|
groupChannelPubSub.publish({ type: 'MESSAGES_UPDATED', data: { messages } });
|
|
110
148
|
},
|
|
149
|
+
onChannelUpdated(_, ctx) {
|
|
150
|
+
if (ctx?.source === GroupChannelEventSource.EVENT_CHANNEL_READ) {
|
|
151
|
+
if (ctx.userIds.includes(currentUser?.userId ?? '')) {
|
|
152
|
+
groupChannelPubSub.publish({ type: 'ON_MARKED_AS_READ_BY_CURRENT_USER' });
|
|
153
|
+
}
|
|
154
|
+
} else if (ctx?.source === GroupChannelEventSource.EVENT_CHANNEL_UNREAD) {
|
|
155
|
+
if (ctx.userIds.includes(currentUser?.userId ?? '')) {
|
|
156
|
+
setIsNewLineExistInChannel(true);
|
|
157
|
+
groupChannelPubSub.publish({ type: 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER' });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
111
161
|
onChannelDeleted,
|
|
112
162
|
onCurrentUserBanned: onChannelDeleted,
|
|
113
163
|
collectionCreator: getCollectionCreator(channel, messageListQueryParams, collectionCreator),
|
|
114
164
|
sortComparator,
|
|
115
|
-
markAsRead:
|
|
165
|
+
markAsRead: markAsRead,
|
|
116
166
|
replyType,
|
|
117
167
|
startingPoint: internalSearchItem?.startingPoint,
|
|
118
168
|
});
|
|
@@ -227,7 +277,14 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
227
277
|
},
|
|
228
278
|
);
|
|
229
279
|
const onScrolledAwayFromBottom = useFreshCallback((value: boolean) => {
|
|
230
|
-
if (!value)
|
|
280
|
+
if (!value) {
|
|
281
|
+
resetNewMessages();
|
|
282
|
+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
|
|
283
|
+
if (!hasUserMarkedAsUnreadRef.current && (hasSeenNewLineRef.current || !isNewLineExistInChannel)) {
|
|
284
|
+
confirmAndMarkAsRead([channel]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
231
288
|
setScrolledAwayFromBottom(value);
|
|
232
289
|
});
|
|
233
290
|
|
|
@@ -261,15 +318,21 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
261
318
|
onTopReached={loadPrevious}
|
|
262
319
|
onBottomReached={loadNext}
|
|
263
320
|
hasNext={hasNext}
|
|
321
|
+
hasPrevious={hasPrevious}
|
|
322
|
+
resetNewMessages={resetNewMessages}
|
|
264
323
|
scrolledAwayFromBottom={scrolledAwayFromBottom}
|
|
265
324
|
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
|
|
266
325
|
renderNewMessagesButton={renderNewMessagesButton}
|
|
267
326
|
renderScrollToBottomButton={renderScrollToBottomButton}
|
|
327
|
+
renderUnreadMessagesFloating={renderUnreadMessagesFloating}
|
|
268
328
|
onResendFailedMessage={resendMessage}
|
|
269
329
|
onDeleteMessage={deleteMessage}
|
|
270
330
|
onPressMediaMessage={_onPressMediaMessage}
|
|
271
331
|
flatListComponent={flatListComponent}
|
|
272
332
|
flatListProps={memoizedFlatListProps}
|
|
333
|
+
isNewLineExistInChannel={isNewLineExistInChannel}
|
|
334
|
+
onNewLineSeenChange={onNewLineSeenChange}
|
|
335
|
+
onUserMarkedAsUnreadChange={onUserMarkedAsUnreadChange}
|
|
273
336
|
/>
|
|
274
337
|
<GroupChannelModule.Input
|
|
275
338
|
SuggestedMentionList={GroupChannelModule.SuggestedMentionList}
|
|
@@ -117,6 +117,8 @@ export interface StringSet {
|
|
|
117
117
|
/** GroupChannel > List */
|
|
118
118
|
LIST_DATE_SEPARATOR: (date: Date, locale?: Locale) => string;
|
|
119
119
|
LIST_BUTTON_NEW_MSG: (newMessages: SendbirdMessage[]) => string;
|
|
120
|
+
LIST_FLOATING_UNREAD_MSG: (unreadMessageCount: number) => string;
|
|
121
|
+
LIST_NEW_LINE: string;
|
|
120
122
|
|
|
121
123
|
/** GroupChannel > Message bubble */
|
|
122
124
|
MESSAGE_BUBBLE_TIME: (message: SendbirdMessage, locale?: Locale) => string;
|
|
@@ -312,6 +314,7 @@ export interface StringSet {
|
|
|
312
314
|
CHANNEL_MESSAGE_DELETE: string;
|
|
313
315
|
CHANNEL_MESSAGE_REPLY: string;
|
|
314
316
|
CHANNEL_MESSAGE_THREAD: string;
|
|
317
|
+
CHANNEL_MESSAGE_MARK_AS_UNREAD: string;
|
|
315
318
|
/** Channel > Message > Delete confirm **/
|
|
316
319
|
CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE: string;
|
|
317
320
|
CHANNEL_MESSAGE_DELETE_CONFIRM_OK: string;
|
|
@@ -117,8 +117,16 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
117
117
|
GROUP_CHANNEL: {
|
|
118
118
|
HEADER_TITLE: (uid, channel) => getGroupChannelTitle(uid, channel, USER_NO_NAME, CHANNEL_NO_MEMBERS),
|
|
119
119
|
LIST_DATE_SEPARATOR: (date, locale) => getDateSeparatorFormat(date, locale ?? dateLocale),
|
|
120
|
-
LIST_BUTTON_NEW_MSG: (newMessages) =>
|
|
121
|
-
|
|
120
|
+
LIST_BUTTON_NEW_MSG: (newMessages) => {
|
|
121
|
+
const count = newMessages.length;
|
|
122
|
+
const displayCount = count >= 100 ? '99+' : count;
|
|
123
|
+
return count === 1 ? `${displayCount} new message` : `${displayCount} new messages`;
|
|
124
|
+
},
|
|
125
|
+
LIST_FLOATING_UNREAD_MSG: (unreadMessageCount) => {
|
|
126
|
+
const displayCount = unreadMessageCount >= 100 ? '99+' : unreadMessageCount;
|
|
127
|
+
return unreadMessageCount === 1 ? `${displayCount} unread message` : `${displayCount} unread messages`;
|
|
128
|
+
},
|
|
129
|
+
LIST_NEW_LINE: 'New messages',
|
|
122
130
|
MESSAGE_BUBBLE_TIME: (message, locale) => getMessageTimeFormat(new Date(message.createdAt), locale ?? dateLocale),
|
|
123
131
|
MESSAGE_BUBBLE_FILE_TITLE: (message) => message.name,
|
|
124
132
|
MESSAGE_BUBBLE_EDITED_POSTFIX: ' (edited)',
|
|
@@ -132,8 +140,11 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
132
140
|
HEADER_TITLE: 'Thread',
|
|
133
141
|
HEADER_SUBTITLE: (uid, channel) => getGroupChannelTitle(uid, channel, USER_NO_NAME, CHANNEL_NO_MEMBERS),
|
|
134
142
|
LIST_DATE_SEPARATOR: (date, locale) => getDateSeparatorFormat(date, locale ?? dateLocale),
|
|
135
|
-
LIST_BUTTON_NEW_MSG: (newMessages) =>
|
|
136
|
-
|
|
143
|
+
LIST_BUTTON_NEW_MSG: (newMessages) => {
|
|
144
|
+
const count = newMessages.length;
|
|
145
|
+
const displayCount = count >= 100 ? '99+' : count;
|
|
146
|
+
return count === 1 ? `${displayCount} new message` : `${displayCount} new messages`;
|
|
147
|
+
},
|
|
137
148
|
MESSAGE_BUBBLE_TIME: (message, locale) => getMessageTimeFormat(new Date(message.createdAt), locale ?? dateLocale),
|
|
138
149
|
MESSAGE_BUBBLE_FILE_TITLE: (message) => message.name,
|
|
139
150
|
MESSAGE_BUBBLE_EDITED_POSTFIX: ' (edited)',
|
|
@@ -303,6 +314,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
303
314
|
CHANNEL_MESSAGE_DELETE: 'Delete',
|
|
304
315
|
CHANNEL_MESSAGE_REPLY: 'Reply',
|
|
305
316
|
CHANNEL_MESSAGE_THREAD: 'Reply in thread',
|
|
317
|
+
CHANNEL_MESSAGE_MARK_AS_UNREAD: 'Mark as unread',
|
|
306
318
|
CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE: 'Delete message?',
|
|
307
319
|
CHANNEL_MESSAGE_DELETE_CONFIRM_OK: 'Delete',
|
|
308
320
|
CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL: 'Cancel',
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const VERSION = '3.
|
|
1
|
+
const VERSION = '3.10.1';
|
|
2
2
|
export default VERSION;
|