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