@@ -17,6 +17,15 @@ 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 = {
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+
2029 private var timer : Timer ?
2130 private var currentDate : Date ? {
2231 didSet {
@@ -33,6 +42,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
3342 private lazy var messageCachingUtils = utils. messageCachingUtils
3443
3544 private var loadingPreviousMessages : Bool = false
45+ private var loadingMessagesAround : Bool = false
3646 private var lastMessageRead : String ?
3747 private var disableDateIndicator = false
3848 private var channelName = " "
@@ -96,6 +106,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
96106 }
97107
98108 @Published public var shouldShowTypingIndicator = false
109+ @Published public var scrollPosition : String ?
110+ @Published public private( set) var loadingNextMessages : Bool = false
99111
100112 public var channel : ChatChannel ? {
101113 channelController. channel
@@ -129,7 +141,17 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
129141 messages = channelDataSource. messages
130142
131143 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+ }
133155 }
134156
135157 NotificationCenter . default. addObserver (
@@ -180,19 +202,71 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
180202 }
181203
182204 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 {
184216 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+ }
185256 }
186- scrolledId = messages. first? . messageId
187257 }
188258
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 {
191261 return
192262 }
193263
194264 let message = messages [ index]
195- checkForNewMessages ( index: index)
265+ if scrollDirection == . up {
266+ checkForOlderMessages ( index: index)
267+ } else {
268+ checkForNewerMessages ( index: index)
269+ }
196270 if utils. messageListConfig. dateIndicatorPlacement == . overlay {
197271 save ( lastDate: message. createdAt)
198272 }
@@ -278,8 +352,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
278352
279353 maybeRefreshMessageList ( )
280354
281- if !showScrollToLatestButton && scrolledId == nil {
282- scrollToLastMessage ( )
355+ if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
356+ updateScrolledIdToNewestMessage ( )
283357 }
284358 }
285359
@@ -321,11 +395,12 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
321395
322396 // MARK: - private
323397
324- private func checkForNewMessages ( index: Int ) {
398+ private func checkForOlderMessages ( index: Int ) {
325399 if index < channelDataSource. messages. count - 25 {
326400 return
327401 }
328402
403+ log. debug ( " Loading previous messages " )
329404 if !loadingPreviousMessages {
330405 loadingPreviousMessages = true
331406 channelDataSource. loadPreviousMessages (
@@ -338,6 +413,27 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
338413 )
339414 }
340415 }
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+ }
341437
342438 private func save( lastDate: Date ) {
343439 if disableDateIndicator {
@@ -469,7 +565,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
469565 }
470566
471567 private func shouldAnimate( changes: [ ListChange < ChatMessage > ] ) -> AnimationChange {
472- if !utils. messageListConfig. messageDisplayOptions. animateChanges {
568+ if !utils. messageListConfig. messageDisplayOptions. animateChanges || loadingNextMessages {
473569 return . notAnimated
474570 }
475571
@@ -522,6 +618,13 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
522618 }
523619 }
524620
621+ private func updateScrolledIdToNewestMessage( ) {
622+ if scrolledId != nil {
623+ scrolledId = nil
624+ }
625+ scrolledId = messages. first? . messageId
626+ }
627+
525628 deinit {
526629 messageCachingUtils. clearCache ( )
527630 if messageController == nil {
@@ -542,7 +645,7 @@ extension ChatMessage: Identifiable {
542645 }
543646
544647 var baseId : String {
545- isDeleted ? " \( id) - deleted" : id
648+ isDeleted ? " \( id) $ deleted" : " \( id ) $ "
546649 }
547650
548651 var pinStateId : String {
0 commit comments