替换 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>
101 lines
3.8 KiB
Swift
101 lines
3.8 KiB
Swift
import SwiftUI
|
|
|
|
struct SmartFramer: View {
|
|
var radius: CGFloat = 10
|
|
var height: CGFloat = 56
|
|
@State private var breathing = false
|
|
|
|
var body: some View {
|
|
GeometryReader { geo in
|
|
ZStack {
|
|
Color.black.opacity(0.32)
|
|
.mask(
|
|
Rectangle()
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
|
.frame(height: height)
|
|
.padding(.horizontal, geo.size.width * 0.08)
|
|
.blendMode(.destinationOut)
|
|
)
|
|
.compositingGroup()
|
|
)
|
|
|
|
RoundedRectangle(cornerRadius: radius + 4, style: .continuous)
|
|
.stroke(Color(red: 0.95, green: 0.78, blue: 0.45), lineWidth: 1.5)
|
|
.shadow(color: Color(red: 0.95, green: 0.78, blue: 0.45).opacity(0.5), radius: 8)
|
|
.frame(height: height + 8)
|
|
.padding(.horizontal, geo.size.width * 0.08 - 4)
|
|
.opacity(breathing ? 1 : 0.35)
|
|
|
|
cornerMarks(in: geo.size)
|
|
}
|
|
.frame(width: geo.size.width, height: geo.size.height)
|
|
.onAppear {
|
|
withAnimation(.easeInOut(duration: 2.2).repeatForever(autoreverses: true)) {
|
|
breathing.toggle()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func cornerMarks(in size: CGSize) -> some View {
|
|
let inset = size.width * 0.08
|
|
return ZStack {
|
|
ForEach(Corner.allCases, id: \.self) { corner in
|
|
CornerMark(corner: corner, radius: radius)
|
|
.frame(width: 18, height: 18)
|
|
.position(corner.position(in: size, inset: inset, frameHeight: height))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum Corner: CaseIterable {
|
|
case tl, tr, bl, br
|
|
func position(in size: CGSize, inset: CGFloat, frameHeight: CGFloat) -> CGPoint {
|
|
let centerY = size.height / 2
|
|
let top = centerY - frameHeight / 2
|
|
let bottom = centerY + frameHeight / 2
|
|
switch self {
|
|
case .tl: return CGPoint(x: inset, y: top)
|
|
case .tr: return CGPoint(x: size.width - inset, y: top)
|
|
case .bl: return CGPoint(x: inset, y: bottom)
|
|
case .br: return CGPoint(x: size.width - inset, y: bottom)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct CornerMark: View {
|
|
let corner: Corner
|
|
let radius: CGFloat
|
|
|
|
var body: some View {
|
|
Path { p in
|
|
let r = min(radius, 8)
|
|
switch corner {
|
|
case .tl:
|
|
p.move(to: CGPoint(x: 0, y: 18))
|
|
p.addLine(to: CGPoint(x: 0, y: r))
|
|
p.addQuadCurve(to: CGPoint(x: r, y: 0), control: CGPoint(x: 0, y: 0))
|
|
p.addLine(to: CGPoint(x: 18, y: 0))
|
|
case .tr:
|
|
p.move(to: CGPoint(x: 0, y: 0))
|
|
p.addLine(to: CGPoint(x: 18 - r, y: 0))
|
|
p.addQuadCurve(to: CGPoint(x: 18, y: r), control: CGPoint(x: 18, y: 0))
|
|
p.addLine(to: CGPoint(x: 18, y: 18))
|
|
case .bl:
|
|
p.move(to: CGPoint(x: 0, y: 0))
|
|
p.addLine(to: CGPoint(x: 0, y: 18 - r))
|
|
p.addQuadCurve(to: CGPoint(x: r, y: 18), control: CGPoint(x: 0, y: 18))
|
|
p.addLine(to: CGPoint(x: 18, y: 18))
|
|
case .br:
|
|
p.move(to: CGPoint(x: 0, y: 18))
|
|
p.addLine(to: CGPoint(x: 18 - r, y: 18))
|
|
p.addQuadCurve(to: CGPoint(x: 18, y: 18 - r), control: CGPoint(x: 18, y: 18))
|
|
p.addLine(to: CGPoint(x: 18, y: 0))
|
|
}
|
|
}
|
|
.stroke(Tj.Palette.paper, style: StrokeStyle(lineWidth: 2.5, lineCap: .round))
|
|
}
|
|
}
|