@@ -17,6 +17,15 @@ 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 = {
21
+ if #available( iOS 17 , * ) {
22
+ // On iOS 17 we can maintain the scroll position.
23
+ return 25
24
+ } else {
25
+ return 5
26
+ }
27
+ } ( )
28
+
20
29
private var timer : Timer ?
21
30
private var currentDate : Date ? {
22
31
didSet {
@@ -33,6 +42,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
33
42
private lazy var messageCachingUtils = utils. messageCachingUtils
34
43
35
44
private var loadingPreviousMessages : Bool = false
45
+ private var loadingMessagesAround : Bool = false
36
46
private var lastMessageRead : String ?
37
47
private var disableDateIndicator = false
38
48
private var channelName = " "
@@ -96,6 +106,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
96
106
}
97
107
98
108
@Published public var shouldShowTypingIndicator = false
109
+ @Published public var scrollPosition : String ?
110
+ @Published public private( set) var loadingNextMessages : Bool = false
99
111
100
112
public var channel : ChatChannel ? {
101
113
channelController. channel
@@ -129,7 +141,17 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
129
141
messages = channelDataSource. messages
130
142
131
143
DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) { [ weak self] in
132
- self ? . scrolledId = scrollToMessage? . messageId
144
+ if let scrollToMessage, let parentMessageId = scrollToMessage. parentMessageId, messageController == nil {
145
+ let message = channelController. dataStore. message ( id: parentMessageId)
146
+ self ? . threadMessage = message
147
+ self ? . threadMessageShown = true
148
+ self ? . messageCachingUtils. jumpToReplyId = scrollToMessage. messageId
149
+ } else if messageController != nil , let jumpToReplyId = self ? . messageCachingUtils. jumpToReplyId {
150
+ self ? . scrolledId = jumpToReplyId
151
+ self ? . messageCachingUtils. jumpToReplyId = nil
152
+ } else if messageController == nil {
153
+ self ? . scrolledId = scrollToMessage? . messageId
154
+ }
133
155
}
134
156
135
157
NotificationCenter . default. addObserver (
@@ -180,19 +202,71 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
180
202
}
181
203
182
204
public func scrollToLastMessage( ) {
183
- if scrolledId != nil {
205
+ if channelDataSource. hasLoadedAllNextMessages {
206
+ updateScrolledIdToNewestMessage ( )
207
+ } else {
208
+ channelDataSource. loadFirstPage { [ weak self] _ in
209
+ self ? . scrolledId = self ? . messages. first? . messageId
210
+ }
211
+ }
212
+ }
213
+
214
+ public func jumpToMessage( messageId: String ) -> Bool {
215
+ if messageId == messages. first? . messageId {
184
216
scrolledId = nil
217
+ return true
218
+ } else {
219
+ guard let baseId = messageId. components ( separatedBy: " $ " ) . first else {
220
+ scrolledId = nil
221
+ return true
222
+ }
223
+ let alreadyLoaded = messages. map ( \. id) . contains ( baseId)
224
+ if alreadyLoaded {
225
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2.0 ) {
226
+ self . scrolledId = nil
227
+ }
228
+ return true
229
+ } else {
230
+ let message = channelController. dataStore. message ( id: baseId)
231
+ if let parentMessageId = message? . parentMessageId, !isMessageThread {
232
+ let parentMessage = channelController. dataStore. message ( id: parentMessageId)
233
+ threadMessage = parentMessage
234
+ threadMessageShown = true
235
+ messageCachingUtils. jumpToReplyId = message? . messageId
236
+ return false
237
+ }
238
+
239
+ scrolledId = nil
240
+ if loadingMessagesAround {
241
+ return false
242
+ }
243
+ loadingMessagesAround = true
244
+ channelDataSource. loadPageAroundMessageId ( baseId) { [ weak self] error in
245
+ if error != nil {
246
+ log. error ( " Error loading messages around message \( messageId) " )
247
+ return
248
+ }
249
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
250
+ self ? . scrolledId = messageId
251
+ self ? . loadingMessagesAround = false
252
+ }
253
+ }
254
+ return false
255
+ }
185
256
}
186
- scrolledId = messages. first? . messageId
187
257
}
188
258
189
- open func handleMessageAppear( index: Int ) {
190
- if index >= messages. count {
259
+ open func handleMessageAppear( index: Int , scrollDirection : ScrollDirection ) {
260
+ if index >= channelDataSource . messages. count || loadingMessagesAround {
191
261
return
192
262
}
193
263
194
264
let message = messages [ index]
195
- checkForNewMessages ( index: index)
265
+ if scrollDirection == . up {
266
+ checkForOlderMessages ( index: index)
267
+ } else {
268
+ checkForNewerMessages ( index: index)
269
+ }
196
270
if utils. messageListConfig. dateIndicatorPlacement == . overlay {
197
271
save ( lastDate: message. createdAt)
198
272
}
@@ -278,8 +352,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
278
352
279
353
maybeRefreshMessageList ( )
280
354
281
- if !showScrollToLatestButton && scrolledId == nil {
282
- scrollToLastMessage ( )
355
+ if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
356
+ updateScrolledIdToNewestMessage ( )
283
357
}
284
358
}
285
359
@@ -321,11 +395,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
321
395
322
396
// MARK: - private
323
397
324
- private func checkForNewMessages ( index: Int ) {
398
+ private func checkForOlderMessages ( index: Int ) {
325
399
if index < channelDataSource. messages. count - 25 {
326
400
return
327
401
}
328
402
403
+ log. debug ( " Loading previous messages " )
329
404
if !loadingPreviousMessages {
330
405
loadingPreviousMessages = true
331
406
channelDataSource. loadPreviousMessages (
@@ -338,6 +413,27 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
338
413
)
339
414
}
340
415
}
416
+
417
+ private func checkForNewerMessages( index: Int ) {
418
+ if channelDataSource. hasLoadedAllNextMessages {
419
+ return
420
+ }
421
+ if loadingNextMessages || ( index > 5 ) {
422
+ return
423
+ }
424
+ loadingNextMessages = true
425
+
426
+ if scrollPosition != messages. first? . messageId {
427
+ scrollPosition = messages [ index] . messageId
428
+ }
429
+
430
+ channelDataSource. loadNextMessages ( limit: newerMessagesLimit) { [ weak self] _ in
431
+ guard let self = self else { return }
432
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
433
+ self . loadingNextMessages = false
434
+ }
435
+ }
436
+ }
341
437
342
438
private func save( lastDate: Date ) {
343
439
if disableDateIndicator {
@@ -469,7 +565,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
469
565
}
470
566
471
567
private func shouldAnimate( changes: [ ListChange < ChatMessage > ] ) -> AnimationChange {
472
- if !utils. messageListConfig. messageDisplayOptions. animateChanges {
568
+ if !utils. messageListConfig. messageDisplayOptions. animateChanges || loadingNextMessages {
473
569
return . notAnimated
474
570
}
475
571
@@ -522,6 +618,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
522
618
}
523
619
}
524
620
621
+ private func updateScrolledIdToNewestMessage( ) {
622
+ if scrolledId != nil {
623
+ scrolledId = nil
624
+ }
625
+ scrolledId = messages. first? . messageId
626
+ }
627
+
525
628
deinit {
526
629
messageCachingUtils. clearCache ( )
527
630
if messageController == nil {
@@ -542,7 +645,7 @@ extension ChatMessage: Identifiable {
542
645
}
543
646
544
647
var baseId : String {
545
- isDeleted ? " \( id) - deleted" : id
648
+ isDeleted ? " \( id) $ deleted" : " \( id ) $ "
546
649
}
547
650
548
651
var pinStateId : String {
0 commit comments