// // WaterView.swift // 摸鱼侠 plugin v0.1 // // 喝水 — 8 cup target with progress dots, tap big cup to log a sip. // import SwiftUI struct WaterView: View { @ObservedObject var store: WorkerStore var body: some View { // No ScrollView — panel body sits at ~470pt available and we // tune everything below to fit: tighter VStack spacing, smaller // cup hero, toggles on a single row, no tipText. VStack(spacing: 10) { statusBadge cupHero controls Divider() .background(WorkerTheme.overlay08) .padding(.horizontal, 24) goalRow toggleRow Spacer(minLength: 0) } .padding(.top, 6) } // MARK: - Toggle row (compact horizontal layout — saves vertical // space so the whole tab fits without a scroll bar). private var toggleRow: some View { HStack(spacing: 8) { togglePill( icon: "timer", label: "🍅 +1", isOn: store.waterAutoFromPomodoro, tint: WorkerTheme.tomato, action: { store.waterSetAutoFromPomodoro(!store.waterAutoFromPomodoro) } ) togglePill( icon: "bell.fill", label: "整点提醒", isOn: store.waterHourlyReminder, tint: WorkerTheme.water, action: { store.waterSetHourlyReminder(!store.waterHourlyReminder) } ) } .padding(.horizontal, 16) } /// Compact toggle for the horizontal toggleRow. Icon + short label + /// inline slim toggle track inside a single capsule. Sized to fit /// 2 across a 380pt panel with 16pt horizontal padding. private func togglePill(icon: String, label: String, isOn: Bool, tint: Color, action: @escaping () -> Void) -> some View { Button(action: action) { HStack(spacing: 6) { Image(systemName: icon) .font(.system(size: 10, weight: .semibold)) .foregroundColor(isOn ? tint : WorkerTheme.fg55) Text(label) .font(.system(size: 11, weight: .medium)) .foregroundColor(isOn ? WorkerTheme.fgPrimary : WorkerTheme.fg70) .lineLimit(1) Spacer(minLength: 4) ZStack(alignment: isOn ? .trailing : .leading) { Capsule() .fill(isOn ? tint.opacity(0.35) : WorkerTheme.overlay08) .frame(width: 24, height: 14) Circle() .fill(isOn ? tint : WorkerTheme.fg55) .frame(width: 10, height: 10) .padding(2) } } .padding(.horizontal, 10) .frame(maxWidth: .infinity) .frame(height: 30) .background( RoundedRectangle(cornerRadius: 8) .fill(WorkerTheme.overlay04) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(isOn ? tint.opacity(0.35) : WorkerTheme.overlay08, lineWidth: 0.5) ) ) } .buttonStyle(.plain) } // MARK: - Status private var statusBadge: some View { HStack(spacing: 8) { Circle() .fill(WorkerTheme.water) .frame(width: 8, height: 8) .shadow(color: WorkerTheme.water.opacity(0.7), radius: 4) Text(badgeText) .font(.system(size: 12.5, weight: .semibold)) .foregroundColor(WorkerTheme.fg85) } .padding(.horizontal, 12) .frame(height: 26) .background(Capsule().fill(WorkerTheme.overlay06)) } private var badgeText: String { if store.waterCupsToday >= store.waterGoal { return "今日目标已达成 ✨" } return "今日 \(store.waterCupsToday) / \(store.waterGoal) 杯" } // MARK: - Cup hero (tappable) private var cupHero: some View { VStack(spacing: 8) { Button(action: { store.waterAddCup() }) { ZStack { // Water-fill cup — sized down from 130×160 to fit // the 380×580 panel without scroll. cupShape .frame(width: 100, height: 130) } } .buttonStyle(.plain) .help("点击杯子记录一杯水") // Progress dots HStack(spacing: 5) { ForEach(0.. Path { // Slight trapezoid: narrower at the bottom. Straight rim with // a 4pt rounded base. var p = Path() let topInset: CGFloat = 0 let bottomInset: CGFloat = rect.width * 0.08 let radius: CGFloat = 8 // Start top-left. p.move(to: CGPoint(x: rect.minX + topInset, y: rect.minY)) // Top edge. p.addLine(to: CGPoint(x: rect.maxX - topInset, y: rect.minY)) // Right side (sloping inward). p.addLine(to: CGPoint(x: rect.maxX - bottomInset, y: rect.maxY - radius)) // Bottom-right curve. p.addQuadCurve( to: CGPoint(x: rect.maxX - bottomInset - radius, y: rect.maxY), control: CGPoint(x: rect.maxX - bottomInset, y: rect.maxY) ) // Bottom edge. p.addLine(to: CGPoint(x: rect.minX + bottomInset + radius, y: rect.maxY)) // Bottom-left curve. p.addQuadCurve( to: CGPoint(x: rect.minX + bottomInset, y: rect.maxY - radius), control: CGPoint(x: rect.minX + bottomInset, y: rect.maxY) ) // Left side back to top. p.addLine(to: CGPoint(x: rect.minX + topInset, y: rect.minY)) p.closeSubpath() return p } }