替换 Xcode 默认模板: - 删除 ContentView/Item/__App - 新增 App/TijiApp(SwiftData ModelContainer)、RootView(3 Tab + RecordSheet) - DesignSystem:Tokens(色板/字体/圆角)+ Components(卡片/按钮/Chip) - Models:Indicator / Report / DiaryEntry @Model 初版 - Features:Home / Quick(A1-A3)/ Archive(B1-B5)/ Record / Trends / Me 静态 UI W2 AI 基座工作将在此基线上叠加。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
160 lines
5.3 KiB
Swift
160 lines
5.3 KiB
Swift
import SwiftUI
|
|
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
#endif
|
|
|
|
struct A1ViewfinderView: View {
|
|
var onShoot: () -> Void
|
|
var onClose: () -> Void
|
|
|
|
@State private var dotPulse = false
|
|
|
|
var body: some View {
|
|
GeometryReader { geometry in
|
|
ZStack {
|
|
Color(red: 0.04, green: 0.047, blue: 0.04).ignoresSafeArea()
|
|
|
|
mockCameraPreview(screenHeight: geometry.size.height)
|
|
|
|
VStack {
|
|
HStack {
|
|
Button(action: onClose) {
|
|
Image(systemName: "xmark")
|
|
.font(.system(size: 18, weight: .semibold))
|
|
.foregroundStyle(Color.white)
|
|
.frame(width: 36, height: 36)
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 6)
|
|
.padding(.top, 50)
|
|
|
|
topHint
|
|
|
|
Spacer()
|
|
}
|
|
|
|
SmartFramer()
|
|
.allowsHitTesting(false)
|
|
.ignoresSafeArea()
|
|
|
|
identifiedPill
|
|
.padding(.top, geometry.size.height * 0.62 - 20)
|
|
|
|
VStack {
|
|
Spacer()
|
|
bottomControls
|
|
}
|
|
}
|
|
}
|
|
#if os(iOS)
|
|
.statusBarHidden(false)
|
|
#endif
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
|
|
private func mockCameraPreview(screenHeight: CGFloat) -> some View {
|
|
RadialGradient(
|
|
colors: [Color.white.opacity(0.05), Color.clear],
|
|
center: .init(x: 0.5, y: 0.3),
|
|
startRadius: 20,
|
|
endRadius: 400
|
|
)
|
|
.overlay(alignment: .center) {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("总胆固醇 TC 5.42 mmol/L").opacity(0.65)
|
|
Text("甘油三酯 TG 1.78 mmol/L").opacity(0.65)
|
|
Text("低密度脂蛋白 3.84 mmol/L ↑").fontWeight(.semibold).opacity(1)
|
|
Text("高密度脂蛋白 1.21 mmol/L").opacity(0.65)
|
|
Text("载脂蛋白 A1 1.42 g/L").opacity(0.45)
|
|
Text("载脂蛋白 B 1.04 g/L").opacity(0.45)
|
|
}
|
|
.font(.system(size: 11, design: .monospaced))
|
|
.foregroundStyle(Tj.Palette.text)
|
|
.padding(.vertical, 20)
|
|
.padding(.horizontal, 18)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(Color(red: 0.96, green: 0.93, blue: 0.87).opacity(0.92))
|
|
.clipShape(RoundedRectangle(cornerRadius: 4, style: .continuous))
|
|
.rotationEffect(.degrees(-1.2))
|
|
.shadow(color: .black.opacity(0.45), radius: 15, x: 0, y: 12)
|
|
.padding(.horizontal, 24)
|
|
.padding(.vertical, screenHeight * 0.20)
|
|
}
|
|
}
|
|
|
|
private var topHint: some View {
|
|
Text("对准异常的那一行就好 · 不用拍整张")
|
|
.font(.system(size: 12))
|
|
.tracking(0.5)
|
|
.foregroundStyle(Color.white.opacity(0.92))
|
|
.padding(.horizontal, 14)
|
|
.padding(.vertical, 7)
|
|
.background(Capsule().fill(Color(red: 0.08, green: 0.11, blue: 0.094).opacity(0.7)))
|
|
.padding(.top, 6)
|
|
}
|
|
|
|
private var identifiedPill: some View {
|
|
HStack(spacing: 6) {
|
|
Circle()
|
|
.fill(Tj.Palette.paper)
|
|
.frame(width: 6, height: 6)
|
|
.opacity(dotPulse ? 1 : 0.35)
|
|
Text("AI 已识别到 1 项指标")
|
|
.font(.system(size: 11))
|
|
.tracking(0.5)
|
|
}
|
|
.foregroundStyle(Tj.Palette.paper)
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 4)
|
|
.background(Capsule().fill(Color(red: 0.37, green: 0.47, blue: 0.31).opacity(0.85)))
|
|
.onAppear {
|
|
withAnimation(.easeInOut(duration: 2.2).repeatForever(autoreverses: true)) {
|
|
dotPulse.toggle()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var bottomControls: some View {
|
|
HStack {
|
|
CircleIconButton(icon: "bolt.fill", size: 44) { }
|
|
Spacer()
|
|
Button(action: onShoot) {
|
|
ZStack {
|
|
Circle().fill(Tj.Palette.ink)
|
|
Circle().strokeBorder(Tj.Palette.paper, lineWidth: 4)
|
|
}
|
|
.frame(width: 72, height: 72)
|
|
.overlay(
|
|
Circle().strokeBorder(Color.white.opacity(0.2), lineWidth: 1)
|
|
.frame(width: 76, height: 76)
|
|
)
|
|
}
|
|
.buttonStyle(.plain)
|
|
Spacer()
|
|
CircleIconButton(icon: "photo.on.rectangle", size: 44) { }
|
|
}
|
|
.padding(.horizontal, 32)
|
|
.padding(.bottom, 40)
|
|
}
|
|
}
|
|
|
|
private struct CircleIconButton: View {
|
|
let icon: String
|
|
let size: CGFloat
|
|
let action: () -> Void
|
|
var body: some View {
|
|
Button(action: action) {
|
|
ZStack {
|
|
Circle().fill(Color.white.opacity(0.12))
|
|
Image(systemName: icon)
|
|
.font(.system(size: 18, weight: .medium))
|
|
.foregroundStyle(Tj.Palette.paper)
|
|
}
|
|
.frame(width: size, height: size)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|