@@ -66,19 +66,16 @@ struct ChatPanelMessages: View {
6666 }
6767
6868 Spacer ( minLength: 12 )
69- . onAppear {
70- withAnimation {
71- proxy. scrollTo ( bottomID, anchor: . bottom)
72- }
73- }
7469 . id ( bottomID)
70+ . task {
71+ proxy. scrollTo ( bottomID, anchor: . bottom)
72+ }
7573 . background ( GeometryReader { geo in
7674 let offset = geo. frame ( in: . named( scrollSpace) ) . minY
77- Color . clear
78- . preference (
79- key: ScrollViewOffsetPreferenceKey . self,
80- value: offset
81- )
75+ Color . clear. preference (
76+ key: ScrollViewOffsetPreferenceKey . self,
77+ value: offset
78+ )
8279 } )
8380 . preference (
8481 key: ListHeightPreferenceKey . self,
@@ -96,12 +93,16 @@ struct ChatPanelMessages: View {
9693 . listStyle ( . plain)
9794 . coordinateSpace ( name: scrollSpace)
9895 . onPreferenceChange ( ListHeightPreferenceKey . self) { value in
99- listHeight = value
100- updatePinningState ( )
96+ Task { @MainActor in
97+ listHeight = value
98+ updatePinningState ( )
99+ }
101100 }
102101 . onPreferenceChange ( ScrollViewOffsetPreferenceKey . self) { value in
103- scrollOffset = value
104- updatePinningState ( )
102+ Task { @MainActor in
103+ scrollOffset = value
104+ updatePinningState ( )
105+ }
105106 }
106107 . overlay ( alignment: . bottom) {
107108 WithViewStore ( chat, observe: \. isReceivingMessage) { viewStore in
@@ -114,51 +115,54 @@ struct ChatPanelMessages: View {
114115 }
115116 }
116117 . overlay ( alignment: . bottomTrailing) {
117- WithViewStore ( chat, observe: \. history. last) { viewStore in
118- Button ( action: {
119- withAnimation ( . easeInOut( duration: 0.1 ) ) {
120- proxy. scrollTo ( bottomID, anchor: . bottom)
121- }
122- } ) {
123- Image ( systemName: " arrow.down " )
124- . padding ( 4 )
125- . background {
126- Circle ( )
127- . fill ( . thickMaterial)
128- . shadow ( color: . black. opacity ( 0.2 ) , radius: 2 )
129- }
130- . overlay {
131- Circle ( ) . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
132- }
133- . foregroundStyle ( . secondary)
134- . padding ( 4 )
135- }
136- . keyboardShortcut ( . downArrow, modifiers: [ . command] )
137- . opacity ( pinnedToBottom ? 0 : 1 )
138- . buttonStyle ( . plain)
139- . onChange ( of: viewStore. state) { _ in
140- if pinnedToBottom || isInitialLoad {
141- if isInitialLoad {
142- isInitialLoad = false
143- }
144- withAnimation {
145- proxy. scrollTo ( bottomID, anchor: . bottom)
146- }
147- }
148- }
149- }
118+ scrollToBottomButton ( proxy: proxy)
150119 }
151120 }
152121 }
153122 }
154123
155124 func updatePinningState( ) {
156- if scrollOffset > listHeight + 24 + 100 || scrollOffset <= 0 {
125+ if scrollOffset > listHeight + 30 + 100 || scrollOffset <= 0 {
157126 pinnedToBottom = false
158127 } else {
159128 pinnedToBottom = true
160129 }
161130 }
131+
132+ @ViewBuilder
133+ func scrollToBottomButton( proxy: ScrollViewProxy ) -> some View {
134+ WithViewStore ( chat, observe: \. history. last) { viewStore in
135+ Button ( action: {
136+ withAnimation ( . easeInOut( duration: 0.1 ) ) {
137+ proxy. scrollTo ( bottomID, anchor: . bottom)
138+ }
139+ } ) {
140+ Image ( systemName: " arrow.down " )
141+ . padding ( 4 )
142+ . background {
143+ Circle ( )
144+ . fill ( . thickMaterial)
145+ . shadow ( color: . black. opacity ( 0.2 ) , radius: 2 )
146+ }
147+ . overlay {
148+ Circle ( ) . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
149+ }
150+ . foregroundStyle ( . secondary)
151+ . padding ( 4 )
152+ }
153+ . keyboardShortcut ( . downArrow, modifiers: [ . command] )
154+ . opacity ( pinnedToBottom ? 0 : 1 )
155+ . buttonStyle ( . plain)
156+ . onChange ( of: viewStore. state) { _ in
157+ if pinnedToBottom || isInitialLoad {
158+ if isInitialLoad {
159+ isInitialLoad = false
160+ }
161+ proxy. scrollTo ( bottomID, anchor: . bottom)
162+ }
163+ }
164+ }
165+ }
162166}
163167
164168struct ChatHistory : View {
0 commit comments