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: AnimalCard.swift · raw

  1//
  2//  AnimalCard.swift
  3//  DayByDay
  4//
  5
  6import SwiftUI
  7
  8/// A single large, colorful card representing one animal.
  9/// Tapping the card plays a bounce animation and speaks the animal name aloud.
 10struct AnimalCard: View {
 11    let animal: LearnAnimal
 12    @State private var isTapped = false
 13
 14    var body: some View {
 15        ZStack {
 16            RoundedRectangle(cornerRadius: 32, style: .continuous)
 17                .fill(animal.color.gradient)
 18                .shadow(color: animal.color.opacity(0.4), radius: 8, y: 4)
 19
 20            VStack(spacing: 16) {
 21                Image(systemName: animal.symbol)
 22                    .font(.system(size: 64))
 23                    .foregroundStyle(.white)
 24                    .symbolRenderingMode(.hierarchical)
 25
 26                Text(animal.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(animal.name)
 38            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
 39                isTapped = false
 40            }
 41        }
 42        .accessibilityLabel(animal.name)
 43        .accessibilityAddTraits(.isButton)
 44    }
 45}
 46
 47// MARK: - Animal model
 48
 49enum LearnAnimal: Int, CaseIterable, Identifiable {
 50    case cat, dog, bird, fish, rabbit, turtle, ladybug, ant, lizard
 51
 52    var id: Int { rawValue }
 53
 54    var name: String {
 55        switch self {
 56        case .cat:       "Cat"
 57        case .dog:       "Dog"
 58        case .bird:      "Bird"
 59        case .fish:      "Fish"
 60        case .rabbit:    "Rabbit"
 61        case .turtle:    "Turtle"
 62        case .ladybug:   "Ladybug"
 63        case .ant:       "Ant"
 64        case .lizard:    "Lizard"
 65        }
 66    }
 67
 68    var symbol: String {
 69        switch self {
 70        case .cat:       "cat.fill"
 71        case .dog:       "dog.fill"
 72        case .bird:      "bird.fill"
 73        case .fish:      "fish.fill"
 74        case .rabbit:    "hare.fill"
 75        case .turtle:    "tortoise.fill"
 76        case .ladybug:   "ladybug.fill"
 77        case .ant:       "ant.fill"
 78        case .lizard:    "lizard.fill"
 79        }
 80    }
 81
 82    var color: Color {
 83        switch self {
 84        case .cat:       Color(red: 0.9, green: 0.55, blue: 0.2)   // orange
 85        case .dog:       Color(red: 0.6, green: 0.45, blue: 0.3)   // brown
 86        case .bird:      Color(red: 0.3, green: 0.65, blue: 0.9)   // sky blue
 87        case .fish:      Color(red: 0.2, green: 0.75, blue: 0.8)   // teal
 88        case .rabbit:    Color(red: 0.75, green: 0.6, blue: 0.8)   // lavender
 89        case .turtle:    Color(red: 0.35, green: 0.7, blue: 0.4)   // green
 90        case .ladybug:   Color(red: 0.9, green: 0.25, blue: 0.25)  // red
 91        case .ant:       Color(red: 0.3, green: 0.3, blue: 0.3)    // dark gray
 92        case .lizard:    Color(red: 0.45, green: 0.75, blue: 0.35)  // lime
 93        }
 94    }
 95}
 96
 97#Preview {
 98    HStack(spacing: 24) {
 99        AnimalCard(animal: .cat)
100            .frame(width: 200, height: 260)
101    }
102    .padding()
103}