feat(clockout): weekend "今天不上班" mode

Saturday/Sunday previously kept showing the workday countdown even
though no one's working — UX surfaced friction where there was
nothing to count. Now ClockoutView switches to a purple
weekend-hero card with "🌴 今天是周末 · 去做你想做的事" copy.

Implementation:
- WorkerStore.isWeekend computed (Sun=1, Sat=7)
- ClockoutView.body branches: weekendHero + weekendTipText for
  weekend, original countdown+progress+controls for weekday
- Clockout celebration logic still runs in the background — if
  someone does work Saturday and crosses their configured clockout
  hour, the next-weekday view will see the dedupe key updated;
  no spurious extra fire

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
徐翔宇 2026-05-20 09:49:17 +08:00
parent d4fea96d17
commit f0a116ccf7
2 changed files with 90 additions and 17 deletions

View File

@ -576,6 +576,14 @@ final class WorkerStore: ObservableObject {
/// no observable "zero" frame celebrating the "" moment was /// no observable "zero" frame celebrating the "" moment was
/// impossible. Now: hit zero, hold zero until midnight, restart at /// impossible. Now: hit zero, hold zero until midnight, restart at
/// new day's wall-time countdown. /// new day's wall-time countdown.
/// True iff today is Saturday or Sunday (local time). Used by
/// ClockoutView to switch to a "" mode without forcing
/// the user to fiddle with their clockout time on the weekend.
var isWeekend: Bool {
let weekday = calendar.component(.weekday, from: Date())
return weekday == 1 || weekday == 7 // Sun = 1, Sat = 7
}
var clockoutRemainingSec: Int { var clockoutRemainingSec: Int {
let now = Date() let now = Date()
var comps = calendar.dateComponents([.year, .month, .day], from: now) var comps = calendar.dateComponents([.year, .month, .day], from: now)

View File

@ -16,24 +16,23 @@ struct ClockoutView: View {
var body: some View { var body: some View {
ZStack(alignment: .top) { ZStack(alignment: .top) {
VStack(spacing: 18) { VStack(spacing: 18) {
if store.isWeekend {
weekendHero
weekendTipText
Spacer(minLength: 0)
} else {
statusBadge statusBadge
countdownDisplay countdownDisplay
progressBar progressBar
controls controls
Divider() Divider()
.background(WorkerTheme.overlay08) .background(WorkerTheme.overlay08)
.padding(.horizontal, 24) .padding(.horizontal, 24)
timeRow timeRow
tipText tipText
Spacer(minLength: 0) Spacer(minLength: 0)
} }
}
.padding(.top, 8) .padding(.top, 8)
// Celebration banner overlays the panel for ~5s after the // Celebration banner overlays the panel for ~5s after the
@ -275,6 +274,72 @@ struct ClockoutView: View {
) )
} }
// MARK: - Weekend mode
/// Weekend-mode hero replaces the countdown card with a purple
/// "today's a weekend" panel. The clockout countdown still works
/// in the background (anyone working Saturday's edge case), but
/// the default UI no longer rubs in the fact that there's
/// theoretically a clockout time on a day no one's working.
private var weekendHero: some View {
VStack(spacing: 8) {
HStack(spacing: 8) {
Circle()
.fill(WorkerTheme.weekendPurple)
.frame(width: 8, height: 8)
.shadow(color: WorkerTheme.weekendPurple.opacity(0.7), radius: 4)
Text("今天不上班")
.font(.system(size: 12.5, weight: .semibold))
.foregroundColor(WorkerTheme.fg85)
}
.padding(.horizontal, 12)
.frame(height: 26)
.background(Capsule().fill(WorkerTheme.overlay06))
Text("🌴")
.font(.system(size: 64))
.padding(.top, 8)
Text("今天是周末")
.font(.system(size: 22, weight: .bold))
.foregroundColor(WorkerTheme.fgPrimary)
Text("去做你想做的事")
.font(.system(size: 13))
.foregroundColor(WorkerTheme.fg55)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 28)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(
LinearGradient(
colors: [
WorkerTheme.weekendPurple.opacity(0.18),
WorkerTheme.weekendPurple.opacity(0.04)
],
startPoint: .topLeading, endPoint: .bottomTrailing
)
)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(WorkerTheme.weekendPurple.opacity(0.35), lineWidth: 0.5)
)
)
.padding(.horizontal, 16)
.padding(.top, 12)
}
private var weekendTipText: some View {
Text("如果你周末也上班,记得在 周末 tab 取消休息时调整设置。\n(暂时还没做这个 toggle — 反正下周一会自动恢复倒计时。)")
.font(.system(size: 11))
.foregroundColor(WorkerTheme.fg45)
.padding(.horizontal, 20)
.padding(.top, 8)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
}
private var tipText: some View { private var tipText: some View {
Text("到点会有通知 + 一段 Glass 音效庆祝下班\n每日只触发一次,重启插件不会重复响铃。") Text("到点会有通知 + 一段 Glass 音效庆祝下班\n每日只触发一次,重启插件不会重复响铃。")
.font(.system(size: 11)) .font(.system(size: 11))