krz/daybyday
clone: git clone https://gitbay.org/krz/daybyday.git
main: WeatherCard.swift · raw
1//
2// WeatherCard.swift
3// DayByDay
4//
5
6import SwiftUI
7
8/// A single large, colorful card representing one weather type.
9/// Tapping the card plays a bounce animation and speaks the weather name aloud.
10struct WeatherCard: View {
11 let weather: LearnWeather
12 @State private var isTapped = false
13
14 var body: some View {
15 ZStack {
16 RoundedRectangle(cornerRadius: 32, style: .continuous)
17 .fill(weather.color.gradient)
18 .shadow(color: weather.color.opacity(0.4), radius: 8, y: 4)
19
20 VStack(spacing: 16) {
21 Image(systemName: weather.symbol)
22 .font(.system(size: 64))
23 .foregroundStyle(.white)
24 .symbolRenderingMode(.hierarchical)
25
26 Text(weather.name)
27 .font(.system(size: 32, weight: .bold, design: .rounded))
28 .foregroundStyle(.white)
29 }
30 }
31 .scaleEffect(isTapped ? 1.12 : 1.0)
32 .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
33 .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
34 .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
35 .onTapGesture {
36 isTapped = true
37 SpeechSynthesizer.shared.speak(weather.name)
38 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
39 isTapped = false
40 }
41 }
42 .accessibilityLabel(weather.name)
43 .accessibilityAddTraits(.isButton)
44 }
45}
46
47// MARK: - Weather model
48
49enum LearnWeather: Int, CaseIterable, Identifiable {
50 case sunny, cloudy, rainy, snowy, windy, stormy, foggy, rainbow
51
52 var id: Int { rawValue }
53
54 var name: String {
55 switch self {
56 case .sunny: "Sunny"
57 case .cloudy: "Cloudy"
58 case .rainy: "Rainy"
59 case .snowy: "Snowy"
60 case .windy: "Windy"
61 case .stormy: "Stormy"
62 case .foggy: "Foggy"
63 case .rainbow: "Rainbow"
64 }
65 }
66
67 var symbol: String {
68 switch self {
69 case .sunny: "sun.max.fill"
70 case .cloudy: "cloud.fill"
71 case .rainy: "cloud.rain.fill"
72 case .snowy: "cloud.snow.fill"
73 case .windy: "wind"
74 case .stormy: "cloud.bolt.rain.fill"
75 case .foggy: "cloud.fog.fill"
76 case .rainbow: "rainbow"
77 }
78 }
79
80 var color: Color {
81 switch self {
82 case .sunny: Color(red: 1.0, green: 0.7, blue: 0.2) // orange
83 case .cloudy: Color(red: 0.6, green: 0.6, blue: 0.65) // gray
84 case .rainy: Color(red: 0.3, green: 0.6, blue: 0.9) // blue
85 case .snowy: Color(red: 0.55, green: 0.8, blue: 0.95) // light blue
86 case .windy: Color(red: 0.25, green: 0.7, blue: 0.7) // teal
87 case .stormy: Color(red: 0.2, green: 0.3, blue: 0.6) // dark blue
88 case .foggy: Color(red: 0.6, green: 0.5, blue: 0.7) // muted purple
89 case .rainbow: Color(red: 0.9, green: 0.45, blue: 0.6) // pink
90 }
91 }
92}
93
94#Preview {
95 HStack(spacing: 24) {
96 WeatherCard(weather: .sunny)
97 .frame(width: 200, height: 260)
98 WeatherCard(weather: .stormy)
99 .frame(width: 200, height: 260)
100 }
101 .padding()
102}