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()
}
|