Skip to content

Commit 68fb16a

Browse files
authored
Jumping to an unread message did not keep scroll location (#534)
* Jumping to an unread message did not set the scroll location to the first unread message * Wait for channel to load before allowing reading messages * Show scroll to last message button after jumping. Disable animations when paginating messages * Mark as read when scrolling to the last message
1 parent b92425e commit 68fb16a

File tree

3 files changed

+62
-43
lines changed

3 files changed

+62
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88

99
### 🐞 Fixed
1010
- Smoother and more performant view updates in channel and message lists [#522](https://github.com/GetStream/stream-chat-swiftui/pull/522)
11+
- Fix scrolling location when jumping to a message not in the currently loaded message list [#533](https://github.com/GetStream/stream-chat-swiftui/pull/533)
1112

1213
# [4.58.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.58.0)
1314
_June 27, 2024_

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
3535

3636
private var isActive = true
3737
private var readsString = ""
38-
private var canMarkRead = true
38+
private var canMarkRead = false
39+
private var hasSetInitialCanMarkRead = false
3940

4041
private let messageListDateOverlay: DateFormatter = DateFormatter.messageListDateOverlay
4142

@@ -44,7 +45,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
4445

4546
private var loadingPreviousMessages: Bool = false
4647
private var loadingMessagesAround: Bool = false
47-
private var lastMessageRead: String?
48+
private var scrollsToUnreadAfterJumpToMessage = false
4849
private var disableDateIndicator = false
4950
private var channelName = ""
5051
private var onlineIndicatorShown = false
@@ -245,25 +246,21 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
245246
updateScrolledIdToNewestMessage()
246247
} else {
247248
channelDataSource.loadFirstPage { [weak self] _ in
248-
self?.scrolledId = self?.messages.first?.messageId
249+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
250+
self?.scrolledId = self?.messages.first?.messageId
251+
self?.showScrollToLatestButton = false
252+
}
249253
}
250254
}
251255
}
252256

