@@ -30,25 +30,23 @@ enum ViewState: Equatable {
3030 return " Stand so that your whole body is inside the bounding box "
3131 case . introExercise( let exercise) :
3232 return " Now let's start the \( exercise. name) exercise "
33- case . exercise( let results, _) :
34- return " \( results. count) "
3533 default :
3634 return nil
3735 }
3836 }
39-
37+
4038 var features : [ QuickPose . Feature ] ? {
4139 switch self {
4240 case . introBoundingBox, . boundingBox:
43- return [ . inside( edgeInsets : QuickPose . RelativeCameraEdgeInsets ( top: 0.1 , left: 0.2 , bottom: 0.01 , right: 0.2 ) ) ]
44- default :
45- return nil
41+ return [ . inside( QuickPose . RelativeCameraEdgeInsets ( top: 0.1 , left: 0.2 , bottom: 0.01 , right: 0.2 ) ) ]
42+ default :
43+ return nil
4644 }
4745 }
4846}
4947
5048struct QuickPoseBasicView : View {
51- private var quickPose = QuickPose ( sdkKey: " 01H122Z3J6NY33V548V2D55K3J " ) // register for your free key at https://dev.quickpose.ai
49+ private var quickPose = QuickPose ( sdkKey: " YOUR SDK KEY " ) // register for your free key at https://dev.quickpose.ai
5250 @EnvironmentObject var viewModel : ViewModel
5351 @EnvironmentObject var sessionConfig : SessionConfig
5452
@@ -63,6 +61,8 @@ struct QuickPoseBasicView: View {
6361 @State private var countScale = 1.0
6462 @State private var boundingBoxMaskWidth = 0.0
6563
64+ static let synthesizer = AVSpeechSynthesizer ( )
65+
6666 var body : some View {
6767 GeometryReader { geometry in
6868 VStack {
@@ -74,62 +74,41 @@ struct QuickPoseBasicView: View {
7474 . edgesIgnoringSafeArea ( . all)
7575 . overlay ( ) {
7676 switch state {
77- case . startVolume:
78- VolumeChangeView ( )
79- . overlay ( alignment: . bottom) {
80- Button ( action: {
81- state = . instructions
82- } ) {
83- Text ( " Continue " ) . foregroundColor ( . white)
84- . padding ( )
85- . background ( Color ( " AccentColor " ) )
86- . cornerRadius ( 8 )
77+ case . startVolume:
78+ VolumeChangeView ( )
79+ . overlay ( alignment: . bottom) {
80+ Button ( action: {
81+ state = . instructions
82+ } ) {
83+ Text ( " Continue " ) . foregroundColor ( . white)
84+ . padding ( )
85+ . background ( Color ( " AccentColor " ) )
86+ . cornerRadius ( 8 )
8787 }
8888 }
89- case . instructions:
90- InstructionsView ( )
89+ case . instructions:
90+ InstructionsView ( )
9191 . overlay ( alignment: . bottom) {
9292 Button ( action: {
9393 state = . introBoundingBox
94+ Text2Speech ( text: state. speechPrompt!) . say ( )
9495 } ) {
9596 Text ( " Start Workout " ) . foregroundColor ( . white)
9697 . padding ( )
9798 . background ( Color ( " AccentColor " ) )
9899 . cornerRadius ( 8 )
99- }
100- }
101- case . introBoundingBox:
102- ZStack {
103- RoundedRectangle ( cornerRadius: 15 )
104- . stroke ( . red, lineWidth: 5 )
105- }
106- . frame ( width: geometry. size. width * 0.6 , height: geometry. size. height * 0.8 )
107- . padding ( . horizontal, ( geometry. size. width * 1 - 0.6 ) / 2 )
108-
109- case . boundingBox:
110- ZStack {
111- RoundedRectangle ( cornerRadius: 15 )
112- . stroke ( boundingBoxVisibility == 1 ? . green : . red, lineWidth: 5 )
113-
114- RoundedRectangle ( cornerRadius: 15 )
115- . fill ( . green. opacity ( 0.5 ) )
116- . mask ( alignment: . leading) {
117- Rectangle ( )
118- . frame ( width: geometry. size. width * 0.6 * boundingBoxMaskWidth)
119100 }
120- }
121- . frame ( width: geometry. size. width * 0.6 , height: geometry. size. height * 0.8 )
122- . padding ( . horizontal, ( geometry. size. width * 1 - 0.6 ) / 2 )
123-
124- case . results( let results) :
125- WorkoutResultsView ( sessionData: results)
126- . environmentObject ( viewModel)
127-
128- default :
129- EmptyView ( )
101+ }
102+
103+ case . results( let results) :
104+ WorkoutResultsView ( sessionData: results)
105+ . environmentObject ( viewModel)
106+
107+ default :
108+ EmptyView ( )
130109 }
131110 }
132-
111+
133112 . overlay ( alignment: . topTrailing) {
134113 Button ( action: {
135114 if case . results = state {
@@ -144,15 +123,15 @@ struct QuickPoseBasicView: View {
144123 }
145124 . padding ( )
146125 }
147-
126+
148127 . overlay ( alignment: . bottom) {
149128 if case . exercise( let results, let enterTime) = state {
150129 HStack {
151130 Text ( String ( results. count) + ( sessionConfig. useReps ? " \\ " + String( sessionConfig. nReps) : " " ) + " reps " )
152131 . font ( . system( size: 30 , weight: . semibold) )
153132 . padding ( 16 )
154133 . scaleEffect ( countScale)
155-
134+
156135 Text ( String ( format: " %.0f " , - enterTime. timeIntervalSinceNow) + ( !sessionConfig. useReps ? " \\ " + String( sessionConfig. nSeconds + sessionConfig. nMinutes * 60 ) : " " ) + " sec " )
157136 . font ( . system( size: 30 , weight: . semibold) )
158137 . padding ( 16 )
@@ -171,14 +150,8 @@ struct QuickPoseBasicView: View {
171150 }
172151 }
173152 }
174-
153+
175154 . onChange ( of: state) { _ in
176-
177- if let speechPrompt = state. speechPrompt {
178- let utterance = AVSpeechUtterance ( string: speechPrompt)
179- AVSpeechSynthesizer ( ) . speak ( utterance)
180- }
181-
182155 if case . results( let result) = state {
183156 let sessionDataDump = SessionDataModel ( exercise: sessionConfig. exercise. name, count: result. count, seconds: result. seconds, date: Date ( ) )
184157 appendToJson ( sessionData: sessionDataDump)
@@ -191,7 +164,7 @@ struct QuickPoseBasicView: View {
191164 quickPose. start ( features: state. features ?? sessionConfig. exercise. features, onFrame: { status, image, features, feedback, landmarks in
192165 overlayImage = image
193166 if case . success( _, _) = status {
194-
167+
195168 switch state {
196169 case . introBoundingBox:
197170
@@ -211,28 +184,30 @@ struct QuickPoseBasicView: View {
211184 }
212185 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 1 ) {
213186 state = . introExercise( sessionConfig. exercise)
187+ Text2Speech ( text: state. speechPrompt!) . say ( )
214188 }
215189 } else {
216190 state = . introBoundingBox
217191 }
218192 }
219193 }
220-
194+
221195 case . introExercise( _) :
222196 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
223197 state = . exercise( SessionData ( count: 0 , seconds: 0 ) , enterTime: Date ( ) )
224198 }
225199 case . exercise( _, let enterDate) :
226200 let secondsElapsed = Int ( - enterDate. timeIntervalSinceNow)
227-
201+
228202 if let feedback = feedback [ . fitness( . bicepCurls) ] {
229203 feedbackText = feedback. displayString
230204 } else {
231205 feedbackText = nil
232-
206+
233207 if case . fitness = sessionConfig. exercise. features. first, let result = features [ sessionConfig. exercise. features. first!] {
234208 _ = counter. count ( result. value) { newState in
235209 if !newState. isEntered {
210+ Text2Speech ( text: " \( counter. state. count) " ) . say ( )
236211 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
237212 withAnimation ( . easeInOut( duration: 0.1 ) ) {
238213 countScale = 2.0
@@ -247,7 +222,7 @@ struct QuickPoseBasicView: View {
247222 }
248223 }
249224 }
250-
225+
251226 let newResults = SessionData ( count: counter. state. count, seconds: secondsElapsed)
252227 state = . exercise( newResults, enterTime: enterDate) // refresh view for every updated second
253228 var hasFinished = false
@@ -256,9 +231,10 @@ struct QuickPoseBasicView: View {
256231 } else {
257232 hasFinished = secondsElapsed >= sessionConfig. nSeconds + sessionConfig. nMinutes * 60
258233 }
259-
234+
260235 if hasFinished {
261236 state = . results( newResults)
237+ quickPose. stop ( )
262238 }
263239 default :
264240 break
@@ -269,12 +245,11 @@ struct QuickPoseBasicView: View {
269245 } )
270246 }
271247 . onDisappear {
272- quickPose. stop ( )
273248 UIApplication . shared. isIdleTimerDisabled = false
274249 }
275250 }
276- . navigationBarBackButtonHidden ( true )
277- . toolbar ( . hidden, for: . tabBar)
251+ . navigationBarBackButtonHidden ( true )
252+ . toolbar ( . hidden, for: . tabBar)
278253 }
279254 }
280255}
0 commit comments