@@ -19,7 +19,7 @@ enum ViewState: Equatable {
1919 case startVolume
2020 case instructions
2121 case introBoundingBox
22- case boundingBox
22+ case boundingBox( enterTime : Date )
2323 case introExercise( Exercise )
2424 case exercise( SessionData , enterTime: Date )
2525 case results( SessionData )
@@ -34,15 +34,6 @@ enum ViewState: Equatable {
3434 return nil
3535 }
3636 }
37-
38- var features : [ QuickPose . Feature ] ? {
39- switch self {
40- case . introBoundingBox, . boundingBox:
41- return [ . inside( QuickPose . RelativeCameraEdgeInsets ( top: 0.1 , left: 0.2 , bottom: 0.01 , right: 0.2 ) ) ]
42- default :
43- return nil
44- }
45- }
4637}
4738
4839struct QuickPoseBasicView : View {
@@ -54,7 +45,6 @@ struct QuickPoseBasicView: View {
5445 @State private var feedbackText : String ? = nil
5546
5647 @State private var counter = QuickPoseThresholdCounter ( )
57- @State private var unchanged = QuickPoseDoubleUnchangedDetector ( similarDuration: 2 , leniency: 0 )
5848 @State private var state : ViewState = . startVolume
5949
6050 @State private var boundingBoxVisibility = 1.0
@@ -63,6 +53,13 @@ struct QuickPoseBasicView: View {
6353
6454 static let synthesizer = AVSpeechSynthesizer ( )
6555
56+ func canMoveFromBoundingBox( landmarks: QuickPose . Landmarks ) -> Bool {
57+ let xsInBox = landmarks. allLandmarksForBody ( ) . allSatisfy { 0.5 - ( 0.8 / 2 ) < $0. x && $0. x < 0.5 + ( 0.8 / 2 ) }
58+ let ysInBox = landmarks. allLandmarksForBody ( ) . allSatisfy { 0.5 - ( 0.9 / 2 ) < $0. y && $0. y < 0.5 + ( 0.9 / 2 ) }
59+
60+ return xsInBox && ysInBox
61+ }
62+
6663 var body : some View {
6764 GeometryReader { geometry in
6865 VStack {
@@ -99,6 +96,29 @@ struct QuickPoseBasicView: View {
9996 . cornerRadius ( 8 )
10097 }
10198 }
99+ case . introBoundingBox:
100+ ZStack {
101+ RoundedRectangle ( cornerRadius: 15 )
102+ . stroke ( . red, lineWidth: 5 )
103+ }
104+ . frame ( width: geometry. size. width * 0.8 , height: geometry. size. height * 0.9 )
105+ . padding ( . horizontal, ( geometry. size. width * 1 - 0.8 ) / 2 )
106+
107+ case . boundingBox:
108+ ZStack {
109+ RoundedRectangle ( cornerRadius: 15 )
110+ . stroke ( . green, lineWidth: 5 )
111+
112+ RoundedRectangle ( cornerRadius: 15 )
113+ . fill ( . green. opacity ( 0.5 ) )
114+ . mask ( alignment: . leading) {
115+ Rectangle ( )
116+ . frame ( width: geometry. size. width * 0.9 * boundingBoxMaskWidth)
117+ }
118+ }
119+ . frame ( width: geometry. size. width * 0.8 , height: geometry. size. height * 0.9 )
120+ . padding ( . horizontal, ( geometry. size. width * 1 - 0.8 ) / 2 )
121+
102122
103123 case . results( let results) :
104124 WorkoutResultsView ( sessionData: results)
@@ -115,6 +135,7 @@ struct QuickPoseBasicView: View {
115135 viewModel. popToRoot ( )
116136 } else {
117137 state = . results( SessionData ( count: counter. state. count, seconds: 0 ) )
138+ quickPose. stop ( )
118139 }
119140 } ) {
120141 Image ( systemName: " xmark.circle.fill " )
@@ -157,49 +178,39 @@ struct QuickPoseBasicView: View {
157178 appendToJson ( sessionData: sessionDataDump)
158179 }
159180
160- quickPose. update ( features: state . features ?? sessionConfig. exercise. features)
181+ quickPose. update ( features: sessionConfig. exercise. features)
161182 }
162183 . onAppear ( ) {
163184 UIApplication . shared. isIdleTimerDisabled = true
164- quickPose. start ( features: state . features ?? sessionConfig. exercise. features, onFrame: { status, image, features, feedback, landmarks in
185+ quickPose. start ( features: sessionConfig. exercise. features, onFrame: { status, image, features, feedback, landmarks in
165186 overlayImage = image
166187 if case . success( _, _) = status {
167188
168189 switch state {
169190 case . introBoundingBox:
170191
171- if let result = features . first ? . value , result . value == 1.0 {
172- unchanged . reset ( )
173- state = . boundingBox
192+ if let landmarks = landmarks , canMoveFromBoundingBox ( landmarks : landmarks ) {
193+ state = . boundingBox ( enterTime : Date ( ) )
194+ boundingBoxMaskWidth = 0
174195 }
175- case . boundingBox:
176- if let dictionaryEntry = features. first {
177- let result = dictionaryEntry. value
178- boundingBoxVisibility = result. value
179- unchanged. count ( result: result. value) {
180- if result. value == 1 { // been in for 2 seconds
181- boundingBoxMaskWidth = 0
182- withAnimation ( . easeInOut( duration: 1 ) ) {
183- boundingBoxMaskWidth = 1.0
184- }
185- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 1 ) {
186- state = . introExercise( sessionConfig. exercise)
187- Text2Speech ( text: state. speechPrompt!) . say ( )
188- }
189- } else {
190- state = . introBoundingBox
191- }
196+ case . boundingBox( let enterDate) :
197+ if let landmarks = landmarks, canMoveFromBoundingBox ( landmarks: landmarks) {
198+ let timeSinceInsideBBox = - enterDate. timeIntervalSinceNow
199+ boundingBoxMaskWidth = timeSinceInsideBBox / 2
200+ if timeSinceInsideBBox > 2 {
201+ state = . introExercise( sessionConfig. exercise)
192202 }
203+ } else {
204+ state = . introBoundingBox
193205 }
194-
195206 case . introExercise( _) :
196207 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
197208 state = . exercise( SessionData ( count: 0 , seconds: 0 ) , enterTime: Date ( ) )
198209 }
199210 case . exercise( _, let enterDate) :
200211 let secondsElapsed = Int ( - enterDate. timeIntervalSinceNow)
201212
202- if let feedback = feedback [ . fitness ( . bicepCurls ) ] {
213+ if let feedback = feedback [ sessionConfig . exercise . features . first! ] {
203214 feedbackText = feedback. displayString
204215 } else {
205216 feedbackText = nil
0 commit comments