253257
public func jumpToMessage(messageId: String) -> Bool {
254258
if messageId == .unknownMessageId {
255259
if firstUnreadMessageId == nil, let lastReadMessageId {
256-
channelDataSource.loadPageAroundMessageId(lastReadMessageId) { [weak self] error in
260+
scrollsToUnreadAfterJumpToMessage = true
261+
channelDataSource.loadPageAroundMessageId(lastReadMessageId) { error in
257262
if error != nil {
258263
log.error("Error loading messages around message \(messageId)")
259-
return
260-
}
261-
if let firstUnread = self?.channelDataSource.firstUnreadMessageId,
262-
let message = self?.channelController.dataStore.message(id: firstUnread) {
263-
self?.firstUnreadMessageId = message.messageId
264-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
265-
self?.scrolledId = message.messageId
266-
}
267264
}
268265
}
269266
}
@@ -338,13 +335,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
338335
} else {
339336
checkForNewerMessages(index: index)
340337
}
341-
if let firstUnreadMessageId, firstUnreadMessageId.contains(message.id) {
338+
if let firstUnreadMessageId, firstUnreadMessageId.contains(message.id), hasSetInitialCanMarkRead {
342339
canMarkRead = true
343340
}
344341
if utils.messageListConfig.dateIndicatorPlacement == .overlay {
345342
save(lastDate: message.createdAt)
346343
}
347-
if index == 0 {
344+
if index == 0, channelDataSource.hasLoadedAllNextMessages {
348345
let isActive = UIApplication.shared.applicationState == .active
349346
if isActive && canMarkRead {
350347
sendReadEventIfNeeded(for: message)
@@ -427,6 +424,17 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
427424

428425
refreshMessageListIfNeeded()
429426

427+
// Jump to a message but we were already scrolled to the bottom
428+
if !channelDataSource.hasLoadedAllNextMessages {
429+
showScrollToLatestButton = true
430+
}
431+
432+
// Set scroll id after the message id has changed
433+
if scrollsToUnreadAfterJumpToMessage, let firstUnreadMessageId {
434+
scrollsToUnreadAfterJumpToMessage = false
435+
scrolledId = firstUnreadMessageId
436+
}
437+
430438
if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
431439
updateScrolledIdToNewestMessage()
432440
}
@@ -441,6 +449,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
441449
checkTypingIndicator()
442450
checkHeaderType()
443451
checkOnlineIndicator()
452+
checkUnreadCount()
444453
}
445454

446455
public func showReactionOverlay(for view: AnyView) {
@@ -472,31 +481,29 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
472481
// MARK: - private
473482

474483
private func checkForOlderMessages(index: Int) {
475-
if index < channelDataSource.messages.count - 25 {
476-
return
477-
}
478-
484+
guard index >= channelDataSource.messages.count - 25 else { return }
485+
guard !loadingPreviousMessages else { return }
486+
guard !channelController.hasLoadedAllPreviousMessages else { return }
487+
479488
log.debug("Loading previous messages")
480-
if !loadingPreviousMessages {
481-
loadingPreviousMessages = true
482-
channelDataSource.loadPreviousMessages(
483-
before: nil,
484-
limit: utils.messageListConfig.pageSize,
485-
completion: { [weak self] _ in
486-
guard let self = self else { return }
489+
loadingPreviousMessages = true
490+
channelDataSource.loadPreviousMessages(
491+
before: nil,
492+
limit: utils.messageListConfig.pageSize,
493+
completion: { [weak self] _ in
494+
guard let self = self else { return }
495+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
487496
self.loadingPreviousMessages = false
488497
}
489-
)
490-
}
498+
}
499+
)
491500
}
492501

493502
private func checkForNewerMessages(index: Int) {
494-
if channelDataSource.hasLoadedAllNextMessages {
495-
return
496-
}
497-
if loadingNextMessages || (index > 5) {
498-
return
499-
}
503+
guard index <= 5 else { return }
504+
guard !loadingNextMessages else { return }
505+
guard !channelController.hasLoadedAllNextMessages else { return }
506+
500507
loadingNextMessages = true
501508

502509
if scrollPosition != messages.first?.messageId {
@@ -529,13 +536,11 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
529536
}
530537

531538
private func sendReadEventIfNeeded(for message: ChatMessage) {
532-
if message.id != lastMessageRead {
533-
lastMessageRead = message.id
534-
throttler.throttle { [weak self] in
535-
self?.channelController.markRead()
536-
DispatchQueue.main.async {
537-
self?.firstUnreadMessageId = nil
538-
}
539+
guard let channel, channel.unreadCount.messages > 0 else { return }
540+
throttler.throttle { [weak self] in
541+
self?.channelController.markRead()
542+
DispatchQueue.main.async {
543+
self?.firstUnreadMessageId = nil
539544
}
540545
}
541546
}
@@ -633,7 +638,14 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
633638

634639
private func checkUnreadCount() {
635640
guard !isMessageThread else { return }
636-
if channelController.channel?.unreadCount.messages ?? 0 > 0 {
641+
642+
guard let channel = channelController.channel else { return }
643+
// Delay marking any messages as read until channel has loaded for the first time (slow internet + observer delay)
644+
guard !hasSetInitialCanMarkRead else { return }
645+
hasSetInitialCanMarkRead = true
646+
canMarkRead = true
647+
648+
if channel.unreadCount.messages > 0 {
637649
if channelController.firstUnreadMessageId != nil {
638650
firstUnreadMessageId = channelController.firstUnreadMessageId
639651
canMarkRead = false
@@ -657,7 +669,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
657669
}
658670

659671
private func shouldAnimate(changes: [ListChange<ChatMessage>]) -> Bool {
660-
if !utils.messageListConfig.messageDisplayOptions.animateChanges || loadingNextMessages {
672+
if !utils.messageListConfig.messageDisplayOptions.animateChanges {
673+
return false
674+
}
675+
if loadingMessagesAround || loadingPreviousMessages || loadingNextMessages {
676+
return false
677+
}
678+
if channelController.channel == nil {
661679
return false
662680
}
663681

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
230230
} else if diff < 0 && scrollDirection == .down {
231231
scrollDirection = .up
232232
}
233+
utils.messageCachingUtils.scrollOffset = offsetValue
233234
}
234-
utils.messageCachingUtils.scrollOffset = offsetValue
235235
let scrollButtonShown = offsetValue < -20
236236
if scrollButtonShown != showScrollToLatestButton {
237237
showScrollToLatestButton = scrollButtonShown

0 commit comments

Comments
 (0)