summaryrefslogtreecommitdiff
path: root/ShapeCard.swift
blob: ebc71e818a4d19812ae4dade48c50d3700f7a67f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
//  ShapeCard.swift
//  DayByDay
//

import SwiftUI

/// A single large, colorful card representing one shape.
/// Tapping the card plays a bounce animation and speaks the shape name aloud.
struct ShapeCard: View {
    let shape: LearnShape
    @State private var isTapped = false

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 32, style: .continuous)
                .fill(shape.color.gradient)
                .shadow(color: shape.color.opacity(0.4), radius: 8, y: 4)

            VStack(spacing: 16) {
                shape.shapeView
                    .frame(width: 80, height: 80)
                    .foregroundStyle(.white)

                Text(shape.name)
                    .font(.system(size: 32, weight: .bold, design: .rounded))
                    .foregroundStyle(.white)
            }
        }
        .scaleEffect(isTapped ? 1.12 : 1.0)
        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
        .onTapGesture {
            isTapped = true
            SpeechSynthesizer.shared.speak(shape.name)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                isTapped = false
            }
        }
        .accessibilityLabel(shape.name)
        .accessibilityAddTraits(.isButton)
    }
}

// MARK: - Shape model

enum LearnShape: Int, CaseIterable, Identifiable {
    case circle, square, triangle, star, heart, oval, diamond, rectangle

    var id: Int { rawValue }

    var name: String {
        switch self {
        case .circle:    "Circle"
        case .square:    "Square"
        case .triangle:  "Triangle"
        case .star:      "Star"
        case .heart:     "Heart"
        case .oval:      "Oval"
        case .diamond:   "Diamond"
        case .rectangle: "Rectangle"
        }
    }

    @ViewBuilder
    var shapeView: some View {
        switch self {
        case .circle:
            Circle().fill(.white)
        case .square:
            SwiftUI.Rectangle().fill(.white)
        case .triangle:
            TriangleShape().fill(.white)
        case .star:
            Image(systemName: "star.fill")
                .font(.system(size: 72))
                .foregroundStyle(.white)
        case .heart:
            Image(systemName: "heart.fill")
                .font(.system(size: 72))
                .foregroundStyle(.white)
        case .oval:
            Ellipse().fill(.white)
                .frame(width: 90, height: 60)
        case .diamond:
            SwiftUI.Rectangle().fill(.white)
                .frame(width: 60, height: 60)
                .rotationEffect(.degrees(45))
        case .rectangle:
            SwiftUI.Rectangle().fill(.white)
                .frame(width: 90, height: 55)
        }
    }

    var color: Color {
        switch self {
        case .circle:    Color(red: 0.9, green: 0.3, blue: 0.3)   // red
        case .square:    Color(red: 0.3, green: 0.5, blue: 0.9)   // blue
        case .triangle:  Color(red: 0.35, green: 0.75, blue: 0.45) // green
        case .star:      Color(red: 1.0, green: 0.75, blue: 0.15)  // gold
        case .heart:     Color(red: 0.9, green: 0.35, blue: 0.5)   // pink
        case .oval:      Color(red: 0.55, green: 0.4, blue: 0.85)  // purple
        case .diamond:   Color(red: 0.2, green: 0.75, blue: 0.8)   // teal
        case .rectangle: Color(red: 1.0, green: 0.55, blue: 0.2)   // orange
        }
    }
}

/// A simple triangle drawn with a Path.
struct TriangleShape: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: CGPoint(x: rect.midX, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
            path.closeSubpath()
        }
    }
}

#Preview {
    HStack(spacing: 24) {
        ShapeCard(shape: .triangle)
            .frame(width: 200, height: 260)
        ShapeCard(shape: .star)
            .frame(width: 200, height: 260)
    }
    .padding()
}