Skip to content

Commit 6adc82f

Browse files
OnestarLeeCopilotbang9
authored
chore/mark-as-unread-qa (#248)
* chore: refine unread message visibility logic and remove unnecessary height from GroupChannelMessageNewLine * chore: enhance unread message handling and visibility logic in GroupChannelMessageList * refactor: show 99+ when new message count reaches 100 or higher * Update packages/uikit-react-native/src/localization/createBaseStringSet.ts Co-authored-by: Copilot <[email protected]> * chore: streamline unread message detection logic in GroupChannelMessageList * chore: rename UnreadMessage.ts to UnreadMessage.tsx and update string resource references * chore: replace ref with state for new line existence in GroupChannelMessageList * chore: adjust scrolling behavior and update reply type options in GroupChannelMessageList and uikitLocalConfigs * chore: correct variable reference for previous message in GroupChannelMessageList * chore: enable debug mode for Firebase App Distribution in deploy lane * chore: update CA certificates in config.yml * chore: update gem dependencies in Gemfile.lock * chore: update Ruby version to 3.2.2 in config.yml * chore: update Ruby version to 3.0.6 in config.yml * chore: update Ruby version to 3.1.0 in config.yml * Revert "chore: update Ruby version to 3.1.0 in config.yml" This reverts commit 1ef2159. * Revert "chore: update Ruby version to 3.0.6 in config.yml" This reverts commit 55b1e8c. * Revert "chore: update Ruby version to 3.2.2 in config.yml" This reverts commit 8d4f2d6. * Revert "chore: update gem dependencies in Gemfile.lock" This reverts commit 6e2a7d6. * Revert "chore: update CA certificates in config.yml" This reverts commit b14fe0b. * chore: update Android Docker image version to 2025.04.1-node * chore: update android fastlane plugin * chore: add setup for trusted certificates in config.yml * chore: add setup for trusted certificates in config.yml * chore: update confirmAndMarkAsRead to optionally skip unread count check --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: bang9 <[email protected]>
1 parent 9650e6a commit 6adc82f

File tree

10 files changed

+81
-33
lines changed

10 files changed

+81
-33
lines changed

.circleci/config.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ jobs:
8989

9090
deploy-android:
9191
docker:
92-
- image: cimg/android:2023.10-node
92+
- image: cimg/android:2025.04.1-node
9393
resource_class: xlarge
9494
environment:
9595
APP_VERSION: << pipeline.parameters.version >>
@@ -109,6 +109,12 @@ jobs:
109109
- save_cache: *save_node_modules_base
110110
- save_cache: *save_node_modules_packages
111111
- run: *create_app_env
112+
- run:
113+
name: Set up trusted certificates
114+
command: |
115+
sudo apt-get update
116+
sudo apt-get install -y ca-certificates
117+
echo 'export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt' >> $BASH_ENV
112118
- run:
113119
name: Create service-account.json
114120
environment:

docs-validation/2_features/UnreadMessage.ts renamed to docs-validation/2_features/UnreadMessage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import type { StringSet } from '@sendbird/uikit-react-native';
55
* {@link https://sendbird.com/docs/chat/uikit/v3/react-native/features/reactions}
66
* */
77
function _stringResource(str: StringSet) {
8+
str.GROUP_CHANNEL.LIST_NEW_LINE;
89
str.GROUP_CHANNEL.LIST_FLOATING_UNREAD_MSG;
910
str.LABELS.CHANNEL_MESSAGE_MARK_AS_UNREAD;
1011
}
1112
/** ------------------ **/
1213
// interface StringSet {
1314
// GROUP_CHANNEL: {
15+
// LIST_NEW_LINE: string;
1416
// LIST_FLOATING_UNREAD_MSG: (unreadMessageCount: number) => string;
1517
// };
1618
// }
17-
//
19+
1820
// interface StringSet {
1921
// LABELS: {
2022
// CHANNEL_MESSAGE_MARK_AS_UNREAD: string;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const GroupChannelMessageNewLine = ({ shouldRenderNewLine }: Props) => {
2929
const styles = StyleSheet.create({
3030
container: {
3131
width: '100%',
32-
height: 12,
3332
flexDirection: 'row',
3433
alignItems: 'center',
3534
marginBottom: 16,

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

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
3434

3535
const hasSeenNewLineRef = useRef(false);
3636
const isNewLineInViewportRef = useRef(false);
37+
const isNewLineExistInChannelRef = useRef(false);
38+
const scrolledAwayFromBottomRef = useRef(false);
3739
const [isVisibleUnreadMessageFloating, setIsVisibleUnreadMessageFloating] = useState(false);
40+
const viewableMessages = useRef<SendbirdMessage[]>();
41+
const hasUserMarkedAsUnreadRef = useRef(false);
42+
const [unreadFirstMessage, setUnreadFirstMessage] = useState<SendbirdMessage | undefined>(undefined);
3843

3944
const updateHasSeenNewLine = useCallback(
4045
(hasSeenNewLine: boolean) => {
@@ -46,10 +51,6 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
4651
[props.onNewLineSeenChange],
4752
);
4853

49-
const viewableMessages = useRef<SendbirdMessage[]>();
50-
const hasUserMarkedAsUnreadRef = useRef(false);
51-
const [unreadFirstMessage, setUnreadFirstMessage] = useState<SendbirdMessage | undefined>(undefined);
52-
5354
const updateHasUserMarkedAsUnread = useCallback(
5455
(hasUserMarkedAsUnread: boolean) => {
5556
if (hasUserMarkedAsUnreadRef.current !== hasUserMarkedAsUnread) {
@@ -84,13 +85,18 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
8485
},
8586
);
8687

88+
const onScrolledAwayFromBottom = useFreshCallback((value: boolean) => {
89+
scrolledAwayFromBottomRef.current = value;
90+
props.onScrolledAwayFromBottom(value);
91+
});
92+
8793
const scrollToBottom = useFreshCallback(async (animated = false) => {
8894
if (props.hasNext()) {
8995
props.onUpdateSearchItem(undefined);
90-
props.onScrolledAwayFromBottom(false);
96+
onScrolledAwayFromBottom(false);
9197

9298
await props.onResetMessageList().catch((_) => {});
93-
props.onScrolledAwayFromBottom(false);
99+
onScrolledAwayFromBottom(false);
94100
lazyScrollToBottom({ animated });
95101
} else {
96102
lazyScrollToBottom({ animated });
@@ -110,7 +116,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
110116
return null;
111117
}
112118

113-
const prevMessage = props.messages[prevMessageIndex];
119+
const prevMessage = messages[prevMessageIndex];
114120
if (prevMessage) {
115121
if (prevMessage.silent) {
116122
return getPrevNonSilentMessage(messages, prevMessageIndex + 1);
@@ -203,12 +209,22 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
203209
[sbOptions.uikit.groupChannel.channel.enableMarkAsUnread, updateHasUserMarkedAsUnread],
204210
);
205211

212+
useEffect(() => {
213+
isNewLineExistInChannelRef.current = !!props.isNewLineExistInChannel && !!viewableMessages.current;
214+
}, [props.isNewLineExistInChannel, viewableMessages.current]);
215+
206216
const unreadMessagesFloatingPropsRef = useRef<UnreadMessagesFloatingProps>();
207217
const updateUnreadMessagesFloatingProps = useFreshCallback(() => {
218+
const canAutoMarkAsRead =
219+
!scrolledAwayFromBottomRef.current &&
220+
!hasUserMarkedAsUnreadRef.current &&
221+
(hasSeenNewLineRef.current || !isNewLineExistInChannelRef.current);
222+
208223
unreadMessagesFloatingPropsRef.current = {
209224
visible:
210225
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread &&
211-
!!props.isNewLineExistInChannel &&
226+
!canAutoMarkAsRead &&
227+
isNewLineExistInChannelRef.current &&
212228
0 < props.channel.unreadMessageCount &&
213229
!isNewLineInViewportRef.current,
214230
onPressClose: onPressUnreadMessagesFloatingCloseButton,
@@ -221,7 +237,11 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
221237

222238
useEffect(() => {
223239
updateUnreadMessagesFloatingProps();
224-
}, [props.isNewLineExistInChannel, sbOptions.uikit.groupChannel.channel.enableMarkAsUnread]);
240+
}, [
241+
isNewLineExistInChannelRef.current,
242+
props.channel.unreadMessageCount,
243+
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread,
244+
]);
225245

226246
useGroupChannelHandler(sdk, {
227247
onReactionUpdated(channel, event) {
@@ -274,9 +294,13 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
274294
break;
275295
}
276296
case 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER': {
297+
isNewLineExistInChannelRef.current = true;
277298
const foundUnreadFirstMessage = findUnreadFirstMessage(true);
278299
processNewLineVisibility(foundUnreadFirstMessage);
279300
setUnreadFirstMessage(foundUnreadFirstMessage);
301+
if (!props.scrolledAwayFromBottom) {
302+
scrollToBottom(true);
303+
}
280304
break;
281305
}
282306
}
@@ -326,6 +350,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
326350
<ChannelMessageList
327351
{...props}
328352
ref={flatListRef}
353+
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
329354
onReplyMessage={setMessageToReply}
330355
onReplyInThreadMessage={setMessageToReply}
331356
onEditMessage={setMessageToEdit}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,12 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
9292
}
9393
});
9494

95-
const isNewLineExistInChannelRef = useRef(false);
95+
const [isNewLineExistInChannel, setIsNewLineExistInChannel] = useState(false);
9696
const hasSeenNewLineRef = useRef(false);
9797
const hasUserMarkedAsUnreadRef = useRef(false);
9898

9999
useEffect(() => {
100-
isNewLineExistInChannelRef.current =
101-
channel.myLastRead < (channel.lastMessage?.createdAt ?? Number.MIN_SAFE_INTEGER);
100+
setIsNewLineExistInChannel(channel.myLastRead < (channel.lastMessage?.createdAt ?? Number.MIN_SAFE_INTEGER));
102101
}, [channel.url]);
103102

104103
const onNewLineSeenChange = useFreshCallback((hasSeenNewLine: boolean) => {
@@ -114,9 +113,9 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
114113
if (
115114
!scrolledAwayFromBottom &&
116115
!hasUserMarkedAsUnreadRef.current &&
117-
(hasSeenNewLineRef.current || !isNewLineExistInChannelRef.current)
116+
(hasSeenNewLineRef.current || !isNewLineExistInChannel)
118117
) {
119-
confirmAndMarkAsRead(channels);
118+
confirmAndMarkAsRead(channels, true);
120119
}
121120
} else {
122121
confirmAndMarkAsRead(channels);
@@ -154,7 +153,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
154153
}
155154
} else if (ctx?.source === GroupChannelEventSource.EVENT_CHANNEL_UNREAD) {
156155
if (ctx.userIds.includes(currentUser?.userId ?? '')) {
157-
isNewLineExistInChannelRef.current = true;
156+
setIsNewLineExistInChannel(true);
158157
groupChannelPubSub.publish({ type: 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER' });
159158
}
160159
}
@@ -281,7 +280,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
281280
if (!value) {
282281
resetNewMessages();
283282
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
284-
if (!hasUserMarkedAsUnreadRef.current && (hasSeenNewLineRef.current || !isNewLineExistInChannelRef.current)) {
283+
if (!hasUserMarkedAsUnreadRef.current && (hasSeenNewLineRef.current || !isNewLineExistInChannel)) {
285284
confirmAndMarkAsRead([channel]);
286285
}
287286
}
@@ -331,7 +330,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
331330
onPressMediaMessage={_onPressMediaMessage}
332331
flatListComponent={flatListComponent}
333332
flatListProps={memoizedFlatListProps}
334-
isNewLineExistInChannel={isNewLineExistInChannelRef.current}
333+
isNewLineExistInChannel={isNewLineExistInChannel}
335334
onNewLineSeenChange={onNewLineSeenChange}
336335
onUserMarkedAsUnreadChange={onUserMarkedAsUnreadChange}
337336
/>

packages/uikit-react-native/src/localization/createBaseStringSet.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,15 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
117117
GROUP_CHANNEL: {
118118
HEADER_TITLE: (uid, channel) => getGroupChannelTitle(uid, channel, USER_NO_NAME, CHANNEL_NO_MEMBERS),
119119
LIST_DATE_SEPARATOR: (date, locale) => getDateSeparatorFormat(date, locale ?? dateLocale),
120-
LIST_BUTTON_NEW_MSG: (newMessages) =>
121-
newMessages.length === 1 ? `${newMessages.length} new message` : `${newMessages.length} new messages`,
122-
LIST_FLOATING_UNREAD_MSG: (unreadMessageCount) =>
123-
unreadMessageCount === 1 ? `${unreadMessageCount} unread message` : `${unreadMessageCount} unread messages`,
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+
},
124129
LIST_NEW_LINE: 'New messages',
125130
MESSAGE_BUBBLE_TIME: (message, locale) => getMessageTimeFormat(new Date(message.createdAt), locale ?? dateLocale),
126131
MESSAGE_BUBBLE_FILE_TITLE: (message) => message.name,
@@ -135,8 +140,11 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
135140
HEADER_TITLE: 'Thread',
136141
HEADER_SUBTITLE: (uid, channel) => getGroupChannelTitle(uid, channel, USER_NO_NAME, CHANNEL_NO_MEMBERS),
137142
LIST_DATE_SEPARATOR: (date, locale) => getDateSeparatorFormat(date, locale ?? dateLocale),
138-
LIST_BUTTON_NEW_MSG: (newMessages) => `${newMessages.length} new messages`,
139-
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+
},
140148
MESSAGE_BUBBLE_TIME: (message, locale) => getMessageTimeFormat(new Date(message.createdAt), locale ?? dateLocale),
141149
MESSAGE_BUBBLE_FILE_TITLE: (message) => message.name,
142150
MESSAGE_BUBBLE_EDITED_POSTFIX: ' (edited)',

packages/uikit-utils/src/sendbird/channel.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ export const getOpenChannelChatAvailableState = async (channel: SendbirdOpenChan
3434
return { disabled, frozen, muted };
3535
};
3636

37-
export const confirmAndMarkAsRead = (channels: SendbirdBaseChannel[]) => {
37+
export const confirmAndMarkAsRead = (channels: SendbirdBaseChannel[], skipUnreadCountCheck?: boolean) => {
3838
channels
39-
.filter((it): it is SendbirdGroupChannel => it.isGroupChannel() && it.unreadMessageCount > 0)
39+
.filter((it): it is SendbirdGroupChannel => {
40+
if (!it.isGroupChannel()) return false;
41+
return skipUnreadCountCheck ? true : it.unreadMessageCount > 0;
42+
})
4043
.forEach((it) => BufferedRequest.markAsRead.push(() => it.markAsRead(), it.url));
4144
};
4245

sample/android/Gemfile.lock

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ GEM
109109
xcodeproj (>= 1.13.0, < 2.0.0)
110110
xcpretty (~> 0.3.0)
111111
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
112-
fastlane-plugin-firebase_app_distribution (0.5.0)
113-
fastlane-plugin-json (1.1.0)
112+
fastlane-plugin-firebase_app_distribution (0.10.1)
113+
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
114+
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
115+
fastlane-plugin-json (1.1.7)
114116
fastlane-plugin-versioning_android (0.1.1)
115117
gh_inspector (1.1.3)
116118
google-apis-androidpublisher_v3 (0.54.0)
@@ -123,6 +125,10 @@ GEM
123125
representable (~> 3.0)
124126
retriable (>= 2.0, < 4.a)
125127
rexml
128+
google-apis-firebaseappdistribution_v1 (0.3.0)
129+
google-apis-core (>= 0.11.0, < 2.a)
130+
google-apis-firebaseappdistribution_v1alpha (0.2.0)
131+
google-apis-core (>= 0.11.0, < 2.a)
126132
google-apis-iamcredentials_v1 (0.17.0)
127133
google-apis-core (>= 0.11.0, < 2.a)
128134
google-apis-playcustomapp_v1 (0.13.0)

sample/android/fastlane/Fastfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ platform :android do
1414
lane :deploy do
1515
android_set_version_name(gradle_file: "app/build.gradle", version_name: "#{VERSION}-#{DATE}")
1616
gradle(task: "assemble", build_type: "Release", flags: "--no-daemon")
17-
firebase_app_distribution(groups: "sendbird, external")
17+
firebase_app_distribution(groups: "sendbird, external", debug: true)
1818
end
1919
end

sample/src/context/uikitLocalConfigs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { uikitLocalConfigStorage } from '../factory/mmkv';
55
const KEY = 'uikitOptions';
66
const defaultOptions = {
77
rtl: false,
8-
replyType: 'thread' as 'none' | 'thread' | 'quote_reply',
9-
threadReplySelectType: 'thread' as 'thread' | 'parent',
8+
replyType: 'quote_reply' as 'none' | 'thread' | 'quote_reply',
9+
threadReplySelectType: 'parent' as 'thread' | 'parent',
1010
};
1111

1212
type ContextValue = typeof defaultOptions;

0 commit comments

Comments
 (0)