- ContentView 新增 TabBarVisibleKey 环境值 + AnimatedTabBarHide modifier - TabView 使用 .toolbar(visible/hidden) + .animation 驱动动画 - 子页面 onAppear 隐藏 → onDisappear 出现,带 easeInOut 0.28s - 替换所有静态 .toolbar(.hidden, for: .tabBar) 为 .animatedTabBarHide() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67 lines
2.9 KiB
Swift
67 lines
2.9 KiB
Swift
import SwiftUI
|
|
|
|
struct AIChatPage: View {
|
|
@StateObject private var vm = AIChatViewModel()
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Color.zxBg0.ignoresSafeArea()
|
|
VStack(spacing: 0) {
|
|
ScrollViewReader { proxy in
|
|
ScrollView {
|
|
VStack(spacing: 16) {
|
|
ForEach(vm.messages) { m in
|
|
chatBubble(m)
|
|
.id(m.id)
|
|
}
|
|
if vm.isSending {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "brain.head.profile").foregroundColor(Color.zxPurple)
|
|
.frame(width: 28, height: 28)
|
|
.background(Color(hex: "#7C6EFA", opacity: 0.15))
|
|
.clipShape(Circle())
|
|
ZXDotLoader(color: Color.zxPurple)
|
|
.padding(.leading, 4)
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 20)
|
|
}
|
|
}
|
|
.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
|
|
}
|
|
.scrollIndicators(.hidden)
|
|
.onChange(of: vm.messages.count) { _ in
|
|
withAnimation { proxy.scrollTo(vm.messages.last?.id) }
|
|
}
|
|
}
|
|
ZXAIInputBar(text: $vm.inputText, onSend: { vm.send() })
|
|
.padding(.horizontal, 20).padding(.bottom, 34)
|
|
}
|
|
}
|
|
.navigationBarTitleDisplayMode(.inline).animatedTabBarHide()
|
|
.toolbarBackground(.hidden, for: .navigationBar)
|
|
}
|
|
|
|
private func chatBubble(_ m: AIMessage) -> some View {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
if m.role == .ai {
|
|
Image(systemName: "brain.head.profile").foregroundColor(Color.zxPurple)
|
|
.frame(width: 28, height: 28)
|
|
.background(Color(hex: "#7C6EFA", opacity: 0.15))
|
|
.clipShape(Circle())
|
|
}
|
|
Text(m.content).zxFontScaled(size: 14)
|
|
.foregroundColor(m.role == .user ? .white : Color.zxF007)
|
|
.padding(12)
|
|
.background(m.role == .user ? AnyView(ZXGradient.brandPurple) : AnyView(Color.zxFill004))
|
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
|
if m.role == .user {
|
|
Circle().frame(width: 28, height: 28)
|
|
.foregroundColor(Color.zxPurpleBG(0.2))
|
|
.overlay(Text("我").font(.system(size: 10, weight: .bold)).foregroundColor(Color.zxPurple))
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: m.role == .user ? .trailing : .leading)
|
|
}
|
|
}
|