ios: invert scroll view technique
This commit is contained in:
@@ -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,66 +15,42 @@ 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 isSending && !hasPendingAssistant {
|
||||||
if isLoading && messages.isEmpty {
|
HStack(spacing: 8) {
|
||||||
Text("Loading messages…")
|
ProgressView()
|
||||||
|
.controlSize(.small)
|
||||||
|
.tint(SybilTheme.textMuted)
|
||||||
|
Text("Assistant is typing…")
|
||||||
.font(.sybil(.footnote))
|
.font(.sybil(.footnote))
|
||||||
.foregroundStyle(SybilTheme.textMuted)
|
.foregroundStyle(SybilTheme.textMuted)
|
||||||
.padding(.top, 24)
|
|
||||||
}
|
}
|
||||||
|
.scaleEffect(x: 1, y: -1)
|
||||||
ForEach(messages) { message in
|
}
|
||||||
MessageBubble(message: message, isSending: isSending)
|
|
||||||
.frame(maxWidth: .infinity)
|
ForEach(messages.reversed()) { message in
|
||||||
.id(message.id)
|
MessageBubble(message: message, isSending: isSending)
|
||||||
}
|
.frame(maxWidth: .infinity)
|
||||||
|
.scaleEffect(x: 1, y: -1)
|
||||||
if isSending && !hasPendingAssistant {
|
}
|
||||||
HStack(spacing: 8) {
|
|
||||||
ProgressView()
|
if isLoading && messages.isEmpty {
|
||||||
.controlSize(.small)
|
Text("Loading messages…")
|
||||||
.tint(SybilTheme.textMuted)
|
.font(.sybil(.footnote))
|
||||||
Text("Assistant is typing…")
|
.foregroundStyle(SybilTheme.textMuted)
|
||||||
.font(.sybil(.footnote))
|
.padding(.top, 24)
|
||||||
.foregroundStyle(SybilTheme.textMuted)
|
.scaleEffect(x: 1, y: -1)
|
||||||
}
|
|
||||||
.id("typing-indicator")
|
|
||||||
}
|
|
||||||
|
|
||||||
Color.clear
|
|
||||||
.frame(height: 2)
|
|
||||||
.id("chat-bottom-anchor")
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding(.horizontal, 14)
|
|
||||||
.padding(.top, 18 + topContentInset)
|
|
||||||
.padding(.bottom, 18 + bottomContentInset)
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.padding(.horizontal, 14)
|
||||||
.onAppear {
|
.padding(.top, 18 + bottomContentInset)
|
||||||
scrollToBottom(with: proxy, animated: false)
|
.padding(.bottom, 18 + topContentInset)
|
||||||
}
|
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.scrollDismissesKeyboard(.interactively)
|
||||||
|
.scaleEffect(x: 1, y: -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user