Skip to content

Commit a3b15ed

Browse files
committed
Fix scroll animation on new message and more
1 parent 26c0d34 commit a3b15ed

File tree

2 files changed

+82
-48
lines changed

2 files changed

+82
-48
lines changed

apple/InlineMac/Views/Chat/AppKit/MessageSizeCalculator.swift

+17
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class MessageSizeCalculator {
7777
if props.firstInGroup {
7878
totalHeight += Theme.messageNameLabelHeight
7979
totalHeight += Theme.messageVerticalStackSpacing
80+
totalHeight += Theme.messageGroupSpacing
8081
}
8182
if props.isLastMessage == true {
8283
totalHeight += Theme.messageListBottomInset
@@ -103,6 +104,22 @@ class MessageSizeCalculator {
103104

104105
static func getTextViewWidth(for tableWidth: CGFloat) -> CGFloat {
105106
tableWidth - Theme.messageAvatarSize - Theme.messageHorizontalStackSpacing
107+
}
108+
static func getTextViewHeight(for props: MessageViewProps) -> CGFloat {
109+
var height = props.height ?? 60.0
110+
if props.firstInGroup {
111+
height -= Theme.messageNameLabelHeight
112+
height -= Theme.messageVerticalStackSpacing
113+
height -= Theme.messageGroupSpacing
114+
}
115+
if props.isLastMessage == true {
116+
height -= Theme.messageListBottomInset
117+
}
118+
if props.isFirstMessage == true {
119+
height -= Theme.messageListTopInset
120+
}
121+
height -= Theme.messageVerticalPadding * 2
122+
return height
106123
}
107124

108125
private func heightForSingleLineText() -> CGFloat {

apple/InlineMac/Views/Chat/AppKit/MessageTableView.swift

+65-48
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ class MessagesTableView: NSViewController {
5757
let scroll = NSScrollView()
5858
scroll.hasVerticalScroller = true
5959
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
6263
// Used for quick scroll on resize
6364
scroll.translatesAutoresizingMaskIntoConstraints = false
6465
scroll.documentView = tableView
@@ -118,9 +119,7 @@ class MessagesTableView: NSViewController {
118119
)
119120

120121
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
124123
window.titlebarAppearsTransparent = false
125124
window.isMovableByWindowBackground = true
126125
}
@@ -148,6 +147,7 @@ class MessagesTableView: NSViewController {
148147

149148
private func scrollToBottom(animated: Bool) {
150149
guard messages.count > 0 else { return }
150+
log.trace("Scrolling to bottom animated=\(animated)")
151151

152152
let lastRow = messages.count - 1
153153
isProgrammaticScroll = true
@@ -182,6 +182,7 @@ class MessagesTableView: NSViewController {
182182
contentSizeObserver = scrollView.documentView?.observe(\.frame) { [weak self] view, _ in
183183
guard let self = self else { return }
184184
let newHeight = view.frame.height
185+
log.trace("scrollView document frame change")
185186
updateAvatars()
186187
handleContentSizeChange(newHeight)
187188
}
@@ -229,11 +230,15 @@ class MessagesTableView: NSViewController {
229230
private func handleContentSizeChange(_ newHeight: CGFloat) {
230231
lastContentHeight = newHeight
231232

233+
log.trace("scrollView content size change")
234+
232235
if isAtBottom && (!isPerformingUpdate || needsInitialScroll) {
233236
scrollToBottom(animated: false)
234237
}
235238
}
236239

240+
private var prevOffset: CGFloat = 0
241+
237242
@objc func scrollViewBoundsChanged(notification: Notification) {
238243
log.trace("scroll view bounds changed")
239244

@@ -265,9 +270,14 @@ class MessagesTableView: NSViewController {
265270
}
266271
#endif
267272

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
271281
}
272282

273283
// Using CFAbsoluteTimeGetCurrent()
@@ -303,9 +313,11 @@ class MessagesTableView: NSViewController {
303313

304314
// utils
305315
func avatarPadding(at row: Int) -> CGFloat {
306-
return row == 0 ?
316+
let padding = row == 0 ?
307317
Theme.messageListTopInset + Theme.messageVerticalPadding :
308318
Theme.messageVerticalPadding
319+
320+
return padding + Theme.messageGroupSpacing
309321
}
310322

311323
let availableViewportHeight = scrollView.contentView.bounds.height - scrollTopInset
@@ -540,49 +552,54 @@ class MessagesTableView: NSViewController {
540552
}
541553
//
542554
// Batch all visual updates
543-
NSAnimationContext.runAnimationGroup { _ in
544-
tableView.beginUpdates()
555+
// NSAnimationContext.runAnimationGroup { context in
556+
tableView.beginUpdates()
545557

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+
}
549561

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()
558570

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+
}
565577

578+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
566579
// Handle scroll position
567580
if (!removals.isEmpty || !insertions.isEmpty) && wasAtBottom {
568581
// 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)
584584
}
585585
}
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+
// }
586603
}
587604

588605
private func getVisibleRowIndexes() -> IndexSet {
@@ -634,19 +651,19 @@ class MessagesTableView: NSViewController {
634651
}
635652
}
636653

654+
// Note this function will stop any animation that is happening so must be used with caution
637655
private func recalculateHeightsOnWidthChange() {
656+
log.trace("Recalculating heights on width change")
638657
let visibleRect = tableView.visibleRect
639658
let visibleRange = tableView.rows(in: visibleRect)
640659

641660
guard visibleRange.location != NSNotFound else { return }
642661

643-
let bufferCount = 0
644-
645662
// Calculate ranges
646-
let visibleStartIndex = max(0, visibleRange.location - bufferCount)
663+
let visibleStartIndex = max(0, visibleRange.location)
647664
let visibleEndIndex = min(
648665
tableView.numberOfRows,
649-
visibleRange.location + visibleRange.length + bufferCount
666+
visibleRange.location + visibleRange.length
650667
)
651668

652669
// First, immediately update visible rows
@@ -696,7 +713,7 @@ extension MessagesTableView: NSTableViewDataSource {
696713
extension MessagesTableView: NSTableViewDelegate {
697714
func isFirstInGroup(at row: Int) -> Bool {
698715
guard row >= 0, row < messages.count else { return true }
699-
716+
700717
let prevMessage = row > 0 ? messages[row - 1] : nil
701718
guard prevMessage != nil else {
702719
return true

0 commit comments

Comments
 (0)