perf: reduce chat animation churn

This commit is contained in:
Peter Steinberger
2025-12-24 13:51:00 +01:00
parent 0139a77e94
commit e158bee95f
2 changed files with 33 additions and 3 deletions

View File

@@ -323,6 +323,13 @@ struct ChatTypingIndicatorBubble: View {
RoundedRectangle(cornerRadius: 16, style: .continuous) RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)) .strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
.frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading) .frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading)
.focusable(false)
}
}
extension ChatTypingIndicatorBubble: @MainActor Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.style == rhs.style
} }
} }
@@ -342,6 +349,7 @@ struct ChatStreamingAssistantBubble: View {
RoundedRectangle(cornerRadius: 16, style: .continuous) RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)) .strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
.frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading) .frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading)
.focusable(false)
} }
} }
@@ -376,12 +384,20 @@ struct ChatPendingToolsBubble: View {
RoundedRectangle(cornerRadius: 16, style: .continuous) RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)) .strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
.frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading) .frame(maxWidth: ChatUIConstants.bubbleMaxWidth, alignment: .leading)
.focusable(false)
}
}
extension ChatPendingToolsBubble: @MainActor Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.toolCalls == rhs.toolCalls
} }
} }
@MainActor @MainActor
private struct TypingDots: View { private struct TypingDots: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion @Environment(\.accessibilityReduceMotion) private var reduceMotion
@Environment(\.scenePhase) private var scenePhase
@State private var animate = false @State private var animate = false
var body: some View { var body: some View {
@@ -399,10 +415,22 @@ private struct TypingDots: View {
value: self.animate) value: self.animate)
} }
} }
.onAppear { .onAppear { self.updateAnimationState() }
guard !self.reduceMotion else { return } .onDisappear { self.animate = false }
self.animate = true .onChange(of: self.scenePhase) { _, _ in
self.updateAnimationState()
} }
.onChange(of: self.reduceMotion) { _, _ in
self.updateAnimationState()
}
}
private func updateAnimationState() {
guard !self.reduceMotion, self.scenePhase == .active else {
self.animate = false
return
}
self.animate = true
} }
} }

View File

@@ -80,11 +80,13 @@ public struct ClawdisChatView: View {
if self.viewModel.pendingRunCount > 0 { if self.viewModel.pendingRunCount > 0 {
ChatTypingIndicatorBubble(style: self.style) ChatTypingIndicatorBubble(style: self.style)
.equatable()
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
if !self.viewModel.pendingToolCalls.isEmpty { if !self.viewModel.pendingToolCalls.isEmpty {
ChatPendingToolsBubble(toolCalls: self.viewModel.pendingToolCalls) ChatPendingToolsBubble(toolCalls: self.viewModel.pendingToolCalls)
.equatable()
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }