@@ -17,7 +17,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
1717 private var cancellables = Set < AnyCancellable > ( )
1818 private var lastRefreshThreshold = 200
1919 private let refreshThreshold = 200
20- private let newerMessagesLimit : Int = {
20+ private static let newerMessagesLimit : Int = {
2121 if #available( iOS 17 , * ) {
2222 // On iOS 17 we can maintain the scroll position.
2323 return 25
@@ -35,6 +35,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
3535
3636 private var isActive = true
3737 private var readsString = " "
38+ private var canMarkRead = true
3839
3940 private let messageListDateOverlay : DateFormatter = DateFormatter . messageListDateOverlay
4041
@@ -47,6 +48,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
4748 private var disableDateIndicator = false
4849 private var channelName = " "
4950 private var onlineIndicatorShown = false
51+ private var lastReadMessageId : String ?
5052 private let throttler = Throttler ( interval: 3 , broadcastLatestEvent: true )
5153
5254 public var channelController : ChatChannelController
@@ -108,6 +110,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
108110 @Published public var shouldShowTypingIndicator = false
109111 @Published public var scrollPosition : String ?
110112 @Published public private( set) var loadingNextMessages : Bool = false
113+ @Published public var firstUnreadMessageId : String ? {
114+ didSet {
115+ if oldValue != nil && firstUnreadMessageId == nil && ( channel? . unreadCount. messages ?? 0 ) > 0 {
116+ channelController. markRead ( )
117+ }
118+ }
119+ }
111120
112121 public var channel : ChatChannel ? {
113122 channelController. channel
@@ -179,6 +188,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
179188
180189 channelName = channel? . name ?? " "
181190 checkHeaderType ( )
191+ checkUnreadCount ( )
182192 }
183193
184194 @objc
@@ -198,7 +208,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
198208 @objc
199209 private func applicationWillEnterForeground( ) {
200210 guard let first = messages. first else { return }
201- maybeSendReadEvent ( for: first)
211+ if canMarkRead {
212+ sendReadEventIfNeeded ( for: first)
213+ }
202214 }
203215
204216 public func scrollToLastMessage( ) {
@@ -212,6 +224,24 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
212224 }
213225
214226 public func jumpToMessage( messageId: String ) -> Bool {
227+ if messageId == . unknownMessageId {
228+ if firstUnreadMessageId == nil , let lastReadMessageId {
229+ channelDataSource. loadPageAroundMessageId ( lastReadMessageId) { [ weak self] error in
230+ if error != nil {
231+ log. error ( " Error loading messages around message \( messageId) " )
232+ return
233+ }
234+ if let firstUnread = self ? . channelDataSource. firstUnreadMessageId,
235+ let message = self ? . channelController. dataStore. message ( id: firstUnread) {
236+ self ? . firstUnreadMessageId = message. messageId
237+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
238+ self ? . scrolledId = message. messageId
239+ }
240+ }
241+ }
242+ }
243+ return false
244+ }
215245 if messageId == messages. first? . messageId {
216246 scrolledId = nil
217247 return true
@@ -221,9 +251,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
221251 return true
222252 }
223253 let alreadyLoaded = messages. map ( \. id) . contains ( baseId)
224- if alreadyLoaded {
225- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2.0 ) {
226- self . scrolledId = nil
254+ if alreadyLoaded && baseId != messageId {
255+ if scrolledId == nil {
256+ scrolledId = messageId
257+ }
258+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2.0 ) { [ weak self] in
259+ self ? . scrolledId = nil
227260 }
228261 return true
229262 } else {
@@ -246,8 +279,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
246279 log. error ( " Error loading messages around message \( messageId) " )
247280 return
248281 }
282+ var toJumpId = messageId
283+ if toJumpId == baseId, let message = self ? . channelController. dataStore. message ( id: toJumpId) {
284+ toJumpId = message. messageId
285+ }
249286 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
250- self ? . scrolledId = messageId
287+ self ? . scrolledId = toJumpId
251288 self ? . loadingMessagesAround = false
252289 }
253290 }
@@ -267,13 +304,16 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
267304 } else {
268305 checkForNewerMessages ( index: index)
269306 }
307+ if let firstUnreadMessageId, firstUnreadMessageId. contains ( message. id) {
308+ canMarkRead = true
309+ }
270310 if utils. messageListConfig. dateIndicatorPlacement == . overlay {
271311 save ( lastDate: message. createdAt)
272312 }
273313 if index == 0 {
274314 let isActive = UIApplication . shared. applicationState == . active
275- if isActive {
276- maybeSendReadEvent ( for: message)
315+ if isActive && canMarkRead {
316+ sendReadEventIfNeeded ( for: message)
277317 }
278318 }
279319 }
@@ -350,11 +390,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
350390 }
351391 }
352392
353- maybeRefreshMessageList ( )
393+ refreshMessageListIfNeeded ( )
354394
355395 if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
356396 updateScrolledIdToNewestMessage ( )
357397 }
398+
399+ if lastReadMessageId != nil && firstUnreadMessageId == nil {
400+ firstUnreadMessageId = channelDataSource. firstUnreadMessageId
401+ }
358402 }
359403
360404 func dataSource(
@@ -382,6 +426,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
382426 public func onViewAppear( ) {
383427 setActive ( )
384428 messages = channelDataSource. messages
429+ firstUnreadMessageId = channelDataSource. firstUnreadMessageId
385430 checkNameChange ( )
386431 }
387432
@@ -427,7 +472,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
427472 scrollPosition = messages [ index] . messageId
428473 }
429474
430- channelDataSource. loadNextMessages ( limit: newerMessagesLimit) { [ weak self] _ in
475+ channelDataSource. loadNextMessages ( limit: Self . newerMessagesLimit) { [ weak self] _ in
431476 guard let self = self else { return }
432477 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
433478 self . loadingNextMessages = false
@@ -452,16 +497,19 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
452497 )
453498 }
454499
455- private func maybeSendReadEvent ( for message: ChatMessage ) {
500+ private func sendReadEventIfNeeded ( for message: ChatMessage ) {
456501 if message. id != lastMessageRead {
457502 lastMessageRead = message. id
458503 throttler. throttle { [ weak self] in
459504 self ? . channelController. markRead ( )
505+ DispatchQueue . main. async {
506+ self ? . firstUnreadMessageId = nil
507+ }
460508 }
461509 }
462510 }
463511
464- private func maybeRefreshMessageList ( ) {
512+ private func refreshMessageListIfNeeded ( ) {
465513 let count = messages. count
466514 if count > lastRefreshThreshold {
467515 lastRefreshThreshold = lastRefreshThreshold + refreshThreshold
@@ -552,6 +600,19 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
552600 }
553601 }
554602
603+ private func checkUnreadCount( ) {
604+ guard !isMessageThread else { return }
605+ if channelController. channel? . unreadCount. messages ?? 0 > 0 {
606+ if channelController. firstUnreadMessageId != nil {
607+ firstUnreadMessageId = channelController. firstUnreadMessageId
608+ canMarkRead = false
609+ } else if channelController. lastReadMessageId != nil {
610+ lastReadMessageId = channelController. lastReadMessageId
611+ canMarkRead = false
612+ }
613+ }
614+ }
615+
555616 private func handleDateChange( ) {
556617 guard showScrollToLatestButton == true , let currentDate = currentDate else {
557618 currentDateString = nil
@@ -630,6 +691,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
630691 if messageController == nil {
631692 utils. channelControllerFactory. clearCurrentController ( )
632693 ImageCache . shared. trim ( toCost: utils. messageListConfig. cacheSizeOnChatDismiss)
694+ if !channelDataSource. hasLoadedAllNextMessages {
695+ channelDataSource. loadFirstPage { _ in }
696+ }
633697 }
634698 }
635699}
0 commit comments