1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
//
// MonthCard.swift
// DayByDay
//
import SwiftUI
/// A single large, colorful card representing one month of the year.
/// Tapping the card plays a bounce animation and speaks the month name aloud.
struct MonthCard: View {
let month: MonthOfYear
var isHighlighted = false
@State private var isTapped = false
private var cardScale: CGFloat {
if isTapped { return 1.12 }
if isHighlighted { return 1.04 }
return 1.0
}
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(month.color.gradient)
.shadow(color: month.color.opacity(0.4), radius: 8, y: 4)
VStack(spacing: 16) {
Image(systemName: month.symbol)
.font(.system(size: 64))
.foregroundStyle(.white)
.symbolRenderingMode(.hierarchical)
Text(month.name)
.font(.system(size: 32, weight: .bold, design: .rounded))
.foregroundStyle(.white)
}
}
.overlay {
if isHighlighted {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.strokeBorder(.white, lineWidth: 5)
}
}
.shadow(color: isHighlighted ? .white.opacity(0.8) : .clear, radius: 12)
.scaleEffect(cardScale)
.contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
.rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
.animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
.onTapGesture {
isTapped = true
SpeechSynthesizer.shared.speak(month.name)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
isTapped = false
}
}
.accessibilityLabel(month.name)
.accessibilityAddTraits(.isButton)
}
}
// MARK: - Month model
enum MonthOfYear: Int, CaseIterable, Identifiable {
case january, february, march, april, may, june,
july, august, september, october, november, december
var id: Int { rawValue }
/// The month for today's date.
static var current: MonthOfYear {
// Calendar month: 1 = January, 12 = December
let month = Calendar.current.component(.month, from: Date())
return MonthOfYear(rawValue: month - 1)!
}
var name: String {
switch self {
case .january: "January"
case .february: "February"
case .march: "March"
case .april: "April"
case .may: "May"
case .june: "June"
case .july: "July"
case .august: "August"
case .september: "September"
case .october: "October"
case .november: "November"
case .december: "December"
}
}
var symbol: String {
switch self {
case .january: "snowflake"
case .february: "heart.fill"
case .march: "wind"
case .april: "cloud.rain.fill"
case .may: "leaf.fill"
case .june: "sun.max.fill"
case .july: "fireworks"
case .august: "beach.umbrella.fill"
case .september: "book.fill"
case .october: "moon.stars.fill"
case .november: "fork.knife"
case .december: "gift.fill"
}
}
var color: Color {
switch self {
case .january: Color(red: 0.55, green: 0.75, blue: 0.95) // ice blue
case .february: Color(red: 0.9, green: 0.35, blue: 0.5) // pink
case .march: Color(red: 0.45, green: 0.8, blue: 0.55) // spring green
case .april: Color(red: 0.5, green: 0.65, blue: 0.9) // rain blue
case .may: Color(red: 0.95, green: 0.7, blue: 0.2) // golden
case .june: Color(red: 1.0, green: 0.55, blue: 0.25) // warm orange
case .july: Color(red: 0.85, green: 0.25, blue: 0.3) // red
case .august: Color(red: 0.2, green: 0.75, blue: 0.8) // ocean teal
case .september: Color(red: 0.6, green: 0.45, blue: 0.85) // purple
case .october: Color(red: 0.9, green: 0.5, blue: 0.15) // pumpkin
case .november: Color(red: 0.7, green: 0.5, blue: 0.3) // brown
case .december: Color(red: 0.3, green: 0.5, blue: 0.85) // winter blue
}
}
}
#Preview {
HStack(spacing: 24) {
MonthCard(month: .march, isHighlighted: true)
.frame(width: 200, height: 260)
MonthCard(month: .april)
.frame(width: 200, height: 260)
}
.padding()
}
|