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/SeasonCard.swift · raw

  1//
  2//  SeasonCard.swift
  3//  DayByDay
  4//
  5
  6import SwiftUI
  7
  8/// A single large, colorful card representing one season.
  9/// Tapping the card plays a bounce animation and speaks the season name aloud.
 10struct SeasonCard: View {
 11    let season: Season
 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(season.color.gradient)
 25                .shadow(color: season.color.opacity(0.4), radius: 8, y: 4)
 26
 27            VStack(spacing: 16) {
 28                Image(systemName: season.symbol)
 29                    .font(.system(size: 64))
 30                    .foregroundStyle(.white)
 31                    .symbolRenderingMode(.hierarchical)
 32
 33                Text(season.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(season.name)
 52            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
 53                isTapped = false
 54            }
 55        }
 56        .accessibilityLabel(season.name)
 57        .accessibilityAddTraits(.isButton)
 58    }
 59}
 60
 61// MARK: - Season model
 62
 63enum Season: Int, CaseIterable, Identifiable {
 64    case spring, summer, fall, winter
 65
 66    var id: Int { rawValue }
 67
 68    /// The season for today's date.
 69    /// Spring: MarchMay, Summer: JuneAugust,
 70    /// Fall: SeptemberNovember, Winter: DecemberFebruary.
 71    static var current: Season {
 72        let month = Calendar.current.component(.month, from: Date())
 73        switch month {
 74        case 3...5:  return .spring
 75        case 6...8:  return .summer
 76        case 9...11: return .fall
 77        default:     return .winter
 78        }
 79    }
 80
 81    var name: String {
 82        switch self {
 83        case .spring: "Spring"
 84        case .summer: "Summer"
 85        case .fall:   "Fall"
 86        case .winter: "Winter"
 87        }
 88    }
 89
 90    var symbol: String {
 91        switch self {
 92        case .spring: "leaf.fill"
 93        case .summer: "sun.max.fill"
 94        case .fall:   "wind"
 95        case .winter: "snowflake"
 96        }
 97    }
 98
 99    var color: Color {
100        switch self {
101        case .spring: Color(red: 0.45, green: 0.8, blue: 0.5)   // fresh green
102        case .summer: Color(red: 1.0, green: 0.6, blue: 0.15)   // sunny orange
103        case .fall:   Color(red: 0.85, green: 0.45, blue: 0.2)  // autumn orange
104        case .winter: Color(red: 0.4, green: 0.65, blue: 0.95)  // cool blue
105        }
106    }
107}
108
109#Preview {
110    HStack(spacing: 24) {
111        SeasonCard(season: .spring, isHighlighted: true)
112            .frame(width: 200, height: 260)
113        SeasonCard(season: .summer)
114            .frame(width: 200, height: 260)
115    }
116    .padding()
117}