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: LetterCard.swift · raw
1//
2// LetterCard.swift
3// DayByDay
4//
5
6import SwiftUI
7
8/// A single large, colorful card representing one letter of the alphabet.
9/// Tapping the card plays a bounce animation and speaks the letter aloud.
10struct LetterCard: View {
11 let letter: LearnLetter
12 @State private var isTapped = false
13
14 var body: some View {
15 ZStack {
16 RoundedRectangle(cornerRadius: 32, style: .continuous)
17 .fill(letter.color.gradient)
18 .shadow(color: letter.color.opacity(0.4), radius: 8, y: 4)
19
20 Text(letter.character)
21 .font(.system(size: 64, weight: .heavy, design: .rounded))
22 .foregroundStyle(.white)
23 }
24 .scaleEffect(isTapped ? 1.12 : 1.0)
25 .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
26 .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
27 .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
28 .onTapGesture {
29 isTapped = true
30 SpeechSynthesizer.shared.speak(letter.spokenName)
31 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
32 isTapped = false
33 }
34 }
35 .accessibilityLabel(letter.spokenName)
36 .accessibilityAddTraits(.isButton)
37 }
38}
39
40// MARK: - Letter model
41
42enum LearnLetter: Int, CaseIterable, Identifiable {
43 case a, b, c, d, e, f, g, h, i, j, k, l, m,
44 n, o, p, q, r, s, t, u, v, w, x, y, z
45
46 var id: Int { rawValue }
47
48 var character: String {
49 String(Character(UnicodeScalar(65 + rawValue)!))
50 }
51
52 /// Speak the letter name clearly for a child.
53 /// Lowercase prevents TTS from saying "Capital A".
54 var spokenName: String {
55 character.lowercased()
56 }
57
58 /// Cycle through 8 bright colors to give variety without unique-per-letter mapping.
59 var color: Color {
60 let palette: [Color] = [
61 Color(red: 0.9, green: 0.3, blue: 0.3), // red
62 Color(red: 1.0, green: 0.55, blue: 0.2), // orange
63 Color(red: 1.0, green: 0.8, blue: 0.2), // yellow
64 Color(red: 0.35, green: 0.75, blue: 0.4), // green
65 Color(red: 0.2, green: 0.65, blue: 0.9), // blue
66 Color(red: 0.55, green: 0.4, blue: 0.85), // purple
67 Color(red: 0.85, green: 0.4, blue: 0.6), // pink
68 Color(red: 0.2, green: 0.75, blue: 0.75), // teal
69 ]
70 return palette[rawValue % palette.count]
71 }
72}
73
74#Preview {
75 HStack(spacing: 24) {
76 LetterCard(letter: .a)
77 .frame(width: 140, height: 160)
78 LetterCard(letter: .b)
79 .frame(width: 140, height: 160)
80 LetterCard(letter: .c)
81 .frame(width: 140, height: 160)
82 }
83 .padding()
84}