forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContentView.swift
More file actions
129 lines (115 loc) · 5.1 KB
/
Copy pathContentView.swift
File metadata and controls
129 lines (115 loc) · 5.1 KB
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
import SwiftUI
struct WatchRecorderView<Recorder: WatchRecorderControlling>: View {
@ObservedObject var viewModel: Recorder
@StateObject private var presentationController = RecordingPresentationController()
@State private var isPressed = false
var body: some View {
GeometryReader { geometry in
ZStack {
// Black background
Color.black
.ignoresSafeArea()
VStack(spacing: 0) {
Spacer()
Button(action: {
withAnimation(.easeInOut(duration: 0.1)) {
isPressed = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation(.easeInOut(duration: 0.1)) {
isPressed = false
}
}
if viewModel.isRecording {
viewModel.stopRecording()
} else {
viewModel.startRecording()
}
}) {
ZStack {
if presentationController.phase.showsRecordingRipple {
RecordingRippleView()
}
// Main button circle (white background)
Circle()
.fill(Color.white)
.frame(width: 80, height: 80)
.scaleEffect(isPressed ? 1.05 : 1.0)
.animation(.easeInOut(duration: 0.15), value: isPressed)
Image("OmiLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.scaleEffect(isPressed ? 1.05 : 1.0)
.animation(.easeInOut(duration: 0.15), value: isPressed)
}
}
.buttonStyle(PlainButtonStyle())
.accessibilityLabel(
viewModel.isRecording
? Text("watch.accessibility.stopRecording")
: Text("watch.accessibility.startRecording")
)
Spacer()
Group {
if viewModel.isRecording, let startedAt = viewModel.recordingStartedAt {
if presentationController.phase.showsRecordingRipple {
Text("Listening")
.font(.system(size: 16, weight: .medium))
.accessibilityLabel(Text("watch.accessibility.recordingInProgress"))
} else {
Text(startedAt, style: .timer)
.font(.system(size: 22, weight: .semibold, design: .rounded))
.monospacedDigit()
.accessibilityLabel(Text("watch.accessibility.elapsedRecordingTime"))
.accessibilityValue(Text(startedAt, style: .timer))
}
} else {
Text("Tap to Record")
.font(.system(size: 16, weight: .medium))
}
}
.foregroundColor(.white)
Spacer()
.frame(height: 20)
}
}
}
.task(id: viewModel.recordingStartedAt) {
await presentationController.update(
isRecording: viewModel.isRecording,
startedAt: viewModel.recordingStartedAt
)
}
}
}
private struct RecordingRippleView: View {
@State private var rippleScale: CGFloat = 1
@State private var rippleOpacity: Double = 0.8
var body: some View {
ZStack {
ForEach(0..<3, id: \.self) { index in
Circle()
.stroke(Color.white.opacity(0.3), lineWidth: 2)
.frame(width: 100, height: 100)
.scaleEffect(rippleScale)
.opacity(rippleOpacity)
.animation(
.easeOut(duration: 1.5)
.repeatForever(autoreverses: false)
.delay(Double(index) * 0.3),
value: rippleScale
)
}
}
.onAppear {
rippleScale = 2.5
rippleOpacity = 0
}
}
}
#if canImport(WatchConnectivity)
#Preview {
WatchRecorderView(viewModel: WatchAudioRecorderViewModel())
}
#endif