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

v1.3.2: 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}