ios: invert scroll view technique

This commit is contained in:
2026-05-03 21:21:03 -07:00
parent aff2531bf3
commit 0b94d5b3fa

View File

@@ -7,7 +7,6 @@ struct SybilChatTranscriptView: View {
var isSending: Bool var isSending: Bool
var topContentInset: CGFloat = 0 var topContentInset: CGFloat = 0
var bottomContentInset: CGFloat = 0 var bottomContentInset: CGFloat = 0
@State private var hasHandledInitialTranscriptScroll = false
private var hasPendingAssistant: Bool { private var hasPendingAssistant: Bool {
messages.contains { message in messages.contains { message in
@@ -16,22 +15,8 @@ struct SybilChatTranscriptView: View {
} }
var body: some View { var body: some View {
ScrollViewReader { proxy in
ScrollView { ScrollView {
LazyVStack(alignment: .leading, spacing: 26) { LazyVStack(alignment: .leading, spacing: 26) {
if isLoading && messages.isEmpty {
Text("Loading messages…")
.font(.sybil(.footnote))
.foregroundStyle(SybilTheme.textMuted)
.padding(.top, 24)
}
ForEach(messages) { message in
MessageBubble(message: message, isSending: isSending)
.frame(maxWidth: .infinity)
.id(message.id)
}
if isSending && !hasPendingAssistant { if isSending && !hasPendingAssistant {
HStack(spacing: 8) { HStack(spacing: 8) {
ProgressView() ProgressView()
@@ -41,41 +26,31 @@ struct SybilChatTranscriptView: View {
.font(.sybil(.footnote)) .font(.sybil(.footnote))
.foregroundStyle(SybilTheme.textMuted) .foregroundStyle(SybilTheme.textMuted)
} }
.id("typing-indicator") .scaleEffect(x: 1, y: -1)
} }
Color.clear ForEach(messages.reversed()) { message in
.frame(height: 2) MessageBubble(message: message, isSending: isSending)
.id("chat-bottom-anchor") .frame(maxWidth: .infinity)
.scaleEffect(x: 1, y: -1)
}
if isLoading && messages.isEmpty {
Text("Loading messages…")
.font(.sybil(.footnote))
.foregroundStyle(SybilTheme.textMuted)
.padding(.top, 24)
.scaleEffect(x: 1, y: -1)
}
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.top, 18 + topContentInset) .padding(.top, 18 + bottomContentInset)
.padding(.bottom, 18 + bottomContentInset) .padding(.bottom, 18 + topContentInset)
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)
.onAppear { .scaleEffect(x: 1, y: -1)
scrollToBottom(with: proxy, animated: false)
}
.onChange(of: messages.map(\.id)) { _, _ in
scrollToBottom(with: proxy, animated: hasHandledInitialTranscriptScroll && !isLoading)
hasHandledInitialTranscriptScroll = true
}
.onChange(of: isSending) { _, _ in
scrollToBottom(with: proxy, animated: hasHandledInitialTranscriptScroll)
}
}
}
private func scrollToBottom(with proxy: ScrollViewProxy, animated: Bool) {
if animated {
withAnimation(.easeOut(duration: 0.22)) {
proxy.scrollTo("chat-bottom-anchor", anchor: .bottom)
}
} else {
proxy.scrollTo("chat-bottom-anchor", anchor: .bottom)
}
} }
} }