```
refactor: 重命名项目名称从"体己"到"康康" 将整个项目的目录结构从"体己"重命名为"康康",包括所有源代码文件、 资源文件、测试文件以及Xcode项目配置文件。此更改涉及项目中所有的 文件路径和应用入口点(App/TijiApp.swift → App/KangkangApp.swift)。 ```
This commit is contained in:
293
康康/Features/Archive/B4ProgressView.swift
Normal file
293
康康/Features/Archive/B4ProgressView.swift
Normal file
@@ -0,0 +1,293 @@
|
||||
import SwiftUI
|
||||
|
||||
struct B4ProgressView: View {
|
||||
var onComplete: () -> Void
|
||||
|
||||
@State private var step: Int = 1
|
||||
@State private var pulse = false
|
||||
@State private var glow = false
|
||||
@State private var rotate: Double = 0
|
||||
@State private var elapsed: Double = 0.2
|
||||
|
||||
private let lineLabels = [
|
||||
"正在本地识别第 1 / 3 页…",
|
||||
"正在本地识别第 2 / 3 页…",
|
||||
"正在本地识别第 3 / 3 页…",
|
||||
"提取指标 · 共 28 项",
|
||||
"生成整体摘要…",
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
backgroundGradient.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
chip.padding(.bottom, 36)
|
||||
|
||||
Text("本地 AI · 正在解读")
|
||||
.font(.system(size: 22, weight: .semibold))
|
||||
.tracking(1)
|
||||
.foregroundStyle(Color.white.opacity(0.95))
|
||||
.padding(.bottom, 6)
|
||||
|
||||
Text("QWEN2.5-VL · ON-DEVICE · SME2")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.tracking(0.5)
|
||||
.foregroundStyle(Color.white.opacity(0.55))
|
||||
.padding(.bottom, 30)
|
||||
|
||||
lineList
|
||||
.padding(.horizontal, 28)
|
||||
|
||||
speedBadge.padding(.top, 32)
|
||||
Spacer()
|
||||
|
||||
Text("本地处理中 · 不会上传任何内容")
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.tracking(0.5)
|
||||
.foregroundStyle(Color.white.opacity(0.45))
|
||||
.padding(.bottom, 30)
|
||||
}
|
||||
.padding(.horizontal, 28)
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
.onAppear { startAnimations() }
|
||||
}
|
||||
|
||||
private var backgroundGradient: some View {
|
||||
RadialGradient(
|
||||
colors: [
|
||||
Color(red: 0.22, green: 0.21, blue: 0.18),
|
||||
Color(red: 0.13, green: 0.12, blue: 0.10),
|
||||
Color(red: 0.08, green: 0.075, blue: 0.06),
|
||||
],
|
||||
center: .init(x: 0.5, y: 0.3),
|
||||
startRadius: 60,
|
||||
endRadius: 700
|
||||
)
|
||||
}
|
||||
|
||||
private var chip: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(red: 0.93, green: 0.75, blue: 0.40).opacity(glow ? 0.18 : 0.0))
|
||||
.frame(width: 176, height: 176)
|
||||
.blur(radius: 30)
|
||||
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(0.18),
|
||||
style: StrokeStyle(lineWidth: 1, dash: [4, 4]))
|
||||
.frame(width: 140, height: 140)
|
||||
.rotationEffect(.degrees(rotate))
|
||||
|
||||
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color(red: 0.36, green: 0.34, blue: 0.30),
|
||||
Color(red: 0.22, green: 0.21, blue: 0.18)],
|
||||
startPoint: .topLeading, endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(0.10), lineWidth: 1)
|
||||
)
|
||||
.frame(width: 96, height: 96)
|
||||
.shadow(color: .black.opacity(0.4), radius: 20, x: 0, y: 12)
|
||||
.overlay(ChipGlyph())
|
||||
.overlay(alignment: .topTrailing) {
|
||||
Circle()
|
||||
.fill(Color(red: 0.95, green: 0.78, blue: 0.40))
|
||||
.frame(width: 6, height: 6)
|
||||
.opacity(pulse ? 1 : 0.35)
|
||||
.shadow(color: Color(red: 0.95, green: 0.78, blue: 0.40), radius: 6)
|
||||
.padding(10)
|
||||
}
|
||||
.scaleEffect(pulse ? 1.06 : 1.0)
|
||||
.opacity(pulse ? 0.92 : 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
private var lineList: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
ForEach(Array(lineLabels.enumerated()), id: \.offset) { idx, label in
|
||||
LineRow(
|
||||
text: label,
|
||||
done: step > idx + 1,
|
||||
active: step == idx + 1,
|
||||
isLast: idx == lineLabels.count - 1
|
||||
)
|
||||
.opacity(step >= idx + 1 ? 1 : 0)
|
||||
.offset(y: step >= idx + 1 ? 0 : 6)
|
||||
.animation(.easeOut(duration: 0.4).delay(Double(idx) * 0.05), value: step)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
private var speedBadge: some View {
|
||||
Text(String(format: "已处理 %.1fs · 比云端快 4.2×", elapsed))
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.tracking(0.6)
|
||||
.foregroundStyle(Color.white.opacity(0.75))
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Capsule().fill(Color.white.opacity(0.08)))
|
||||
}
|
||||
|
||||
private func startAnimations() {
|
||||
withAnimation(.easeInOut(duration: 2.0).repeatForever(autoreverses: true)) {
|
||||
pulse.toggle()
|
||||
}
|
||||
withAnimation(.easeInOut(duration: 2.4).repeatForever(autoreverses: true)) {
|
||||
glow.toggle()
|
||||
}
|
||||
withAnimation(.linear(duration: 14).repeatForever(autoreverses: false)) {
|
||||
rotate = 360
|
||||
}
|
||||
|
||||
Task {
|
||||
for _ in 0..<lineLabels.count {
|
||||
try? await Task.sleep(nanoseconds: 900_000_000)
|
||||
await MainActor.run {
|
||||
withAnimation { step += 1 }
|
||||
elapsed += 0.9
|
||||
}
|
||||
}
|
||||
try? await Task.sleep(nanoseconds: 600_000_000)
|
||||
await MainActor.run { onComplete() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LineRow: View {
|
||||
let text: String
|
||||
let done: Bool
|
||||
let active: Bool
|
||||
let isLast: Bool
|
||||
|
||||
@State private var dotPulse = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 10) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(done
|
||||
? Color(red: 0.95, green: 0.78, blue: 0.40)
|
||||
: Color.white.opacity(0.12))
|
||||
if done {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
.foregroundStyle(Color(red: 0.10, green: 0.115, blue: 0.094))
|
||||
}
|
||||
}
|
||||
.frame(width: 14, height: 14)
|
||||
|
||||
Text(text)
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(done ? Color.white.opacity(0.95) : Color.white.opacity(0.45))
|
||||
|
||||
if active {
|
||||
Spacer()
|
||||
Text("···")
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.foregroundStyle(Color.white.opacity(dotPulse ? 0.9 : 0.4))
|
||||
.onAppear {
|
||||
withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
|
||||
dotPulse.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChipGlyph: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(0.8), lineWidth: 1.4)
|
||||
.frame(width: 28, height: 28)
|
||||
|
||||
RoundedRectangle(cornerRadius: 2, style: .continuous)
|
||||
.fill(Color(red: 0.95, green: 0.78, blue: 0.40).opacity(0.35))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 2, style: .continuous)
|
||||
.strokeBorder(Color(red: 0.95, green: 0.78, blue: 0.40), lineWidth: 1)
|
||||
)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
innerCross
|
||||
outerPins
|
||||
}
|
||||
.frame(width: 56, height: 56)
|
||||
}
|
||||
|
||||
private var innerCross: some View {
|
||||
Canvas { ctx, size in
|
||||
let amber = Color(red: 0.95, green: 0.78, blue: 0.40)
|
||||
let stroke = GraphicsContext.Shading.color(amber)
|
||||
let cx = size.width / 2
|
||||
let cy = size.height / 2
|
||||
|
||||
let pairs: [(CGPoint, CGPoint)] = [
|
||||
(CGPoint(x: cx, y: cy - 8), CGPoint(x: cx, y: cy - 4)),
|
||||
(CGPoint(x: cx, y: cy + 4), CGPoint(x: cx, y: cy + 8)),
|
||||
(CGPoint(x: cx - 8, y: cy), CGPoint(x: cx - 4, y: cy)),
|
||||
(CGPoint(x: cx + 4, y: cy), CGPoint(x: cx + 8, y: cy)),
|
||||
]
|
||||
for (s, e) in pairs {
|
||||
var p = Path()
|
||||
p.move(to: s)
|
||||
p.addLine(to: e)
|
||||
ctx.stroke(p, with: stroke, style: StrokeStyle(lineWidth: 1, lineCap: .round))
|
||||
}
|
||||
}
|
||||
.frame(width: 56, height: 56)
|
||||
}
|
||||
|
||||
private var outerPins: some View {
|
||||
Canvas { ctx, size in
|
||||
let pinColor = GraphicsContext.Shading.color(Color.white.opacity(0.45))
|
||||
let cx = size.width / 2
|
||||
let cy = size.height / 2
|
||||
let halfChip: CGFloat = 14
|
||||
let outsideStart: CGFloat = 20
|
||||
let outsideEnd: CGFloat = 26
|
||||
|
||||
let positions: [CGFloat] = [-8, 0, 8]
|
||||
|
||||
for offset in positions {
|
||||
// top
|
||||
var p = Path()
|
||||
p.move(to: CGPoint(x: cx + offset, y: cy - outsideEnd))
|
||||
p.addLine(to: CGPoint(x: cx + offset, y: cy - halfChip))
|
||||
ctx.stroke(p, with: pinColor, style: StrokeStyle(lineWidth: 1, lineCap: .round))
|
||||
|
||||
// bottom
|
||||
p = Path()
|
||||
p.move(to: CGPoint(x: cx + offset, y: cy + halfChip))
|
||||
p.addLine(to: CGPoint(x: cx + offset, y: cy + outsideEnd))
|
||||
ctx.stroke(p, with: pinColor, style: StrokeStyle(lineWidth: 1, lineCap: .round))
|
||||
|
||||
// left
|
||||
p = Path()
|
||||
p.move(to: CGPoint(x: cx - outsideEnd, y: cy + offset))
|
||||
p.addLine(to: CGPoint(x: cx - halfChip, y: cy + offset))
|
||||
ctx.stroke(p, with: pinColor, style: StrokeStyle(lineWidth: 1, lineCap: .round))
|
||||
|
||||
// right
|
||||
p = Path()
|
||||
p.move(to: CGPoint(x: cx + halfChip, y: cy + offset))
|
||||
p.addLine(to: CGPoint(x: cx + outsideStart + 2, y: cy + offset))
|
||||
ctx.stroke(p, with: pinColor, style: StrokeStyle(lineWidth: 1, lineCap: .round))
|
||||
}
|
||||
}
|
||||
.frame(width: 56, height: 56)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
B4ProgressView(onComplete: {})
|
||||
}
|
||||
Reference in New Issue
Block a user