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/DayCard.swift · raw
1//
2// DayCard.swift
3// DayByDay
4//
5
6import SwiftUI
7
8/// A single large, colorful card representing one day of the week.
9/// Tapping the card plays a bounce animation and speaks the day name aloud.
10struct DayCard: View {
11 let day: DayOfWeek
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(day.color.gradient)
25 .shadow(color: day.color.opacity(0.4), radius: 8, y: 4)
26
27 VStack(spacing: 16) {
28 Image(systemName: day.symbol)
29 .font(.system(size: 64))
30 .foregroundStyle(.white)
31 .symbolRenderingMode(.hierarchical)
32
33 Text(day.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(day.name)
52 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
53 isTapped = false
54 }
55 }
56 .accessibilityLabel(day.name)
57 .accessibilityAddTraits(.isButton)
58 }
59}
60
61// MARK: - Day model
62
63enum DayOfWeek: Int, CaseIterable, Identifiable {
64 case sunday, monday, tuesday, wednesday, thursday, friday, saturday
65
66 var id: Int { rawValue }
67
68 /// The day of the week for today's date.
69 static var current: DayOfWeek {
70 // Calendar weekday: 1 = Sunday, 7 = Saturday
71 let weekday = Calendar.current.component(.weekday, from: Date())
72 return DayOfWeek(rawValue: weekday - 1)!
73 }
74
75 var name: String {
76 switch self {
77 case .sunday: "Sunday"
78 case .monday: "Monday"
79 case .tuesday: "Tuesday"
80 case .wednesday: "Wednesday"
81 case .thursday: "Thursday"
82 case .friday: "Friday"
83 case .saturday: "Saturday"
84 }
85 }
86
87 /// A recognisable SF Symbol icon for each day so children who cannot read
88 /// still get a unique visual cue.
89 var symbol: String {
90 switch self {
91 case .sunday: "sun.max.fill"
92 case .monday: "moon.fill"
93 case .tuesday: "star.fill"
94 case .wednesday: "cloud.fill"
95 case .thursday: "bolt.fill"
96 case .friday: "heart.fill"
97 case .saturday: "sparkles"
98 }
99 }
100
101 /// Bright, child-friendly colour for each card.
102 var color: Color {
103 switch self {
104 case .sunday: Color(red: 1.0, green: 0.6, blue: 0.2) // orange
105 case .monday: Color(red: 0.35, green: 0.5, blue: 0.9) // blue
106 case .tuesday: Color(red: 0.9, green: 0.3, blue: 0.4) // red-pink
107 case .wednesday: Color(red: 0.3, green: 0.75, blue: 0.45) // green
108 case .thursday: Color(red: 0.55, green: 0.4, blue: 0.9) // purple
109 case .friday: Color(red: 1.0, green: 0.8, blue: 0.2) // yellow
110 case .saturday: Color(red: 0.2, green: 0.8, blue: 0.8) // teal
111 }
112 }
113}
114
115#Preview {
116 HStack(spacing: 24) {
117 DayCard(day: .wednesday, isHighlighted: true)
118 .frame(width: 200, height: 260)
119 DayCard(day: .thursday)
120 .frame(width: 200, height: 260)
121 }
122 .padding()
123}