krz/daybyday

clone: git clone https://gitbay.org/krz/daybyday.git

main: ShapeCard.swift · raw

  1//
  2//  ShapeCard.swift
  3//  DayByDay
  4//
  5
  6import SwiftUI
  7
  8/// A single large, colorful card representing one shape.
  9/// Tapping the card plays a bounce animation and speaks the shape name aloud.
 10struct ShapeCard: View {
 11    let shape: LearnShape
 12    @State private var isTapped = false
 13
 14    var body: some View {
 15        ZStack {
 16            RoundedRectangle(cornerRadius: 32, style: .continuous)
 17                .fill(shape.color.gradient)
 18                .shadow(color: shape.color.opacity(0.4), radius: 8, y: 4)
 19
 20            VStack(spacing: 16) {
 21                shape.shapeView
 22                    .frame(width: 80, height: 80)
 23                    .foregroundStyle(.white)
 24
 25                Text(shape.name)
 26                    .font(.system(size: 32, weight: .bold, design: .rounded))
 27                    .foregroundStyle(.white)
 28            }
 29        }
 30        .scaleEffect(isTapped ? 1.12 : 1.0)
 31        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
 32        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
 33        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
 34        .onTapGesture {
 35            isTapped = true
 36            SpeechSynthesizer.shared.speak(shape.name)
 37            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
 38                isTapped = false
 39            }
 40        }
 41        .accessibilityLabel(shape.name)
 42        .accessibilityAddTraits(.isButton)
 43    }
 44}
 45
 46// MARK: - Shape model
 47
 48enum LearnShape: Int, CaseIterable, Identifiable {
 49    case circle, square, triangle, star, heart, oval, diamond, rectangle
 50
 51    var id: Int { rawValue }
 52
 53    var name: String {
 54        switch self {
 55        case .circle:    "Circle"
 56        case .square:    "Square"
 57        case .triangle:  "Triangle"
 58        case .star:      "Star"
 59        case .heart:     "Heart"
 60        case .oval:      "Oval"
 61        case .diamond:   "Diamond"
 62        case .rectangle: "Rectangle"
 63        }
 64    }
 65
 66    @ViewBuilder
 67    var shapeView: some View {
 68        switch self {
 69        case .circle:
 70            Circle().fill(.white)
 71        case .square:
 72            SwiftUI.Rectangle().fill(.white)
 73        case .triangle:
 74            TriangleShape().fill(.white)
 75        case .star:
 76            Image(systemName: "star.fill")
 77                .font(.system(size: 72))
 78                .foregroundStyle(.white)
 79        case .heart:
 80            Image(systemName: "heart.fill")
 81                .font(.system(size: 72))
 82                .foregroundStyle(.white)
 83        case .oval:
 84            Ellipse().fill(.white)
 85                .frame(width: 90, height: 60)
 86        case .diamond:
 87            SwiftUI.Rectangle().fill(.white)
 88                .frame(width: 60, height: 60)
 89                .rotationEffect(.degrees(45))
 90        case .rectangle:
 91            SwiftUI.Rectangle().fill(.white)
 92                .frame(width: 90, height: 55)
 93        }
 94    }
 95
 96    var color: Color {
 97        switch self {
 98        case .circle:    Color(red: 0.9, green: 0.3, blue: 0.3)   // red
 99        case .square:    Color(red: 0.3, green: 0.5, blue: 0.9)   // blue
100        case .triangle:  Color(red: 0.35, green: 0.75, blue: 0.45) // green
101        case .star:      Color(red: 1.0, green: 0.75, blue: 0.15)  // gold
102        case .heart:     Color(red: 0.9, green: 0.35, blue: 0.5)   // pink
103        case .oval:      Color(red: 0.55, green: 0.4, blue: 0.85)  // purple
104        case .diamond:   Color(red: 0.2, green: 0.75, blue: 0.8)   // teal
105        case .rectangle: Color(red: 1.0, green: 0.55, blue: 0.2)   // orange
106        }
107    }
108}
109
110/// A simple triangle drawn with a Path.
111struct TriangleShape: Shape {
112    func path(in rect: CGRect) -> Path {
113        Path { path in
114            path.move(to: CGPoint(x: rect.midX, y: rect.minY))
115            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
116            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
117            path.closeSubpath()
118        }
119    }
120}
121
122#Preview {
123    HStack(spacing: 24) {
124        ShapeCard(shape: .triangle)
125            .frame(width: 200, height: 260)
126        ShapeCard(shape: .star)
127            .frame(width: 200, height: 260)
128    }
129    .padding()
130}