Files
kangkang/体己/Features/Quick/SmartFramer.swift
link2026 c050865db5 feat(ui): UI 骨架基线 — 3 Tab + RecordSheet + Quick/Archive 流程占位
替换 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>
2026-05-25 14:49:21 +08:00

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))
}
}