summaryrefslogtreecommitdiff
path: root/DayByDay/MonthCard.swift
blob: f462fd49338d0c2450e8bd6f9f2d15254bd531e8 (plain) (blame)
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
//
//  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

    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(isTapped ? 1.12 : (isHighlighted ? 1.04 : 1.0))
        .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()
}