@@ -57,8 +57,9 @@ class MessagesTableView: NSViewController {
57
57
let scroll = NSScrollView ( )
58
58
scroll. hasVerticalScroller = true
59
59
scroll. borderType = . noBorder
60
- scroll. drawsBackground = false // Add this line
61
- scroll. backgroundColor = . clear
60
+ scroll. drawsBackground = true // Add this line
61
+ scroll. backgroundColor = . textBackgroundColor
62
+ // scroll.backgroundColor = .windowBackgroundColor
62
63
// Used for quick scroll on resize
63
64
scroll. translatesAutoresizingMaskIntoConstraints = false
64
65
scroll. documentView = tableView
@@ -118,9 +119,7 @@ class MessagesTableView: NSViewController {
118
119
)
119
120
120
121
log. debug ( " Adjusting view's toolbar " )
121
- // Updated
122
- // TODO: make window toolbar layout and have background to fight the swiftui defaUlt behaviour
123
- // Configure window toolbar
122
+ // make window toolbar layout and have background to fight the swiftui defaUlt behaviour
124
123
window. titlebarAppearsTransparent = false
125
124
window. isMovableByWindowBackground = true
126
125
}
@@ -148,6 +147,7 @@ class MessagesTableView: NSViewController {
148
147
149
148
private func scrollToBottom( animated: Bool ) {
150
149
guard messages. count > 0 else { return }
150
+ log. trace ( " Scrolling to bottom animated= \( animated) " )
151
151
152
152
let lastRow = messages. count - 1
153
153
isProgrammaticScroll = true
@@ -182,6 +182,7 @@ class MessagesTableView: NSViewController {
182
182
contentSizeObserver = scrollView. documentView? . observe ( \. frame) { [ weak self] view, _ in
183
183
guard let self = self else { return }
184
184
let newHeight = view. frame. height
185
+ log. trace ( " scrollView document frame change " )
185
186
updateAvatars ( )
186
187
handleContentSizeChange ( newHeight)
187
188
}
@@ -229,11 +230,15 @@ class MessagesTableView: NSViewController {
229
230
private func handleContentSizeChange( _ newHeight: CGFloat ) {
230
231
lastContentHeight = newHeight
231
232
233
+ log. trace ( " scrollView content size change " )
234
+
232
235
if isAtBottom && ( !isPerformingUpdate || needsInitialScroll) {
233
236
scrollToBottom ( animated: false )
234
237
}
235
238
}
236
239
240
+ private var prevOffset : CGFloat = 0
241
+
237
242
@objc func scrollViewBoundsChanged( notification: Notification ) {
238
243
log. trace ( " scroll view bounds changed " )
239
244
@@ -265,9 +270,14 @@ class MessagesTableView: NSViewController {
265
270
}
266
271
#endif
267
272
268
- // Update heights that might have been changed if a width change happened in a different position
269
- // because we just update visible portion of the table
270
- recalculateHeightsOnWidthChange ( )
273
+ // Only update width of rows if scrolling up otherwise this messes up scroll animation on new item
274
+ if prevOffset != currentScrollOffset && !isProgrammaticScroll && !isAtBottom && prevOffset > currentScrollOffset {
275
+ // Update heights that might have been changed if a width change happened in a different position
276
+ // because we just update visible portion of the table
277
+ recalculateHeightsOnWidthChange ( )
278
+ }
279
+
280
+ prevOffset = currentScrollOffset
271
281
}
272
282
273
283
// Using CFAbsoluteTimeGetCurrent()
@@ -303,9 +313,11 @@ class MessagesTableView: NSViewController {
303
313
304
314
// utils
305
315
func avatarPadding( at row: Int ) -> CGFloat {
306
- return row == 0 ?
316
+ let padding = row == 0 ?
307
317
Theme . messageListTopInset + Theme. messageVerticalPadding :
308
318
Theme . messageVerticalPadding
319
+
320
+ return padding + Theme. messageGroupSpacing
309
321
}
310
322
311
323
let availableViewportHeight = scrollView. contentView. bounds. height - scrollTopInset
@@ -540,49 +552,54 @@ class MessagesTableView: NSViewController {
540
552
}
541
553
//
542
554
// Batch all visual updates
543
- NSAnimationContext . runAnimationGroup { _ in
544
- tableView. beginUpdates ( )
555
+ // NSAnimationContext.runAnimationGroup { context in
556
+ tableView. beginUpdates ( )
545
557
546
- if !removals. isEmpty {
547
- tableView. removeRows ( at: IndexSet ( removals) , withAnimation: . effectFade)
548
- }
558
+ if !removals. isEmpty {
559
+ tableView. removeRows ( at: IndexSet ( removals) , withAnimation: . effectFade)
560
+ }
549
561
550
- if !insertions. isEmpty {
551
- tableView
552
- . insertRows (
553
- at: IndexSet ( insertions) ,
554
- withAnimation: . effectFade
555
- )
556
- }
557
- tableView. endUpdates ( )
562
+ if !insertions. isEmpty {
563
+ tableView
564
+ . insertRows (
565
+ at: IndexSet ( insertions) ,
566
+ withAnimation: . effectFade
567
+ )
568
+ }
569
+ tableView. endUpdates ( )
558
570
559
- if oldMessages. last != messages. last {
560
- // TODO: See if we can optimize here
561
- // last message changed height
562
- tableView. reloadData ( forRowIndexes: IndexSet ( [ messages. count - 2 , messages. count - 1 ] ) , columnIndexes: IndexSet ( [ 0 ] ) )
563
- recalculateVisibleHeightsWithCache ( )
564
- }
571
+ if oldMessages. last != messages. last {
572
+ // TODO: See if we can optimize here
573
+ // last message changed height
574
+ tableView. reloadData ( forRowIndexes: IndexSet ( [ messages. count - 2 , messages. count - 1 ] ) , columnIndexes: IndexSet ( [ 0 ] ) )
575
+ recalculateVisibleHeightsWithCache ( )
576
+ }
565
577
578
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.01 ) {
566
579
// Handle scroll position
567
580
if ( !removals. isEmpty || !insertions. isEmpty) && wasAtBottom {
568
581
// Only animate if it's not the initial load
569
- self . scrollToBottom ( animated: !isInitialUpdate)
570
- }
571
-
572
- } completionHandler: { [ weak self] in
573
- guard let self = self else { return }
574
-
575
- isPerformingUpdate = false
576
-
577
- // Verify the update
578
- let actualRows = self . tableView. numberOfRows
579
- let expectedRows = self . messages. count
580
-
581
- if actualRows != expectedRows {
582
- Log . shared. debug ( " ⚠️ Row count mismatch - forcing reload " )
583
- self . tableView. reloadData ( )
582
+ // self.scrollToBottom(animated: !isInitialUpdate)
583
+ self . scrollToBottom ( animated: true )
584
584
}
585
585
}
586
+
587
+ isPerformingUpdate = false
588
+
589
+ // } completionHandler: { [weak self] in
590
+ // guard let self = self else { return }
591
+ //
592
+ // isPerformingUpdate = false
593
+ //
594
+ // // Verify the update
595
+ // let actualRows = self.tableView.numberOfRows
596
+ // let expectedRows = self.messages.count
597
+ //
598
+ // if actualRows != expectedRows {
599
+ // Log.shared.debug("⚠️ Row count mismatch - forcing reload")
600
+ // self.tableView.reloadData()
601
+ // }
602
+ // }
586
603
}
587
604
588
605
private func getVisibleRowIndexes( ) -> IndexSet {
@@ -634,19 +651,19 @@ class MessagesTableView: NSViewController {
634
651
}
635
652
}
636
653
654
+ // Note this function will stop any animation that is happening so must be used with caution
637
655
private func recalculateHeightsOnWidthChange( ) {
656
+ log. trace ( " Recalculating heights on width change " )
638
657
let visibleRect = tableView. visibleRect
639
658
let visibleRange = tableView. rows ( in: visibleRect)
640
659
641
660
guard visibleRange. location != NSNotFound else { return }
642
661
643
- let bufferCount = 0
644
-
645
662
// Calculate ranges
646
- let visibleStartIndex = max ( 0 , visibleRange. location - bufferCount )
663
+ let visibleStartIndex = max ( 0 , visibleRange. location)
647
664
let visibleEndIndex = min (
648
665
tableView. numberOfRows,
649
- visibleRange. location + visibleRange. length + bufferCount
666
+ visibleRange. location + visibleRange. length
650
667
)
651
668
652
669
// First, immediately update visible rows
@@ -696,7 +713,7 @@ extension MessagesTableView: NSTableViewDataSource {
696
713
extension MessagesTableView : NSTableViewDelegate {
697
714
func isFirstInGroup( at row: Int ) -> Bool {
698
715
guard row >= 0 , row < messages. count else { return true }
699
-
716
+
700
717
let prevMessage = row > 0 ? messages [ row - 1 ] : nil
701
718
guard prevMessage != nil else {
702
719
return true
0 commit comments