@@ -17,7 +17,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
17
17
private var cancellables = Set < AnyCancellable > ( )
18
18
private var lastRefreshThreshold = 200
19
19
private let refreshThreshold = 200
20
- private let newerMessagesLimit : Int = {
20
+ private static let newerMessagesLimit : Int = {
21
21
if #available( iOS 17 , * ) {
22
22
// On iOS 17 we can maintain the scroll position.
23
23
return 25
@@ -35,6 +35,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
35
35
36
36
private var isActive = true
37
37
private var readsString = " "
38
+ private var canMarkRead = true
38
39
39
40
private let messageListDateOverlay : DateFormatter = DateFormatter . messageListDateOverlay
40
41
@@ -47,6 +48,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
47
48
private var disableDateIndicator = false
48
49
private var channelName = " "
49
50
private var onlineIndicatorShown = false
51
+ private var lastReadMessageId : String ?
50
52
private let throttler = Throttler ( interval: 3 , broadcastLatestEvent: true )
51
53
52
54
public var channelController : ChatChannelController
@@ -108,6 +110,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
108
110
@Published public var shouldShowTypingIndicator = false
109
111
@Published public var scrollPosition : String ?
110
112
@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
+ }
111
120
112
121
public var channel : ChatChannel ? {
113
122
channelController. channel
@@ -179,6 +188,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
179
188
180
189
channelName = channel? . name ?? " "
181
190
checkHeaderType ( )
191
+ checkUnreadCount ( )
182
192
}
183
193
184
194
@objc
@@ -198,7 +208,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
198
208
@objc
199
209
private func applicationWillEnterForeground( ) {
200
210
guard let first = messages. first else { return }
201
- maybeSendReadEvent ( for: first)
211
+ if canMarkRead {
212
+ sendReadEventIfNeeded ( for: first)
213
+ }
202
214
}
203
215
204
216
public func scrollToLastMessage( ) {
@@ -212,6 +224,24 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
212
224
}
213
225
214
226
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
+ }
215
245
if messageId == messages. first? . messageId {
216
246
scrolledId = nil
217
247
return true
@@ -221,9 +251,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
221
251
return true
222
252
}
223
253
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
227
260
}
228
261
return true
229
262
} else {
@@ -246,8 +279,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
246
279
log. error ( " Error loading messages around message \( messageId) " )
247
280
return
248
281
}
282
+ var toJumpId = messageId
283
+ if toJumpId == baseId, let message = self ? . channelController. dataStore. message ( id: toJumpId) {
284
+ toJumpId = message. messageId
285
+ }
249
286
DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
250
- self ? . scrolledId = messageId
287
+ self ? . scrolledId = toJumpId
251
288
self ? . loadingMessagesAround = false
252
289
}
253
290
}
@@ -267,13 +304,16 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
267
304
} else {
268
305
checkForNewerMessages ( index: index)
269
306
}
307
+ if let firstUnreadMessageId, firstUnreadMessageId. contains ( message. id) {
308
+ canMarkRead = true
309
+ }
270
310
if utils. messageListConfig. dateIndicatorPlacement == . overlay {
271
311
save ( lastDate: message. createdAt)
272
312
}
273
313
if index == 0 {
274
314
let isActive = UIApplication . shared. applicationState == . active
275
- if isActive {
276
- maybeSendReadEvent ( for: message)
315
+ if isActive && canMarkRead {
316
+ sendReadEventIfNeeded ( for: message)
277
317
}
278
318
}
279
319
}
@@ -350,11 +390,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
350
390
}
351
391
}
352
392
353
- maybeRefreshMessageList ( )
393
+ refreshMessageListIfNeeded ( )
354
394
355
395
if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
356
396
updateScrolledIdToNewestMessage ( )
357
397
}
398
+
399
+ if lastReadMessageId != nil && firstUnreadMessageId == nil {
400
+ firstUnreadMessageId = channelDataSource. firstUnreadMessageId
401
+ }
358
402
}
359
403
360
404
func dataSource(
@@ -382,6 +426,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
382
426
public func onViewAppear( ) {
383
427
setActive ( )
384
428
messages = channelDataSource. messages
429
+ firstUnreadMessageId = channelDataSource. firstUnreadMessageId
385
430
checkNameChange ( )
386
431
}
387
432
@@ -427,7 +472,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
427
472
scrollPosition = messages [ index] . messageId
428
473
}
429
474
430
- channelDataSource. loadNextMessages ( limit: newerMessagesLimit) { [ weak self] _ in
475
+ channelDataSource. loadNextMessages ( limit: Self . newerMessagesLimit) { [ weak self] _ in
431
476
guard let self = self else { return }
432
477
DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
433
478
self . loadingNextMessages = false
@@ -452,16 +497,19 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
452
497
)
453
498
}
454
499
455
- private func maybeSendReadEvent ( for message: ChatMessage ) {
500
+ private func sendReadEventIfNeeded ( for message: ChatMessage ) {
456
501
if message. id != lastMessageRead {
457
502
lastMessageRead = message. id
458
503
throttler. throttle { [ weak self] in
459
504
self ? . channelController. markRead ( )
505
+ DispatchQueue . main. async {
506
+ self ? . firstUnreadMessageId = nil
507
+ }
460
508
}
461
509
}
462
510
}
463
511
464
- private func maybeRefreshMessageList ( ) {
512
+ private func refreshMessageListIfNeeded ( ) {
465
513
let count = messages. count
466
514
if count > lastRefreshThreshold {
467
515
lastRefreshThreshold = lastRefreshThreshold + refreshThreshold
@@ -552,6 +600,19 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
552
600
}
553
601
}
554
602
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
+
555
616
private func handleDateChange( ) {
556
617
guard showScrollToLatestButton == true , let currentDate = currentDate else {
557
618
currentDateString = nil
@@ -630,6 +691,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
630
691
if messageController == nil {
631
692
utils. channelControllerFactory. clearCurrentController ( )
632
693
ImageCache . shared. trim ( toCost: utils. messageListConfig. cacheSizeOnChatDismiss)
694
+ if !channelDataSource. hasLoadedAllNextMessages {
695
+ channelDataSource. loadFirstPage { _ in }
696
+ }
633
697
}
634
698
}
635
699
}
0 commit comments