From a61ba25590c1e8810e205249dd4011384e62cdfb Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Tue, 11 Aug 2026 18:47:03 +0000 Subject: [PATCH] Test de VMA guide au poignet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pendant un test, le telephone est dans une poche : la montre est le seul ecran consultable en courant, et c'est elle qui porte deja vitesse, distance et FC via WorkoutManager. Deux protocoles. Paliers d'une minute de 8 km/h par pas de 0,5, avec haptique au changement et l'ecart a l'allure cible affiche — la vitesse brute demanderait un calcul mental a chaque foulee. Ou six minutes, la distance parcourue donnant directement la VMA. ⚠️ AUCUN calcul cote montre : elle transmet des mesures (paliers tenus, distance, FC max), le serveur calcule. Deux implementations d'une meme formule divergent toujours. Le POST passe par CoachRoutineBridge, qui a deja l'abonnement NotificationCenter, la base d'API et le cookie — un fichier de plus devrait etre cable a la main dans le pbxproj. ⚠️ Les paliers avancent sur `elapsedSec` de HKLiveWorkoutBuilder, PAS sur un compteur accumule au fil du Timer de la vue : ce Timer ne tourne pas quand l'ecran s'eteint, c'est-a-dire des que le poignet retombe — soit pendant l'essentiel du test. Meme famille de piege que la Live Activity fantome. L'envoi reprend la livraison differee de la routine : un test se fait dehors, l'iPhone peut etre hors de portee, et refaire un test maximal parce qu'un message s'est perdu n'est pas acceptable. Si l'envoi echoue, les chiffres restent affiches pour une saisie manuelle. VmaTestView.swift est un fichier NOUVEAU : cable dans le pbxproj (4 entrees, sauvegarde project.pbxproj.bak-vma). Verification : `grep -c "VmaTestView.swift in Sources"` doit rendre 2 (declaration + phase de compilation = 1 cible). ⚠️ NON COMPILE — a builder au prochain passage sur le Mac. Co-Authored-By: Claude Opus 5 (1M context) --- ios/App/App.xcodeproj/project.pbxproj | 4 + ios/App/App.xcodeproj/project.pbxproj.bak-vma | 1075 +++++++++++++++++ ios/App/App/CoachLiveBridge.swift | 8 + ios/App/App/CoachRoutineBridge.swift | 60 + ios/App/CoachWatch/ConnectivityManager.swift | 21 + ios/App/CoachWatch/ContentView.swift | 12 + ios/App/CoachWatch/VmaTestView.swift | 255 ++++ 7 files changed, 1435 insertions(+) create mode 100644 ios/App/App.xcodeproj/project.pbxproj.bak-vma create mode 100644 ios/App/CoachWatch/VmaTestView.swift diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 175437c..f13883b 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 548F8BFA06417B35474BD69E /* CoachLiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340B99D5EA4DB985D7DFDA10 /* CoachLiveView.swift */; }; 60061E60061E60061E060001 /* CoachGoogleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60061E60061E60061E060002 /* CoachGoogleAuth.swift */; }; 6442A95576AFC77377D0DC61 /* RoutineGuidedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */; }; + A1B2C3D4E5F60718293A4B5D /* VmaTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5C /* VmaTestView.swift */; }; 694B2F69591CAA85CE5DEC3A /* CoachLiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */; }; 79A94FD334CC9EC24D1FB700 /* CoachWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0276928E86E785EEA434057C /* CoachWidgets.swift */; }; 880A76D1302B601000F582D5 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 880A76D0302B601000F582D5 /* WidgetKit.framework */; }; @@ -167,6 +168,7 @@ B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = ""; }; C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RoutineGuidedView.swift; sourceTree = ""; }; + A1B2C3D4E5F60718293A4B5C /* VmaTestView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VmaTestView.swift; sourceTree = ""; }; D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = ""; }; D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = ""; }; D61D7B2F5AFEF834423E07AB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; @@ -319,6 +321,7 @@ A420239FC6D065C8A599B61C /* ConnectivityManager.swift */, AA5C5902EFB7F60563678E46 /* RoutineView.swift */, C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */, + A1B2C3D4E5F60718293A4B5C /* VmaTestView.swift */, 969DB880395BDEB107C9F2FE /* RoutineStore.swift */, 8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */, 3342ACCE085010828CF63565 /* Assets.xcassets */, @@ -574,6 +577,7 @@ B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */, 95D0478A499056FE922211EA /* RoutineView.swift in Sources */, 6442A95576AFC77377D0DC61 /* RoutineGuidedView.swift in Sources */, + A1B2C3D4E5F60718293A4B5D /* VmaTestView.swift in Sources */, 23F47FD81F646516727B5317 /* RoutineStore.swift in Sources */, 436442D6BF54833C59D9E668 /* WorkoutManager.swift in Sources */, ); diff --git a/ios/App/App.xcodeproj/project.pbxproj.bak-vma b/ios/App/App.xcodeproj/project.pbxproj.bak-vma new file mode 100644 index 0000000..175437c --- /dev/null +++ b/ios/App/App.xcodeproj/project.pbxproj.bak-vma @@ -0,0 +1,1075 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 70; + objects = { + +/* Begin PBXBuildFile section */ + 01468471ECA28EA66AFE2D6C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6C76F68679844EC72178F /* Foundation.framework */; }; + 0318E7E610501FC1F958251B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3342ACCE085010828CF63565 /* Assets.xcassets */; }; + 19F735674999FD63AB1C566F /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; }; + 1B690F0D16AB76A6854CA1F7 /* CoachLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */; }; + 21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */; }; + 23F47FD81F646516727B5317 /* RoutineStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969DB880395BDEB107C9F2FE /* RoutineStore.swift */; }; + 290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06CDFF5B4F7852B6329862 /* ContentView.swift */; }; + 2BAD3233026A4DFEEA02EAE8 /* CoachWidgetBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11ECAD3DB1486DE606285921 /* CoachWidgetBridge.swift */; }; + 2F86A358498667B66E5EFC60 /* CoachRoutineBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97BC6E07E6984342AE95C312 /* CoachRoutineBridge.swift */; }; + 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; + 3E6740C521EF14892687BCF7 /* CoachWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 2517374227A4072A4D6AFC15 /* CoachWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 436442D6BF54833C59D9E668 /* WorkoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */; }; + 4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; }; + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; + 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; + 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + 543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */; }; + 548F8BFA06417B35474BD69E /* CoachLiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340B99D5EA4DB985D7DFDA10 /* CoachLiveView.swift */; }; + 60061E60061E60061E060001 /* CoachGoogleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60061E60061E60061E060002 /* CoachGoogleAuth.swift */; }; + 6442A95576AFC77377D0DC61 /* RoutineGuidedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */; }; + 694B2F69591CAA85CE5DEC3A /* CoachLiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */; }; + 79A94FD334CC9EC24D1FB700 /* CoachWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0276928E86E785EEA434057C /* CoachWidgets.swift */; }; + 880A76D1302B601000F582D5 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 880A76D0302B601000F582D5 /* WidgetKit.framework */; }; + 880A76D3302B601000F582D5 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 880A76D2302B601000F582D5 /* SwiftUI.framework */; }; + 880A76DE302B601100F582D5 /* CoachWatchWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 880A76CF302B601000F582D5 /* CoachWatchWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 880A76E6302B62FB00F582D5 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; }; + 88924CCE2FD2A733002F3A80 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 88924CCD2FD2A733002F3A80 /* GoogleSignIn */; }; + 88ABE0043024C45A002636FD /* CoachQuickLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0023024C45A002636FD /* CoachQuickLog.swift */; }; + 88ABE0053024C45A002636FD /* CoachQuickSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0033024C45A002636FD /* CoachQuickSync.swift */; }; + 88ABE0073024C498002636FD /* CoachQuickLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0023024C45A002636FD /* CoachQuickLog.swift */; }; + 88ABE0093024C51D002636FD /* CoachQuickWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0083024C51D002636FD /* CoachQuickWidget.swift */; }; + 88D1DD5E302B46B000B9BA42 /* CoachAuth.swift.example in Resources */ = {isa = PBXBuildFile; fileRef = 88D1DD5D302B46B000B9BA42 /* CoachAuth.swift.example */; }; + 88D1DD5F302B46B000B9BA42 /* CoachAuth.swift.example in Resources */ = {isa = PBXBuildFile; fileRef = 88D1DD5D302B46B000B9BA42 /* CoachAuth.swift.example */; }; + 88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */; }; + 95D0478A499056FE922211EA /* RoutineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C5902EFB7F60563678E46 /* RoutineView.swift */; }; + 97116C31D4CC40F71A6A0FE7 /* LiveStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6030BD533EBC2D1D26DB9479 /* LiveStore.swift */; }; + 97600B69ADF2F19B56E79621 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; }; + 9BED4C04C38FCB8634CE2A94 /* CoachLiveActivity.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9E88D51C0B0255D24274DACC /* CoachLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BA75EEC3CBF1F46A9028C66 /* CoachLiveActivityBundle.swift */; }; + A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */; }; + B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A420239FC6D065C8A599B61C /* ConnectivityManager.swift */; }; + B130613187FE0661DDF636C9 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; }; + C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4A07811751FB78AA0002 /* CoachAuth.swift */; }; + D1384CFA4960060CDBF05FC3 /* CoachLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */; }; + D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */; }; + E1E8BD7CF9C19E56CE0F57C4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61D7B2F5AFEF834423E07AB /* Foundation.framework */; }; + E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */; }; + E547F675B2F61C3475D2A787 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */; }; + F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3063F4D3E3903D2EB4956B44 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 504EC2FC1FED79650016851F /* Project object */; + proxyType = 1; + remoteGlobalIDString = D5BF41648D334DC24770B9D2; + remoteInfo = CoachWatch; + }; + 880A76DC302B601100F582D5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 504EC2FC1FED79650016851F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 880A76CE302B601000F582D5; + remoteInfo = CoachWatchWidgetsExtension; + }; + 9D160400D2AE8723B1112F2F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 504EC2FC1FED79650016851F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 28FB3582F417B2F3C6BFC5D1; + remoteInfo = CoachLiveActivity; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 880A76DF302B601100F582D5 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 880A76DE302B601100F582D5 /* CoachWatchWidgetsExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + C4916F81E61FABAAD62EE4F9 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 9BED4C04C38FCB8634CE2A94 /* CoachLiveActivity.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + F50D9A7DFABB52EE96BB93D6 /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 3E6740C521EF14892687BCF7 /* CoachWatch.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0276928E86E785EEA434057C /* CoachWidgets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWidgets.swift; sourceTree = ""; }; + 0EB6C76F68679844EC72178F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS11.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 11ECAD3DB1486DE606285921 /* CoachWidgetBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWidgetBridge.swift; sourceTree = ""; }; + 1BA75EEC3CBF1F46A9028C66 /* CoachLiveActivityBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveActivityBundle.swift; sourceTree = ""; }; + 2517374227A4072A4D6AFC15 /* CoachWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoachWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; + 3342ACCE085010828CF63565 /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 340B99D5EA4DB985D7DFDA10 /* CoachLiveView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveView.swift; sourceTree = ""; }; + 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveActivityAttributes.swift; sourceTree = ""; }; + 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; + 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + 60061E60061E60061E060002 /* CoachGoogleAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachGoogleAuth.swift; sourceTree = ""; }; + 6030BD533EBC2D1D26DB9479 /* LiveStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiveStore.swift; sourceTree = ""; }; + 82122ACF7E5A608AD1D55D24 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 880A76CF302B601000F582D5 /* CoachWatchWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CoachWatchWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 880A76D0302B601000F582D5 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 880A76D2302B601000F582D5 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 880A76E7302B634400F582D5 /* CoachWatchWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CoachWatchWidgetsExtension.entitlements; sourceTree = ""; }; + 884A1F9D3020E3C100457184 /* CoachLiveActivity.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CoachLiveActivity.entitlements; sourceTree = ""; }; + 88A837392FAA741C00DDD144 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; + 88ABE0023024C45A002636FD /* CoachQuickLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachQuickLog.swift; sourceTree = ""; }; + 88ABE0033024C45A002636FD /* CoachQuickSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachQuickSync.swift; sourceTree = ""; }; + 88ABE0083024C51D002636FD /* CoachQuickWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachQuickWidget.swift; sourceTree = ""; }; + 88D1DD5D302B46B000B9BA42 /* CoachAuth.swift.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = CoachAuth.swift.example; sourceTree = ""; }; + 88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutObserver.swift; sourceTree = ""; }; + 8A06CDFF5B4F7852B6329862 /* ContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WorkoutManager.swift; sourceTree = ""; }; + 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; + 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWidgetSnapshot.swift; sourceTree = ""; }; + 969DB880395BDEB107C9F2FE /* RoutineStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RoutineStore.swift; sourceTree = ""; }; + 97BC6E07E6984342AE95C312 /* CoachRoutineBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachRoutineBridge.swift; sourceTree = ""; }; + 9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CoachLiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + A420239FC6D065C8A599B61C /* ConnectivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = ""; }; + A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAppleAuth.swift; sourceTree = ""; }; + A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveBridge.swift; sourceTree = ""; }; + AA5C5902EFB7F60563678E46 /* RoutineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RoutineView.swift; sourceTree = ""; }; + B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; + C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = ""; }; + C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RoutineGuidedView.swift; sourceTree = ""; }; + D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = ""; }; + D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = ""; }; + D61D7B2F5AFEF834423E07AB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveActivityWidget.swift; sourceTree = ""; }; + F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachHealthRoute.swift; sourceTree = ""; }; + F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWatchApp.swift; sourceTree = ""; }; + FBFCA969A1B083CD3178D9B2 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 880A76E2302B601100F582D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 880A76CE302B601000F582D5 /* CoachWatchWidgetsExtension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 880A76D4302B601000F582D5 /* CoachWatchWidgets */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (880A76E2302B601100F582D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = CoachWatchWidgets; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 504EC3011FED79650016851F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */, + 88924CCE2FD2A733002F3A80 /* GoogleSignIn in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 85FFFE3727FD44280013FE19 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 01468471ECA28EA66AFE2D6C /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 880A76CC302B601000F582D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 880A76D3302B601000F582D5 /* SwiftUI.framework in Frameworks */, + 880A76D1302B601000F582D5 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D8B443A150F5A7E1C135ABAA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E1E8BD7CF9C19E56CE0F57C4 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3E58B60E9B4F1CA14B5EC667 /* watchOS */ = { + isa = PBXGroup; + children = ( + 0EB6C76F68679844EC72178F /* Foundation.framework */, + ); + name = watchOS; + sourceTree = ""; + }; + 460F58BF290358F572689016 /* CoachLiveActivity */ = { + isa = PBXGroup; + children = ( + 88ABE0083024C51D002636FD /* CoachQuickWidget.swift */, + 884A1F9D3020E3C100457184 /* CoachLiveActivity.entitlements */, + 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */, + EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */, + 1BA75EEC3CBF1F46A9028C66 /* CoachLiveActivityBundle.swift */, + 0276928E86E785EEA434057C /* CoachWidgets.swift */, + FBFCA969A1B083CD3178D9B2 /* Info.plist */, + ); + path = CoachLiveActivity; + sourceTree = ""; + }; + 504EC2FB1FED79650016851F = { + isa = PBXGroup; + children = ( + 880A76E7302B634400F582D5 /* CoachWatchWidgetsExtension.entitlements */, + 958DCC722DB07C7200EA8C5F /* debug.xcconfig */, + 504EC3061FED79650016851F /* App */, + 880A76D4302B601000F582D5 /* CoachWatchWidgets */, + 504EC3051FED79650016851F /* Products */, + B91ACF72653FD495148698CF /* Frameworks */, + 5A8EA0CF92316E437E9053A0 /* CoachWatch */, + 460F58BF290358F572689016 /* CoachLiveActivity */, + ); + sourceTree = ""; + }; + 504EC3051FED79650016851F /* Products */ = { + isa = PBXGroup; + children = ( + 504EC3041FED79650016851F /* App.app */, + 2517374227A4072A4D6AFC15 /* CoachWatch.app */, + 9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */, + 880A76CF302B601000F582D5 /* CoachWatchWidgetsExtension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 504EC3061FED79650016851F /* App */ = { + isa = PBXGroup; + children = ( + 88ABE0023024C45A002636FD /* CoachQuickLog.swift */, + 88ABE0033024C45A002636FD /* CoachQuickSync.swift */, + 88A837392FAA741C00DDD144 /* App.entitlements */, + 88D1DD5D302B46B000B9BA42 /* CoachAuth.swift.example */, + 88F28EB02FBB330E00E8306E /* CoachWorkoutObserver.swift */, + 50379B222058CBB4000EE86E /* capacitor.config.json */, + 504EC3071FED79650016851F /* AppDelegate.swift */, + C0AC4A07811751FB78AA0002 /* CoachAuth.swift */, + D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */, + F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */, + A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */, + 60061E60061E60061E060002 /* CoachGoogleAuth.swift */, + E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */, + 504EC30B1FED79650016851F /* Main.storyboard */, + 504EC30E1FED79650016851F /* Assets.xcassets */, + 504EC3101FED79650016851F /* LaunchScreen.storyboard */, + 504EC3131FED79650016851F /* Info.plist */, + 2FAD9762203C412B000D30F8 /* config.xml */, + 50B271D01FEDC1A000F3C39B /* public */, + A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */, + 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */, + 11ECAD3DB1486DE606285921 /* CoachWidgetBridge.swift */, + 97BC6E07E6984342AE95C312 /* CoachRoutineBridge.swift */, + 6030BD533EBC2D1D26DB9479 /* LiveStore.swift */, + 340B99D5EA4DB985D7DFDA10 /* CoachLiveView.swift */, + B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */, + ); + path = App; + sourceTree = ""; + }; + 5A8EA0CF92316E437E9053A0 /* CoachWatch */ = { + isa = PBXGroup; + children = ( + F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */, + 8A06CDFF5B4F7852B6329862 /* ContentView.swift */, + 82122ACF7E5A608AD1D55D24 /* Info.plist */, + D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */, + A420239FC6D065C8A599B61C /* ConnectivityManager.swift */, + AA5C5902EFB7F60563678E46 /* RoutineView.swift */, + C2D78A1BF31D30C5AEC24E7C /* RoutineGuidedView.swift */, + 969DB880395BDEB107C9F2FE /* RoutineStore.swift */, + 8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */, + 3342ACCE085010828CF63565 /* Assets.xcassets */, + ); + path = CoachWatch; + sourceTree = ""; + }; + 7497026ECEFF81CCD11C289D /* iOS */ = { + isa = PBXGroup; + children = ( + D61D7B2F5AFEF834423E07AB /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; + B91ACF72653FD495148698CF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3E58B60E9B4F1CA14B5EC667 /* watchOS */, + 7497026ECEFF81CCD11C289D /* iOS */, + 880A76D0302B601000F582D5 /* WidgetKit.framework */, + 880A76D2302B601000F582D5 /* SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 28FB3582F417B2F3C6BFC5D1 /* CoachLiveActivity */ = { + isa = PBXNativeTarget; + buildConfigurationList = C2671D2741138A4A55CE1ED0 /* Build configuration list for PBXNativeTarget "CoachLiveActivity" */; + buildPhases = ( + 2D25180D64B3326AE6F9D91B /* Sources */, + D8B443A150F5A7E1C135ABAA /* Frameworks */, + 411CB19BB5DB127758A86A2D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CoachLiveActivity; + productName = CoachLiveActivity; + productReference = 9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 504EC3031FED79650016851F /* App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */; + buildPhases = ( + 504EC3001FED79650016851F /* Sources */, + 504EC3011FED79650016851F /* Frameworks */, + 504EC3021FED79650016851F /* Resources */, + F50D9A7DFABB52EE96BB93D6 /* Embed Watch Content */, + C4916F81E61FABAAD62EE4F9 /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + EEEAB45810829EE8B3C4976E /* PBXTargetDependency */, + BACE810C3CD85E302BBFEC84 /* PBXTargetDependency */, + ); + name = App; + packageProductDependencies = ( + 4D22ABE82AF431CB00220026 /* CapApp-SPM */, + 88924CCD2FD2A733002F3A80 /* GoogleSignIn */, + ); + productName = App; + productReference = 504EC3041FED79650016851F /* App.app */; + productType = "com.apple.product-type.application"; + }; + 880A76CE302B601000F582D5 /* CoachWatchWidgetsExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 880A76E3302B601100F582D5 /* Build configuration list for PBXNativeTarget "CoachWatchWidgetsExtension" */; + buildPhases = ( + 880A76CB302B601000F582D5 /* Sources */, + 880A76CC302B601000F582D5 /* Frameworks */, + 880A76CD302B601000F582D5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 880A76D4302B601000F582D5 /* CoachWatchWidgets */, + ); + name = CoachWatchWidgetsExtension; + packageProductDependencies = ( + ); + productName = CoachWatchWidgetsExtension; + productReference = 880A76CF302B601000F582D5 /* CoachWatchWidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + D5BF41648D334DC24770B9D2 /* CoachWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA2A9602402AA384726BFF97 /* Build configuration list for PBXNativeTarget "CoachWatch" */; + buildPhases = ( + E1B262849E79F8569984E173 /* Sources */, + 85FFFE3727FD44280013FE19 /* Frameworks */, + CFD58983FA62853BAD4DC405 /* Resources */, + 880A76DF302B601100F582D5 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 880A76DD302B601100F582D5 /* PBXTargetDependency */, + ); + name = CoachWatch; + productName = CoachWatch; + productReference = 2517374227A4072A4D6AFC15 /* CoachWatch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 504EC2FC1FED79650016851F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 2660; + LastUpgradeCheck = 2650; + TargetAttributes = { + 504EC3031FED79650016851F = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + }; + 880A76CE302B601000F582D5 = { + CreatedOnToolsVersion = 26.6; + }; + }; + }; + buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 504EC2FB1FED79650016851F; + packageReferences = ( + D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */, + 88924CCC2FD2A663002F3A80 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, + ); + productRefGroup = 504EC3051FED79650016851F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 504EC3031FED79650016851F /* App */, + D5BF41648D334DC24770B9D2 /* CoachWatch */, + 28FB3582F417B2F3C6BFC5D1 /* CoachLiveActivity */, + 880A76CE302B601000F582D5 /* CoachWatchWidgetsExtension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 411CB19BB5DB127758A86A2D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88D1DD5F302B46B000B9BA42 /* CoachAuth.swift.example in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 504EC3021FED79650016851F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88D1DD5E302B46B000B9BA42 /* CoachAuth.swift.example in Resources */, + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */, + 50B271D11FEDC1A000F3C39B /* public in Resources */, + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, + 504EC30D1FED79650016851F /* Main.storyboard in Resources */, + 2FAD9763203C412B000D30F8 /* config.xml in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 880A76CD302B601000F582D5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CFD58983FA62853BAD4DC405 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0318E7E610501FC1F958251B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2D25180D64B3326AE6F9D91B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 79A94FD334CC9EC24D1FB700 /* CoachWidgets.swift in Sources */, + 19F735674999FD63AB1C566F /* CoachWidgetSnapshot.swift in Sources */, + 1B690F0D16AB76A6854CA1F7 /* CoachLiveActivityAttributes.swift in Sources */, + 694B2F69591CAA85CE5DEC3A /* CoachLiveActivityWidget.swift in Sources */, + 9E88D51C0B0255D24274DACC /* CoachLiveActivityBundle.swift in Sources */, + 88ABE0073024C498002636FD /* CoachQuickLog.swift in Sources */, + 88ABE0093024C51D002636FD /* CoachQuickWidget.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 504EC3001FED79650016851F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B130613187FE0661DDF636C9 /* CoachWidgetSnapshot.swift in Sources */, + 2BAD3233026A4DFEEA02EAE8 /* CoachWidgetBridge.swift in Sources */, + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */, + D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */, + 88ABE0043024C45A002636FD /* CoachQuickLog.swift in Sources */, + 88ABE0053024C45A002636FD /* CoachQuickSync.swift in Sources */, + F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */, + A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */, + 60061E60061E60061E060001 /* CoachGoogleAuth.swift in Sources */, + 88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */, + E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */, + 21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */, + 2F86A358498667B66E5EFC60 /* CoachRoutineBridge.swift in Sources */, + 97116C31D4CC40F71A6A0FE7 /* LiveStore.swift in Sources */, + 548F8BFA06417B35474BD69E /* CoachLiveView.swift in Sources */, + D1384CFA4960060CDBF05FC3 /* CoachLiveActivityAttributes.swift in Sources */, + E547F675B2F61C3475D2A787 /* LiveActivityManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 880A76CB302B601000F582D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 880A76E6302B62FB00F582D5 /* CoachWidgetSnapshot.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E1B262849E79F8569984E173 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97600B69ADF2F19B56E79621 /* CoachWidgetSnapshot.swift in Sources */, + 543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */, + 290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */, + B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */, + 95D0478A499056FE922211EA /* RoutineView.swift in Sources */, + 6442A95576AFC77377D0DC61 /* RoutineGuidedView.swift in Sources */, + 23F47FD81F646516727B5317 /* RoutineStore.swift in Sources */, + 436442D6BF54833C59D9E668 /* WorkoutManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 880A76DD302B601100F582D5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 880A76CE302B601000F582D5 /* CoachWatchWidgetsExtension */; + targetProxy = 880A76DC302B601100F582D5 /* PBXContainerItemProxy */; + }; + BACE810C3CD85E302BBFEC84 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = CoachLiveActivity; + target = 28FB3582F417B2F3C6BFC5D1 /* CoachLiveActivity */; + targetProxy = 9D160400D2AE8723B1112F2F /* PBXContainerItemProxy */; + }; + EEEAB45810829EE8B3C4976E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = CoachWatch; + target = D5BF41648D334DC24770B9D2 /* CoachWatch */; + targetProxy = 3063F4D3E3903D2EB4956B44 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 504EC30B1FED79650016851F /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC30C1FED79650016851F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 504EC3101FED79650016851F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC3111FED79650016851F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 09CEF8C9131E8948450FEC27 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; + CODE_SIGN_ENTITLEMENTS = CoachWatch/CoachWatch.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = CoachWatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 11.0; + }; + name = Debug; + }; + 504EC3141FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = TZ2PVTRKNY; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 504EC3151FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = TZ2PVTRKNY; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 504EC3171FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = App/App.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + INFOPLIST_FILE = App/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2; + OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 504EC3181FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = App/App.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + INFOPLIST_FILE = App/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 6BD679ED799FBBFC9D3BE40C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; + CODE_SIGN_ENTITLEMENTS = CoachWatch/CoachWatch.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = CoachWatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 11.0; + }; + name = Release; + }; + 880A76E0302B601100F582D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = CoachWatchWidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CoachWatchWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CoachWatchWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.watchkitapp.CoachWatchWidgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.5; + }; + name = Debug; + }; + 880A76E1302B601100F582D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = CoachWatchWidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CoachWatchWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CoachWatchWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.2; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.watchkitapp.CoachWatchWidgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.5; + }; + name = Release; + }; + 90CE3C22F60C6432852DC1B1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_ENTITLEMENTS = CoachLiveActivity/CoachLiveActivity.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + DEVELOPMENT_TEAM = TZ2PVTRKNY; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = CoachLiveActivity/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.2; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.LiveActivity; + PRODUCT_NAME = CoachLiveActivity; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C7A0838F3DA0842EDE320050 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_ENTITLEMENTS = CoachLiveActivity/CoachLiveActivity.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 22; + DEVELOPMENT_TEAM = TZ2PVTRKNY; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = CoachLiveActivity/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.2; + PRODUCT_BUNDLE_IDENTIFIER = ch.hypnotruck.coach.LiveActivity; + PRODUCT_NAME = CoachLiveActivity; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3141FED79650016851F /* Debug */, + 504EC3151FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3171FED79650016851F /* Debug */, + 504EC3181FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 880A76E3302B601100F582D5 /* Build configuration list for PBXNativeTarget "CoachWatchWidgetsExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 880A76E0302B601100F582D5 /* Debug */, + 880A76E1302B601100F582D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C2671D2741138A4A55CE1ED0 /* Build configuration list for PBXNativeTarget "CoachLiveActivity" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C7A0838F3DA0842EDE320050 /* Release */, + 90CE3C22F60C6432852DC1B1 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FA2A9602402AA384726BFF97 /* Build configuration list for PBXNativeTarget "CoachWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6BD679ED799FBBFC9D3BE40C /* Release */, + 09CEF8C9131E8948450FEC27 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "CapApp-SPM"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 88924CCC2FD2A663002F3A80 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 4D22ABE82AF431CB00220026 /* CapApp-SPM */ = { + isa = XCSwiftPackageProductDependency; + package = D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */; + productName = "CapApp-SPM"; + }; + 88924CCD2FD2A733002F3A80 /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = 88924CCC2FD2A663002F3A80 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 504EC2FC1FED79650016851F /* Project object */; +} diff --git a/ios/App/App/CoachLiveBridge.swift b/ios/App/App/CoachLiveBridge.swift index 859f981..6103339 100644 --- a/ios/App/App/CoachLiveBridge.swift +++ b/ios/App/App/CoachLiveBridge.swift @@ -135,6 +135,14 @@ extension CoachLiveBridgePlugin: WCSessionDelegate { ) return true } + // Résultat d'un test de VMA fait au poignet. Même aiguillage que la + // routine : ce plugin est l'unique WCSession.delegate côté iPhone. + if message["vmaTest"] as? Bool == true { + NotificationCenter.default.post( + name: .coachVmaFromWatch, object: nil, userInfo: message + ) + return true + } // Pause / reprise : pilote le libellé du bouton de la Live Activity. if let paused = message["paused"] as? Bool { liveLog.info("état pause reçu de la Watch : \(paused, privacy: .public)") diff --git a/ios/App/App/CoachRoutineBridge.swift b/ios/App/App/CoachRoutineBridge.swift index 4e0363f..5bdc3f2 100644 --- a/ios/App/App/CoachRoutineBridge.swift +++ b/ios/App/App/CoachRoutineBridge.swift @@ -10,6 +10,8 @@ private let routineLog = Logger(subsystem: "ch.hypnotruck.coach", category: "rou /// message et le relaie ici plutôt que de l'émettre comme sample de séance. extension Notification.Name { static let coachRoutineFromWatch = Notification.Name("coachRoutineFromWatch") + /// Résultat d'un test de VMA fait au poignet, relayé par le même aiguillage. + static let coachVmaFromWatch = Notification.Name("coachVmaFromWatch") } /// Pont routine quotidienne iPhone ↔ Apple Watch. @@ -46,6 +48,16 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { name: .coachRoutineFromWatch, object: nil ) + // Le test de VMA vit dans ce plugin plutôt que dans un fichier dédié : + // il a déjà l'abonnement NotificationCenter, la base d'API et le cookie + // d'authentification, et un fichier de plus devrait être câblé à la main + // dans le pbxproj (la cible App n'est pas un dossier synchronisé). + NotificationCenter.default.addObserver( + self, + selector: #selector(handleVmaFromWatch(_:)), + name: .coachVmaFromWatch, + object: nil + ) } deinit { @@ -184,6 +196,54 @@ public class CoachRoutineBridgePlugin: CAPPlugin, CAPBridgedPlugin { } } + // MARK: - Test de VMA fait au poignet + + /// La montre n'envoie que des MESURES (paliers tenus, distance, FC max) ; + /// le calcul de la VMA se fait côté serveur, en un seul endroit. Deux + /// implémentations d'une même formule finissent toujours par diverger. + @objc private func handleVmaFromWatch(_ note: Notification) { + guard let info = note.userInfo as? [String: Any] else { return } + var body: [String: Any] = [:] + body["protocol"] = info["protocol"] as? String ?? "stages" + if let hr = (info["maxHr"] as? NSNumber)?.intValue, hr > 0 { body["max_hr"] = hr } + if let d = (info["distanceM"] as? NSNumber)?.doubleValue { body["distance_m"] = d } + if let s = (info["startKmh"] as? NSNumber)?.doubleValue { body["start_kmh"] = s } + if let s = (info["stepKmh"] as? NSNumber)?.doubleValue { body["step_kmh"] = s } + if let c = (info["completed"] as? NSNumber)?.intValue { body["completed"] = c } + if let r = (info["partialRatio"] as? NSNumber)?.doubleValue { body["partial_ratio"] = r } + body["note"] = "Test guidé depuis l'Apple Watch" + + postVma(body) { [weak self] ok, kmh in + routineLog.info("POST /api/vma : \(ok ? "ok" : "echec", privacy: .public)") + DispatchQueue.main.async { + self?.notifyListeners("vmaRecorded", data: ["ok": ok, "vma_kmh": kmh ?? 0]) + } + } + } + + private func postVma(_ body: [String: Any], + completion: @escaping (Bool, Double?) -> Void) { + guard let url = URL(string: "\(Self.apiBase)/api/vma") else { + completion(false, nil) + return + } + var req = URLRequest(url: url) + req.httpMethod = "POST" + req.setValue("application/json", forHTTPHeaderField: "Content-Type") + req.setValue("coach_web_token=\(CoachAuth.kCoachWebToken)", forHTTPHeaderField: "Cookie") + req.httpBody = try? JSONSerialization.data(withJSONObject: body) + req.timeoutInterval = 15 + URLSession.shared.dataTask(with: req) { data, response, _ in + let code = (response as? HTTPURLResponse)?.statusCode ?? -1 + guard (200..<300).contains(code), let data = data, + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + completion(false, nil) + return + } + completion(true, (json["vma_kmh"] as? NSNumber)?.doubleValue) + }.resume() + } + private static func todayISO() -> String { let fmt = DateFormatter() fmt.dateFormat = "yyyy-MM-dd" diff --git a/ios/App/CoachWatch/ConnectivityManager.swift b/ios/App/CoachWatch/ConnectivityManager.swift index ac9638b..76ff341 100644 --- a/ios/App/CoachWatch/ConnectivityManager.swift +++ b/ios/App/CoachWatch/ConnectivityManager.swift @@ -102,6 +102,27 @@ final class ConnectivityManager: NSObject, ObservableObject { return true } + /// Envoie le résultat brut d'un test de VMA (paliers tenus ou distance). + /// + /// Livraison différée garantie comme la routine : un test se fait dehors, + /// l'iPhone peut être hors de portée, et refaire un test maximal parce que + /// le message s'est perdu n'est pas acceptable. Le calcul de la VMA se fait + /// côté serveur — la montre ne transmet que des mesures. + @discardableResult + func sendVmaResult(_ payload: [String: Any]) -> Bool { + let session = WCSession.default + guard session.activationState == .activated else { return false } + if session.isReachable { + session.sendMessage(payload, replyHandler: nil) { _ in + session.transferUserInfo(payload) + } + } else { + session.transferUserInfo(payload) + } + connLog.info("résultat de test VMA envoyé à l'iPhone") + return true + } + /// Signale à l'iPhone que la séance est en pause / a repris, pour que le /// bouton de la Live Activity affiche le bon état. func sendPaused(_ paused: Bool) { diff --git a/ios/App/CoachWatch/ContentView.swift b/ios/App/CoachWatch/ContentView.swift index 28c47ba..638bc53 100644 --- a/ios/App/CoachWatch/ContentView.swift +++ b/ios/App/CoachWatch/ContentView.swift @@ -25,6 +25,7 @@ struct ContentView: View { struct ActivityPickerView: View { @EnvironmentObject private var workout: WorkoutManager @State private var showRoutine = false + @State private var showVma = false private struct Activity: Identifiable { let id = UUID() @@ -63,6 +64,14 @@ struct ActivityPickerView: View { } .tint(.cyan) + Button { + showVma = true + } label: { + Label("Test VMA", systemImage: "speedometer") + .frame(maxWidth: .infinity, alignment: .leading) + } + .tint(.orange) + Button { workout.startSimulation() } label: { @@ -83,6 +92,9 @@ struct ActivityPickerView: View { .sheet(isPresented: $showRoutine) { RoutineView() } + .sheet(isPresented: $showVma) { + VmaTestView() + } } } diff --git a/ios/App/CoachWatch/VmaTestView.swift b/ios/App/CoachWatch/VmaTestView.swift new file mode 100644 index 0000000..49d080a --- /dev/null +++ b/ios/App/CoachWatch/VmaTestView.swift @@ -0,0 +1,255 @@ +// VmaTestView.swift +// Test de VMA guidé au poignet, cible CoachWatch. +// +// Pourquoi au poignet plutôt que sur le téléphone : pendant un test, le +// téléphone est dans une poche. La montre est le seul écran consultable en +// courant, et c'est elle qui porte déjà la vitesse, la distance et la FC via +// WorkoutManager (HKLiveWorkoutBuilder). +// +// ⚠️ Aucun calcul de VMA ici : la vue mesure (paliers tenus, distance) et +// envoie ces mesures à l'iPhone, qui les poste à /api/vma. Le calcul vit dans +// web/vma.py, en un seul endroit — deux implémentations d'une même formule +// finissent toujours par diverger. +// +// ⚠️ Sous bêtabloquant, la FC ne sert PAS à mesurer : elle est affichée pour +// que l'utilisateur voie son plafond arriver, et transmise pour que le serveur +// signale un test interrompu par le cœur plutôt que par les jambes. +import SwiftUI +import HealthKit +import WatchKit + +struct VmaTestView: View { + enum Protocole: String, CaseIterable { + case stages = "Paliers" + case sixMin = "6 minutes" + } + + @StateObject private var workout = WorkoutManager.shared + @Environment(\.dismiss) private var dismiss + + // Paramètres du protocole à paliers. Départ volontairement bas : la marche + // rapide est à 6 km/h, un départ à 8 laisse une vraie marge de montée. + private let startKmh = 8.0 + private let stepKmh = 0.5 + private let stageSeconds = 60.0 + + @State private var proto: Protocole = .stages + @State private var started = false + @State private var finished = false + @State private var stageIndex = 0 // 0 = premier palier en cours + @State private var maxHr = 0.0 + @State private var sent = false + @State private var sendError: String? + + private let tick = Timer.publish(every: 0.2, on: .main, in: .common).autoconnect() + + private var targetKmh: Double { startKmh + stepKmh * Double(stageIndex) } + private var completedStages: Int { stageIndex } // paliers TERMINÉS + private var sixMinRemaining: Double { max(0, 360 - workout.elapsedSec) } + + /// Avancement dans le palier courant, dérivé de la durée de séance et NON + /// d'un compteur accumulé au fil du Timer. + /// + /// ⚠️ Le Timer de la vue ne tourne pas quand l'écran s'éteint — et il + /// s'éteint dès que le poignet retombe, c'est-à-dire pendant l'essentiel du + /// test. Un décompte accumulé dériverait donc de plusieurs secondes par + /// palier. `elapsedSec` vient de HKLiveWorkoutBuilder, qui continue de + /// courir en arrière-plan : c'est la seule horloge fiable ici. + private var stageElapsed: Double { + workout.elapsedSec.truncatingRemainder(dividingBy: stageSeconds) + } + + var body: some View { + Group { + if finished { resultView } + else if started { runningView } + else { setupView } + } + .onReceive(tick) { _ in onTick() } + .navigationTitle("Test VMA") + } + + // MARK: - Avant le départ + + private var setupView: some View { + ScrollView { + VStack(alignment: .leading, spacing: 10) { + Picker("Protocole", selection: $proto) { + ForEach(Protocole.allCases, id: \.self) { Text($0.rawValue).tag($0) } + } + .labelsHidden() + .frame(height: 60) + + Text(proto == .stages + ? "Paliers d'une minute, de 8 km/h par pas de 0,5. La montre vibre à chaque changement. Arrête-toi quand tu ne tiens plus l'allure." + : "Six minutes à allure maximale régulière, sur terrain plat. La distance parcourue donne la VMA.") + .font(.caption2) + .foregroundStyle(.secondary) + + Button("Démarrer") { start() } + .buttonStyle(.borderedProminent) + .tint(.orange) + } + .padding(.horizontal, 4) + } + } + + // MARK: - Pendant le test + + private var runningView: some View { + VStack(spacing: 6) { + if proto == .stages { + Text("Palier \(stageIndex + 1)") + .font(.caption2).foregroundStyle(.secondary) + Text(String(format: "%.1f", targetKmh)) + .font(.system(size: 44, weight: .semibold, design: .rounded)) + .monospacedDigit() + Text("km/h visés").font(.caption2).foregroundStyle(.secondary) + ProgressView(value: min(1, stageElapsed / stageSeconds)) + .tint(.orange) + // Écart à la cible : c'est la seule information qui aide à + // corriger en courant. La vitesse brute demanderait un calcul + // mental à chaque foulée. + Text(paceHint) + .font(.caption) + .foregroundStyle(paceOnTarget ? .green : .orange) + } else { + Text(timeString(sixMinRemaining)) + .font(.system(size: 44, weight: .semibold, design: .rounded)) + .monospacedDigit() + Text("restant").font(.caption2).foregroundStyle(.secondary) + Text(String(format: "%.0f m", workout.distanceMeters)) + .font(.title3).monospacedDigit() + } + + HStack(spacing: 10) { + Label(String(format: "%.0f", workout.heartRate), systemImage: "heart.fill") + .foregroundStyle(.red) + Label(String(format: "%.1f", workout.speedKmh), systemImage: "speedometer") + } + .font(.caption2) + .monospacedDigit() + + Button("J'arrête") { finish() } + .buttonStyle(.bordered) + .tint(.red) + } + .padding(.horizontal, 4) + } + + private var paceOnTarget: Bool { abs(workout.speedKmh - targetKmh) <= 0.4 } + + private var paceHint: String { + // ⚠️ La vitesse GPS est erratique les premières secondes d'acquisition : + // pas d'indication tant que la montre n'a rien de crédible. + guard workout.speedKmh > 1 else { return "acquisition…" } + let d = workout.speedKmh - targetKmh + if abs(d) <= 0.4 { return "allure tenue" } + return d < 0 ? String(format: "accélère (%.1f)", -d) : String(format: "ralentis (%.1f)", d) + } + + // MARK: - Après + + private var resultView: some View { + ScrollView { + VStack(spacing: 8) { + if proto == .stages { + Text("\(completedStages) paliers") + .font(.title3) + Text(completedStages > 0 + ? String(format: "dernier tenu : %.1f km/h", startKmh + stepKmh * Double(completedStages - 1)) + : "aucun palier terminé") + .font(.caption2).foregroundStyle(.secondary) + } else { + Text(String(format: "%.0f m", workout.distanceMeters)).font(.title3) + Text("en 6 minutes").font(.caption2).foregroundStyle(.secondary) + } + + if maxHr > 0 { + Text(String(format: "FC max %.0f bpm", maxHr)) + .font(.caption2).foregroundStyle(.secondary) + } + + if sent { + Label("Envoyé au téléphone", systemImage: "checkmark.circle.fill") + .font(.caption2).foregroundStyle(.green) + } else if let e = sendError { + // Échec d'envoi ≠ test perdu : les chiffres restent lisibles + // à l'écran, l'utilisateur peut les saisir dans l'app. + Text(e).font(.caption2).foregroundStyle(.orange) + Button("Réessayer") { send() }.buttonStyle(.bordered) + } + + Button("Terminer") { dismiss() }.buttonStyle(.bordered) + } + .padding(.horizontal, 4) + } + } + + // MARK: - Machine à états + + private func start() { + stageIndex = 0 + maxHr = 0 + sent = false + sendError = nil + finished = false + started = true + // startWorkout est async (@MainActor) : même appel que ContentView. + Task { await workout.startWorkout(activityType: .running, locationType: .outdoor) } + WKInterfaceDevice.current().play(.start) + } + + private func onTick() { + guard started, !finished else { return } + if workout.heartRate > maxHr { maxHr = workout.heartRate } + + if proto == .stages { + let reached = Int(workout.elapsedSec / stageSeconds) + if reached > stageIndex { + stageIndex = reached // le palier vient d'être TERMINÉ + WKInterfaceDevice.current().play(.notification) + } + } else if workout.elapsedSec >= 360 { + finish() + } + } + + private func finish() { + guard !finished else { return } + finished = true + started = false + workout.end() + WKInterfaceDevice.current().play(.success) + send() + } + + private func send() { + sendError = nil + var payload: [String: Any] = [ + "vmaTest": true, + "protocol": proto == .stages ? "stages" : "cooper6", + "maxHr": Int(maxHr.rounded()), + ] + if proto == .stages { + payload["startKmh"] = startKmh + payload["stepKmh"] = stepKmh + payload["completed"] = completedStages + // Fraction du palier en cours au moment de l'arrêt : le serveur + // n'en crédite que la moitié, et seulement au-delà de 50 %. + payload["partialRatio"] = min(1.0, stageElapsed / stageSeconds) + } else { + payload["distanceM"] = Int(workout.distanceMeters.rounded()) + } + if ConnectivityManager.shared.sendVmaResult(payload) { + sent = true + } else { + sendError = "iPhone injoignable — note les chiffres." + } + } + + private func timeString(_ s: Double) -> String { + let t = Int(s.rounded()) + return String(format: "%d:%02d", t / 60, t % 60) + } +}