From 594853ed048dacb412d1dacaec127afb760c3d7f Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Mon, 15 Jun 2026 07:37:27 +0200 Subject: [PATCH] Fix punteggio broadcast iOS: decodifica score e aggiornamento UI. ScoreStateDto non leggeva home_points con convertFromSnakeCase; ScoreController assegna sempre lo stato, aggiornamento ottimistico e refresh overlay. Co-authored-by: Cursor --- .../ios/MatchLiveTv.xcodeproj/project.pbxproj | 324 +++++++++--------- .../xcschemes/MatchLiveTv.xcscheme | 10 +- native/ios/MatchLiveTv/Data/API/ApiDtos.swift | 160 ++++----- .../Data/Scoring/ScoreController.swift | 71 ++-- .../Streaming/Preview/LivePreviewView.swift | 1 + .../Broadcast/BroadcastControlsOverlay.swift | 2 + .../UI/Broadcast/BroadcastScreen.swift | 16 +- .../MatchScoringRulesTests.swift | 2 +- .../ScoreActionDecodeTests.swift | 36 ++ .../ScoreControllerTests.swift | 28 ++ 10 files changed, 364 insertions(+), 286 deletions(-) create mode 100644 native/ios/MatchLiveTvTests/ScoreActionDecodeTests.swift create mode 100644 native/ios/MatchLiveTvTests/ScoreControllerTests.swift diff --git a/native/ios/MatchLiveTv.xcodeproj/project.pbxproj b/native/ios/MatchLiveTv.xcodeproj/project.pbxproj index 19109bc..015cb03 100644 --- a/native/ios/MatchLiveTv.xcodeproj/project.pbxproj +++ b/native/ios/MatchLiveTv.xcodeproj/project.pbxproj @@ -4,151 +4,155 @@ classes = {}; objectVersion = 60; objects = { - 7D502D6BFF584BE097F441D3 /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = ""; }; - E4D928872F8A45FAAFFF9F98 /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = ""; }; - 6FD1683477F947CB9C4B32A1 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = ""; }; - EE19BA76641848B8A6BD66BC /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = ""; }; - 674D4E0891724CCC9A530996 /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = ""; }; - F11E75C58605479AB24D6BAB /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = ""; }; - DF7B53044B9B40D5A23F7077 /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = ""; }; - 0FCD7AFD6FBA45B78CEC748E /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = ""; }; - 72E47488A16B4FA49C74FFA6 /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = ""; }; - 0E4851454F9C4D6C9A2BD946 /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = ""; }; - 13CFC4001F3641138379CF77 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = ""; }; - 416D9D0EE1414F0BA98EBC33 /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = ""; }; - C2342D3CB2C146799098D05F /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = ""; }; - 1C23153FA47E4274AA87A136 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = ""; }; - EC76170D739A414EA1F7261B /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = ""; }; - 100C4F0504D8457D94089ED8 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = ""; }; - C649A4EC60774AA7829C6AC4 /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = ""; }; - 616A0E114FE44B5782179A50 /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = ""; }; - F2DD2DA40C21451287827C77 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = ""; }; - DB66A0A42AD54E48929F5A9E /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = ""; }; - C991BA285472411FA0819BA6 /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = ""; }; - F2F260C1D8C44A7DBF26C88C /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = ""; }; - B91FAB0CE884433BAF90CB87 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = ""; }; - C981A70741504FED997BF8A2 /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = ""; }; - E9990A92AB0546B3B77B9A8B /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = ""; }; - A23145A497C4494AB5901FB4 /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = ""; }; - BF15F363ED4D4466B543226D /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = ""; }; - 423A409037C7408088DADC76 /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = ""; }; - 82B588D9B7844FDFB600D9DF /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = ""; }; - DD63A5AFF02A4D32902606FA /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = ""; }; - 50DD7B3855C74DA291286184 /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = ""; }; - CCB366E93584429F8EB8151D /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = ""; }; - E2F9E7C71E4844498BA95A56 /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = ""; }; - 024982C1D4A84A5389A7BCF6 /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = ""; }; - DAE19ACACBBA424AB844674E /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = ""; }; - 5342C88D12634E0894FD16E9 /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = ""; }; - 691D1DF008404C29AC5925BC /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = ""; }; - B56AB91CBBA842A183DD04C9 /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = ""; }; - 2801B8D684B440858B32ABB0 /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = ""; }; - 3C2797E56C8B4857A7FA2880 /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = ""; }; - 1D23B0386B9F487F8AA597D3 /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = ""; }; - 03F2473246C84F41BCD355C2 /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = ""; }; - 8CA4423F75F74FBFAE533080 /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = ""; }; - 854ABA49A0D547B9AEB910B4 /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = ""; }; - 3C3A2E7E20EB4F2DB5712369 /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = ""; }; - 42AD3B662EC5485082D5741F /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = ""; }; - EFB4B3068F404B3EB5A0FF1F /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = ""; }; - 5014F0820E6B40F6B34DC51A /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = ""; }; - CEE1BFA4FF76445790FF6560 /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = ""; }; - 3ECB25032A93474288F2FBA7 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = ""; }; - 2337F10A7DDA4E619DB37E82 /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = ""; }; - 833350AF23F6419A90E317F8 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = ""; }; - 316F29E465194CACB8947EAF /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = ""; }; - 1ADC984335A743A0ACDB409F /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = ""; }; - 116E4DA20AC043D9B6A9CE6D /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = ""; }; - 8E0E99517ABF4F99B08F3705 /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = ""; }; - D2A79ECE8FCA4DF88EC6DE4F /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = ""; }; - D1A38D2697E34F55B9240A8A /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = ""; }; - 0BEC22E0D4E242828468572E /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = ""; }; - EF2B79415AC6441CB2735D4D /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = ""; }; - 94E4E688B3154A7C9791E72C /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = ""; }; - E8B92AF2FADC4DE7B2A6D000 /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = ""; }; - F390918CBBD54AAEB0647D47 /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = ""; }; - F4D6CD1E29934B5D97D2F02E /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = ""; }; - 402D4D4F22DB49678F3E6FE7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = ""; }; - 044C9EE056CF444E9E98D3A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = ""; }; - D28494E6896F4E25A06AE11E /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D8E2905DD6E42479BC4C10E /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - DFA66CC46F824DCF9A20211C /* HaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 80244AADE65A4F05B29ECFB5; productName = HaishinKit; }; - 7CD46223721F489E8F2981D8 /* RTMPHaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 80244AADE65A4F05B29ECFB5; productName = RTMPHaishinKit; }; - EC02F9C46D3E4C1088D88F8A /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D502D6BFF584BE097F441D3 /* MatchLiveTvApp.swift */; }; - 8BDFE8DE8206441A8D094C2E /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D928872F8A45FAAFFF9F98 /* ApiInstant.swift */; }; - 7B8856F51ECA47EDAC0D0F9E /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1683477F947CB9C4B32A1 /* AppConfig.swift */; }; - AD61B798C741478E86CF0F21 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE19BA76641848B8A6BD66BC /* ColorHex.swift */; }; - BC44270E1DB54A9EA94AF25C /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674D4E0891724CCC9A530996 /* DeviceTelemetry.swift */; }; - 13268DA8F54B4835BD433426 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11E75C58605479AB24D6BAB /* MediaUrl.swift */; }; - 32D039D1037C4D4E826C5967 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF7B53044B9B40D5A23F7077 /* TokenStore.swift */; }; - 0713AAA3C14B4D76BCDFD2E0 /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FCD7AFD6FBA45B78CEC748E /* UserFacingError.swift */; }; - 54951F21D9EE48719961E4D2 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E47488A16B4FA49C74FFA6 /* ApiDtos.swift */; }; - 139F79D283B1472E9DBA09A0 /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4851454F9C4D6C9A2BD946 /* MatchLiveAPI.swift */; }; - 308C38EE4912489495844906 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CFC4001F3641138379CF77 /* AppContainer.swift */; }; - 51D03DC2F67F47E1ACE13C62 /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416D9D0EE1414F0BA98EBC33 /* ActionCableClient.swift */; }; - 06BCCCDE5EEE4420965A8AD9 /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2342D3CB2C146799098D05F /* SessionCableService.swift */; }; - F6AD6F9866FD401CB3183B65 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C23153FA47E4274AA87A136 /* AuthRepository.swift */; }; - 53F020EB95F248EAB7B4F160 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC76170D739A414EA1F7261B /* MatchRepository.swift */; }; - D3683FB90FFF4F6BACDC48AA /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100C4F0504D8457D94089ED8 /* MatchSessionLauncher.swift */; }; - 7BFD2334AFE748B6A1E41904 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C649A4EC60774AA7829C6AC4 /* ScoreRepository.swift */; }; - 243992F8BACE403D8901BC2D /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616A0E114FE44B5782179A50 /* SessionRepository.swift */; }; - 85768720CAA94956A4DAD2D1 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DD2DA40C21451287827C77 /* ScoreController.swift */; }; - C92AA21B3A384DC6AD6D6C3A /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66A0A42AD54E48929F5A9E /* WizardSessionHolder.swift */; }; - D11A107F06F749F8909C2513 /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C991BA285472411FA0819BA6 /* MatchHubFilter.swift */; }; - 9316E3BE6C394B90AC4C33BE /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F260C1D8C44A7DBF26C88C /* MatchScoringRules.swift */; }; - 53B9E9BC594B4E5BB6FA7C86 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = B91FAB0CE884433BAF90CB87 /* Models.swift */; }; - 1C9EB4524E2D466A877785CA /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C981A70741504FED997BF8A2 /* ScoreState.swift */; }; - 5D2EFF9E1A444394AE8C4325 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9990A92AB0546B3B77B9A8B /* BroadcastModels.swift */; }; - F40F95B9AF0846CBA1D1E1D3 /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A23145A497C4494AB5901FB4 /* LiveBroadcastCoordinator.swift */; }; - 9BCFF862205B4275A845EDB1 /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15F363ED4D4466B543226D /* LiveBroadcastEngine.swift */; }; - 125330B419C94948942351D7 /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A409037C7408088DADC76 /* CompactScoreboardElement.swift */; }; - 534D7ACB2128436CA844E53B /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B588D9B7844FDFB600D9DF /* OverlayCanvasRenderer.swift */; }; - 24EABF4A150546E4BC7F6C32 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD63A5AFF02A4D32902606FA /* OverlayLogoCache.swift */; }; - B4FC8CF476D44C32912FAD70 /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DD7B3855C74DA291286184 /* OverlayMappings.swift */; }; - 83D733D7DBEE4374B1374074 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB366E93584429F8EB8151D /* OverlayRenderer.swift */; }; - 7D60112C22DA49C1B57A6AED /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F9E7C71E4844498BA95A56 /* OverlayState.swift */; }; - DDC4E6E4080040D595B53D94 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024982C1D4A84A5389A7BCF6 /* ScoreboardElement.swift */; }; - 5D696DC51A334778BA8044A8 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE19ACACBBA424AB844674E /* SponsorElement.swift */; }; - 0C1792555BFD40EC84E093F5 /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5342C88D12634E0894FD16E9 /* WatermarkElement.swift */; }; - F52ED96B71C3448E9E6A93F6 /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691D1DF008404C29AC5925BC /* LivePreviewView.swift */; }; - 76BC7C34AB3E4B2DAEACFB89 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56AB91CBBA842A183DD04C9 /* BroadcastControlsOverlay.swift */; }; - 75E1FAC00E944A789974476C /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2801B8D684B440858B32ABB0 /* BroadcastScreen.swift */; }; - 88CD8E2D7D684BE6BBC9EF7D /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2797E56C8B4857A7FA2880 /* LiveScoreActions.swift */; }; - 10085C82FD204FA1A6727CE5 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D23B0386B9F487F8AA597D3 /* MatchLiveWordmark.swift */; }; - CFB328FB535446608F6AA4B7 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F2473246C84F41BCD355C2 /* MatchPrimaryButton.swift */; }; - 5A2D0215E89F4D0692B60FA8 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4423F75F74FBFAE533080 /* MatchScreenScaffold.swift */; }; - 4B8C519BF65F435C9AF58902 /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854ABA49A0D547B9AEB910B4 /* MatchSecondaryButton.swift */; }; - 41DD9BBB13FB4CFD87C940F8 /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3A2E7E20EB4F2DB5712369 /* MatchStatusBadge.swift */; }; - 4DBBB1020F2A4CD090740ECA /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42AD3B662EC5485082D5741F /* LoginScreen.swift */; }; - 25DB8C2F0DCD40A6BF38E7D3 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFB4B3068F404B3EB5A0FF1F /* MatchesScreen.swift */; }; - DCEF4209679541628926D50D /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5014F0820E6B40F6B34DC51A /* AppNavHost.swift */; }; - B5F397173C1D437DB12CA85B /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE1BFA4FF76445790FF6560 /* ModalRoutes.swift */; }; - 8CEE2254C44F49B48FE4E3CE /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ECB25032A93474288F2FBA7 /* Routes.swift */; }; - 8801885DF5CE4620BBFA1CBB /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2337F10A7DDA4E619DB37E82 /* BroadcastPermissions.swift */; }; - 06DEA551887C423C834E39ED /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 833350AF23F6419A90E317F8 /* SplashScreen.swift */; }; - 5F75111D35E84C22A6CB8C31 /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316F29E465194CACB8947EAF /* KeepScreenOn.swift */; }; - 18A549FCA1AE4507B65A4BDD /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADC984335A743A0ACDB409F /* ScreenOrientation.swift */; }; - 1535B18F8FEF446CB1FEAF74 /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116E4DA20AC043D9B6A9CE6D /* MatchColors.swift */; }; - 9493516ED98F487E9D70CABF /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0E99517ABF4F99B08F3705 /* StepMatchScreen.swift */; }; - BDC66B9F5AD34CA0BFDECE2C /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A79ECE8FCA4DF88EC6DE4F /* StepNetworkTestScreen.swift */; }; - 0F35C68364F242929EB0C7A8 /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A38D2697E34F55B9240A8A /* StepTransmissionScreen.swift */; }; - D1659689E30C484BB31B93C4 /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BEC22E0D4E242828468572E /* TeamBrandingEditor.swift */; }; - AA6C0D7D59AA46CFB3F362EC /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2B79415AC6441CB2735D4D /* TeamColorPicker.swift */; }; - D1304F69E9E7458980E7A5AE /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E4E688B3154A7C9791E72C /* WizardComponents.swift */; }; - CA3858060106420BBD5AA150 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B92AF2FADC4DE7B2A6D000 /* WizardShellScreen.swift */; }; - 3B9B059CA0A9476EBA98FF2F /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F390918CBBD54AAEB0647D47 /* ApiInstantTests.swift */; }; - D3B351AAB75F4996BC381BB6 /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D6CD1E29934B5D97D2F02E /* MatchScoringRulesTests.swift */; }; - AC6319A132F34CD4AE4D0460 /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = 402D4D4F22DB49678F3E6FE7; }; - CBBB3E15BBAD4B658EA9D569 = {isa = PBXGroup; children = (458DC3A81348480A8BFF1C2F, 413A6469A5994B779B5D9CE9, 1B1C9B9F983C4571AD8488D4); sourceTree = ""; }; - 458DC3A81348480A8BFF1C2F = {isa = PBXGroup; children = (7D502D6BFF584BE097F441D3, E4D928872F8A45FAAFFF9F98, 6FD1683477F947CB9C4B32A1, EE19BA76641848B8A6BD66BC, 674D4E0891724CCC9A530996, F11E75C58605479AB24D6BAB, DF7B53044B9B40D5A23F7077, 0FCD7AFD6FBA45B78CEC748E, 72E47488A16B4FA49C74FFA6, 0E4851454F9C4D6C9A2BD946, 13CFC4001F3641138379CF77, 416D9D0EE1414F0BA98EBC33, C2342D3CB2C146799098D05F, 1C23153FA47E4274AA87A136, EC76170D739A414EA1F7261B, 100C4F0504D8457D94089ED8, C649A4EC60774AA7829C6AC4, 616A0E114FE44B5782179A50, F2DD2DA40C21451287827C77, DB66A0A42AD54E48929F5A9E, C991BA285472411FA0819BA6, F2F260C1D8C44A7DBF26C88C, B91FAB0CE884433BAF90CB87, C981A70741504FED997BF8A2, E9990A92AB0546B3B77B9A8B, A23145A497C4494AB5901FB4, BF15F363ED4D4466B543226D, 423A409037C7408088DADC76, 82B588D9B7844FDFB600D9DF, DD63A5AFF02A4D32902606FA, 50DD7B3855C74DA291286184, CCB366E93584429F8EB8151D, E2F9E7C71E4844498BA95A56, 024982C1D4A84A5389A7BCF6, DAE19ACACBBA424AB844674E, 5342C88D12634E0894FD16E9, 691D1DF008404C29AC5925BC, B56AB91CBBA842A183DD04C9, 2801B8D684B440858B32ABB0, 3C2797E56C8B4857A7FA2880, 1D23B0386B9F487F8AA597D3, 03F2473246C84F41BCD355C2, 8CA4423F75F74FBFAE533080, 854ABA49A0D547B9AEB910B4, 3C3A2E7E20EB4F2DB5712369, 42AD3B662EC5485082D5741F, EFB4B3068F404B3EB5A0FF1F, 5014F0820E6B40F6B34DC51A, CEE1BFA4FF76445790FF6560, 3ECB25032A93474288F2FBA7, 2337F10A7DDA4E619DB37E82, 833350AF23F6419A90E317F8, 316F29E465194CACB8947EAF, 1ADC984335A743A0ACDB409F, 116E4DA20AC043D9B6A9CE6D, 8E0E99517ABF4F99B08F3705, D2A79ECE8FCA4DF88EC6DE4F, D1A38D2697E34F55B9240A8A, 0BEC22E0D4E242828468572E, EF2B79415AC6441CB2735D4D, 94E4E688B3154A7C9791E72C, E8B92AF2FADC4DE7B2A6D000, 402D4D4F22DB49678F3E6FE7, 044C9EE056CF444E9E98D3A6); name = MatchLiveTv; sourceTree = ""; }; - 413A6469A5994B779B5D9CE9 = {isa = PBXGroup; children = (F390918CBBD54AAEB0647D47, F4D6CD1E29934B5D97D2F02E); name = MatchLiveTvTests; sourceTree = ""; }; - 1B1C9B9F983C4571AD8488D4 = {isa = PBXGroup; children = (D28494E6896F4E25A06AE11E, 1D8E2905DD6E42479BC4C10E); name = Products; sourceTree = ""; }; - F7A341AD112F40069CF86FD6 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (EC02F9C46D3E4C1088D88F8A, 8BDFE8DE8206441A8D094C2E, 7B8856F51ECA47EDAC0D0F9E, AD61B798C741478E86CF0F21, BC44270E1DB54A9EA94AF25C, 13268DA8F54B4835BD433426, 32D039D1037C4D4E826C5967, 0713AAA3C14B4D76BCDFD2E0, 54951F21D9EE48719961E4D2, 139F79D283B1472E9DBA09A0, 308C38EE4912489495844906, 51D03DC2F67F47E1ACE13C62, 06BCCCDE5EEE4420965A8AD9, F6AD6F9866FD401CB3183B65, 53F020EB95F248EAB7B4F160, D3683FB90FFF4F6BACDC48AA, 7BFD2334AFE748B6A1E41904, 243992F8BACE403D8901BC2D, 85768720CAA94956A4DAD2D1, C92AA21B3A384DC6AD6D6C3A, D11A107F06F749F8909C2513, 9316E3BE6C394B90AC4C33BE, 53B9E9BC594B4E5BB6FA7C86, 1C9EB4524E2D466A877785CA, 5D2EFF9E1A444394AE8C4325, F40F95B9AF0846CBA1D1E1D3, 9BCFF862205B4275A845EDB1, 125330B419C94948942351D7, 534D7ACB2128436CA844E53B, 24EABF4A150546E4BC7F6C32, B4FC8CF476D44C32912FAD70, 83D733D7DBEE4374B1374074, 7D60112C22DA49C1B57A6AED, DDC4E6E4080040D595B53D94, 5D696DC51A334778BA8044A8, 0C1792555BFD40EC84E093F5, F52ED96B71C3448E9E6A93F6, 76BC7C34AB3E4B2DAEACFB89, 75E1FAC00E944A789974476C, 88CD8E2D7D684BE6BBC9EF7D, 10085C82FD204FA1A6727CE5, CFB328FB535446608F6AA4B7, 5A2D0215E89F4D0692B60FA8, 4B8C519BF65F435C9AF58902, 41DD9BBB13FB4CFD87C940F8, 4DBBB1020F2A4CD090740ECA, 25DB8C2F0DCD40A6BF38E7D3, DCEF4209679541628926D50D, B5F397173C1D437DB12CA85B, 8CEE2254C44F49B48FE4E3CE, 8801885DF5CE4620BBFA1CBB, 06DEA551887C423C834E39ED, 5F75111D35E84C22A6CB8C31, 18A549FCA1AE4507B65A4BDD, 1535B18F8FEF446CB1FEAF74, 9493516ED98F487E9D70CABF, BDC66B9F5AD34CA0BFDECE2C, 0F35C68364F242929EB0C7A8, D1659689E30C484BB31B93C4, AA6C0D7D59AA46CFB3F362EC, D1304F69E9E7458980E7A5AE, CA3858060106420BBD5AA150); runOnlyForDeploymentPostprocessing = 0; }; - 80C627F33BA84D3CA773051A = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (3B9B059CA0A9476EBA98FF2F, D3B351AAB75F4996BC381BB6); runOnlyForDeploymentPostprocessing = 0; }; - 6D5FE4DF497646A4AE97C6BF = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (AC6319A132F34CD4AE4D0460); runOnlyForDeploymentPostprocessing = 0; }; - 8DA7499F824E4935BDED6CBC = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }; - E9B2E37AC12D4AB087998F8D = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }; - DA38CD9FFE6747869F6CDDC2 = {isa = XCBuildConfiguration; buildSettings = { + 2C1637685ECE4A02B5681E5F /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = ""; }; + 56472459E69946E8AE093248 /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = ""; }; + 059D2F48669B4073BFE72E8D /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = ""; }; + 8F12B9A34CFD41029CD74A18 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = ""; }; + 6D1122BE2CAB4C6C9D6A12EB /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = ""; }; + 1FD3F6FCA8B54DDA858ABB6D /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = ""; }; + 22FD8122DEC74182A2F4CD69 /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = ""; }; + 58C37FC10F34409EB32CAB94 /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = ""; }; + 229A7B6E2AF14254BE5E1BE7 /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = ""; }; + 92CE832D69994A86B2CA85AD /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = ""; }; + 9AB29B550B774276AA6508CB /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = ""; }; + 9F97D0F58B0C4232827A6FEA /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = ""; }; + BE6073F255774BD1AB25D01A /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = ""; }; + 6E79EB14DEDE4BBBB30734E1 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = ""; }; + 606456B638DD4D8F8FEAF57B /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = ""; }; + 562CD8B2B0DA4275BAA14FE3 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = ""; }; + C9718FB64F514448BB5F8498 /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = ""; }; + 149E69E5291642339CFAC14B /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = ""; }; + 4C53F1C734CB4745BCB4BBD0 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = ""; }; + DF85D2BF2D574EEF97215552 /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = ""; }; + AA323B2E80B74C8DBEE4DA45 /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = ""; }; + 4BF8D73247554EAF8506B4C8 /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = ""; }; + 7AE2FC4135BB4D0A82B4DFFC /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = ""; }; + 126835B4E56A4AD0B3119429 /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = ""; }; + 31DA29870CD24D749044570D /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = ""; }; + 4B0C6EE1A67440D5B5B8EC2F /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = ""; }; + 88CCBA95967F429093C28846 /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = ""; }; + E3EA9302512B4A5AB283D88E /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = ""; }; + F7E85AC8EC7348F4A5B21F94 /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = ""; }; + 87D71E37017C47799B602656 /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = ""; }; + 73D8BF04B48E405BABA5395C /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = ""; }; + 8854A01335904882AADB4938 /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = ""; }; + 6CA512B24B2048DAB9876C7F /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = ""; }; + 0DA1CA2637BC43338153CC1D /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = ""; }; + 58668C3993F243FD9A5621C6 /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = ""; }; + E1722E92B37E4FB5BFA0F64F /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = ""; }; + 6407B9289F0048778B2A6057 /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = ""; }; + E44F2780AEDA44F8B16091E3 /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = ""; }; + 106A45AE33B94E778FDEDED7 /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = ""; }; + 31FAA2DF5F114464977D89B0 /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = ""; }; + 3CA6FD7B0F1E412FA56AB8A3 /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = ""; }; + 9545AF93C34540748A1C6E33 /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = ""; }; + 8AD4558FC0EB4A5D99F41E4D /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = ""; }; + 091497DB283544DFA9C7E736 /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = ""; }; + F93A551F9C8E4A71A007F25C /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = ""; }; + 66AF9547CF434A4D9C1E8335 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = ""; }; + F32269FB35364D969738C23A /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = ""; }; + 00BDEB10F16C4F968EEEF50E /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = ""; }; + A99587B489BF4900B86754ED /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = ""; }; + E013E12B974B4FCCA3C74151 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = ""; }; + 576978DEF79042848BCA07DC /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = ""; }; + BEC9E21F02BC41D58E006F66 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = ""; }; + 64BB42C4455341CBBEF7ADB1 /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = ""; }; + 7AFF6FEE9E2C420DB0B69CDC /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = ""; }; + EBAE4EB3BE0141AD982E1D5E /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = ""; }; + 6CE9AE7967074219B69DFEE4 /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = ""; }; + 40229139B1934E469D211B4F /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = ""; }; + 7F4BEC0641C14E6FBCAF4163 /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = ""; }; + 494C6DACBCA34021BAB851B9 /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = ""; }; + AEB1E4F84BBB438AA1FE8BEC /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = ""; }; + CF166707120B4B589F342EC4 /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = ""; }; + 5C742326421B4176855F5E64 /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = ""; }; + EA7B4BCC9EAA48CF94ADF09F /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = ""; }; + 596D2E6129214E95874E4E1F /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = ""; }; + 82C7043D46EB4FBCB41BFB87 /* ScoreActionDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreActionDecodeTests.swift; path = MatchLiveTvTests/ScoreActionDecodeTests.swift; sourceTree = ""; }; + 1F95F2F95BBB45589765B6C1 /* ScoreControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreControllerTests.swift; path = MatchLiveTvTests/ScoreControllerTests.swift; sourceTree = ""; }; + 7B6C9A5E9BA84A6BBCBF9B3F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = ""; }; + D1A10874BEB246908DF27543 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = ""; }; + 9989F3DEB021437FB1D79792 /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6F0D757BA4DF4A8BAA37E5A1 /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1794E4AFEC524A2391E1F43A /* HaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 144AB99B1DAD4D48925A7103; productName = HaishinKit; }; + 5A2B53779BB342F0A4EB1D50 /* RTMPHaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 144AB99B1DAD4D48925A7103; productName = RTMPHaishinKit; }; + 63AA6E164A9D46F08CE75059 /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1637685ECE4A02B5681E5F /* MatchLiveTvApp.swift */; }; + 9F4B82FECE1B4479BD5C70EA /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56472459E69946E8AE093248 /* ApiInstant.swift */; }; + 9F22BE9935F84C4F9B6DA971 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 059D2F48669B4073BFE72E8D /* AppConfig.swift */; }; + 6EB025E38C2E45C985C7E315 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F12B9A34CFD41029CD74A18 /* ColorHex.swift */; }; + EE457ABEF9B74DA28A2E2FFD /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1122BE2CAB4C6C9D6A12EB /* DeviceTelemetry.swift */; }; + 2D699C6ABD2840B385194046 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD3F6FCA8B54DDA858ABB6D /* MediaUrl.swift */; }; + 2BF572CC8FBB420CAE651946 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD8122DEC74182A2F4CD69 /* TokenStore.swift */; }; + 71B88EC9E30A4866B3F7E20E /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C37FC10F34409EB32CAB94 /* UserFacingError.swift */; }; + 0461E337AAD54BBFA537E7E1 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229A7B6E2AF14254BE5E1BE7 /* ApiDtos.swift */; }; + 1BFEEBA978A54FC8A1A38D7D /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CE832D69994A86B2CA85AD /* MatchLiveAPI.swift */; }; + 4A170EAF1F114BA1BD512C77 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB29B550B774276AA6508CB /* AppContainer.swift */; }; + 8FCD261732D245B69839DAEB /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F97D0F58B0C4232827A6FEA /* ActionCableClient.swift */; }; + A967247B226E4E86811D86DA /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6073F255774BD1AB25D01A /* SessionCableService.swift */; }; + 25360BD296FA4D26AB936847 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E79EB14DEDE4BBBB30734E1 /* AuthRepository.swift */; }; + 77FF5239FE2A458F815117E5 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606456B638DD4D8F8FEAF57B /* MatchRepository.swift */; }; + 4CE31F1DC2A141BB97BBBF77 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562CD8B2B0DA4275BAA14FE3 /* MatchSessionLauncher.swift */; }; + C741D7BBAEF842B1A5CE79F5 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9718FB64F514448BB5F8498 /* ScoreRepository.swift */; }; + 033E9E19FA1D41ECBC6FE954 /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 149E69E5291642339CFAC14B /* SessionRepository.swift */; }; + 9708F347932F4ED9B25F30D4 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C53F1C734CB4745BCB4BBD0 /* ScoreController.swift */; }; + 6F21F50CE3394618BBDA9DB9 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF85D2BF2D574EEF97215552 /* WizardSessionHolder.swift */; }; + E1EBDFB0867C415FBC6D1267 /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA323B2E80B74C8DBEE4DA45 /* MatchHubFilter.swift */; }; + 43B0ABA8C73547FBBA33F681 /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D73247554EAF8506B4C8 /* MatchScoringRules.swift */; }; + 9B29D4ECB04641B386D43176 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE2FC4135BB4D0A82B4DFFC /* Models.swift */; }; + 9FD1B70E6BC94A5B963C84E3 /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 126835B4E56A4AD0B3119429 /* ScoreState.swift */; }; + 53A1BD84C77844F9A5322D71 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DA29870CD24D749044570D /* BroadcastModels.swift */; }; + 55822A3378244F328DEC930B /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0C6EE1A67440D5B5B8EC2F /* LiveBroadcastCoordinator.swift */; }; + 8C3A6D2934054AD685C6B17B /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CCBA95967F429093C28846 /* LiveBroadcastEngine.swift */; }; + BB282D2B90F442FAA19957DE /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EA9302512B4A5AB283D88E /* CompactScoreboardElement.swift */; }; + 12D1F6F6647E4A02B5FAE6A5 /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E85AC8EC7348F4A5B21F94 /* OverlayCanvasRenderer.swift */; }; + 259CD50B331D435CAA934791 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D71E37017C47799B602656 /* OverlayLogoCache.swift */; }; + 597F0E27F4794C5B8EB4421D /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D8BF04B48E405BABA5395C /* OverlayMappings.swift */; }; + 4CB7FA4311CD496DBAF590D2 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8854A01335904882AADB4938 /* OverlayRenderer.swift */; }; + 124E11045F024DB79725B158 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA512B24B2048DAB9876C7F /* OverlayState.swift */; }; + 00E66D28E2C44685B08D3862 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA1CA2637BC43338153CC1D /* ScoreboardElement.swift */; }; + 0D823575D3C34A6AB0B1ADE7 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58668C3993F243FD9A5621C6 /* SponsorElement.swift */; }; + 6826A0C8849B468089F89095 /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1722E92B37E4FB5BFA0F64F /* WatermarkElement.swift */; }; + 85DD60C1258B4454879F3958 /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407B9289F0048778B2A6057 /* LivePreviewView.swift */; }; + 33E82511BEF2435A832885B6 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44F2780AEDA44F8B16091E3 /* BroadcastControlsOverlay.swift */; }; + 63A3704385234F25B6B4D9DE /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A45AE33B94E778FDEDED7 /* BroadcastScreen.swift */; }; + DF93C479029448FB98BB26D4 /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FAA2DF5F114464977D89B0 /* LiveScoreActions.swift */; }; + 1E7B2D233349405B9D8850B8 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA6FD7B0F1E412FA56AB8A3 /* MatchLiveWordmark.swift */; }; + 573B97F0E4D74DCD90962950 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9545AF93C34540748A1C6E33 /* MatchPrimaryButton.swift */; }; + 4B7C1844987C4C4A9331EFF6 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD4558FC0EB4A5D99F41E4D /* MatchScreenScaffold.swift */; }; + 5791C4F50F0948E7993907B3 /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091497DB283544DFA9C7E736 /* MatchSecondaryButton.swift */; }; + 2E8C344A733B462A87720E91 /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93A551F9C8E4A71A007F25C /* MatchStatusBadge.swift */; }; + 2FB16045C6BD4A5E889DD484 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF9547CF434A4D9C1E8335 /* LoginScreen.swift */; }; + 029149C11EA642BEBEA697D8 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32269FB35364D969738C23A /* MatchesScreen.swift */; }; + C9B7DFB707744641BD6CED07 /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BDEB10F16C4F968EEEF50E /* AppNavHost.swift */; }; + A4D5F108E5374A108B5860B1 /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99587B489BF4900B86754ED /* ModalRoutes.swift */; }; + F04BF893D3C248C2AF7A546A /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E013E12B974B4FCCA3C74151 /* Routes.swift */; }; + 7B19B54BCD0047F980097559 /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 576978DEF79042848BCA07DC /* BroadcastPermissions.swift */; }; + 814C7713F99942A896722EEE /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEC9E21F02BC41D58E006F66 /* SplashScreen.swift */; }; + BE9617BC3F2748A2B78AFF9A /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BB42C4455341CBBEF7ADB1 /* KeepScreenOn.swift */; }; + 1341F3BB59E24B7DA0B3F492 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFF6FEE9E2C420DB0B69CDC /* ScreenOrientation.swift */; }; + DA875E6F04BA46C6BB5193DB /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAE4EB3BE0141AD982E1D5E /* MatchColors.swift */; }; + B95B2FF16A294D2A82E74BC9 /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE9AE7967074219B69DFEE4 /* StepMatchScreen.swift */; }; + 0AB2C536294B411EA229CFFF /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40229139B1934E469D211B4F /* StepNetworkTestScreen.swift */; }; + 4EAEC68278E6487496AC81EE /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F4BEC0641C14E6FBCAF4163 /* StepTransmissionScreen.swift */; }; + C39AC5E9671941D89416F412 /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494C6DACBCA34021BAB851B9 /* TeamBrandingEditor.swift */; }; + 68C7A87D2C5E49F2A7BCEF99 /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEB1E4F84BBB438AA1FE8BEC /* TeamColorPicker.swift */; }; + 85563DC5408842109B436A49 /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF166707120B4B589F342EC4 /* WizardComponents.swift */; }; + 5DE5E7C500304E16915076E3 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C742326421B4176855F5E64 /* WizardShellScreen.swift */; }; + 0CB7A2DEBA8041A5B75A2F74 /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7B4BCC9EAA48CF94ADF09F /* ApiInstantTests.swift */; }; + B97B7A37B48540A8BB2653A7 /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596D2E6129214E95874E4E1F /* MatchScoringRulesTests.swift */; }; + 34968BB5B4FD4727A1DB7DA6 /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C7043D46EB4FBCB41BFB87 /* ScoreActionDecodeTests.swift */; }; + 1FB1035FC78C4034BBC93FEF /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F95F2F95BBB45589765B6C1 /* ScoreControllerTests.swift */; }; + 5A54FCC515114BE19610F99F /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = 7B6C9A5E9BA84A6BBCBF9B3F; }; + 9E10ADDF29394FD6B200D2A7 = {isa = PBXGroup; children = (3181CAF4010E480AB325DA30, 61A4D30FDB7644FEBBCB6AC7, 948F72C223D64471ABFCFE2F); sourceTree = ""; }; + 3181CAF4010E480AB325DA30 = {isa = PBXGroup; children = (2C1637685ECE4A02B5681E5F, 56472459E69946E8AE093248, 059D2F48669B4073BFE72E8D, 8F12B9A34CFD41029CD74A18, 6D1122BE2CAB4C6C9D6A12EB, 1FD3F6FCA8B54DDA858ABB6D, 22FD8122DEC74182A2F4CD69, 58C37FC10F34409EB32CAB94, 229A7B6E2AF14254BE5E1BE7, 92CE832D69994A86B2CA85AD, 9AB29B550B774276AA6508CB, 9F97D0F58B0C4232827A6FEA, BE6073F255774BD1AB25D01A, 6E79EB14DEDE4BBBB30734E1, 606456B638DD4D8F8FEAF57B, 562CD8B2B0DA4275BAA14FE3, C9718FB64F514448BB5F8498, 149E69E5291642339CFAC14B, 4C53F1C734CB4745BCB4BBD0, DF85D2BF2D574EEF97215552, AA323B2E80B74C8DBEE4DA45, 4BF8D73247554EAF8506B4C8, 7AE2FC4135BB4D0A82B4DFFC, 126835B4E56A4AD0B3119429, 31DA29870CD24D749044570D, 4B0C6EE1A67440D5B5B8EC2F, 88CCBA95967F429093C28846, E3EA9302512B4A5AB283D88E, F7E85AC8EC7348F4A5B21F94, 87D71E37017C47799B602656, 73D8BF04B48E405BABA5395C, 8854A01335904882AADB4938, 6CA512B24B2048DAB9876C7F, 0DA1CA2637BC43338153CC1D, 58668C3993F243FD9A5621C6, E1722E92B37E4FB5BFA0F64F, 6407B9289F0048778B2A6057, E44F2780AEDA44F8B16091E3, 106A45AE33B94E778FDEDED7, 31FAA2DF5F114464977D89B0, 3CA6FD7B0F1E412FA56AB8A3, 9545AF93C34540748A1C6E33, 8AD4558FC0EB4A5D99F41E4D, 091497DB283544DFA9C7E736, F93A551F9C8E4A71A007F25C, 66AF9547CF434A4D9C1E8335, F32269FB35364D969738C23A, 00BDEB10F16C4F968EEEF50E, A99587B489BF4900B86754ED, E013E12B974B4FCCA3C74151, 576978DEF79042848BCA07DC, BEC9E21F02BC41D58E006F66, 64BB42C4455341CBBEF7ADB1, 7AFF6FEE9E2C420DB0B69CDC, EBAE4EB3BE0141AD982E1D5E, 6CE9AE7967074219B69DFEE4, 40229139B1934E469D211B4F, 7F4BEC0641C14E6FBCAF4163, 494C6DACBCA34021BAB851B9, AEB1E4F84BBB438AA1FE8BEC, CF166707120B4B589F342EC4, 5C742326421B4176855F5E64, 7B6C9A5E9BA84A6BBCBF9B3F, D1A10874BEB246908DF27543); name = MatchLiveTv; sourceTree = ""; }; + 61A4D30FDB7644FEBBCB6AC7 = {isa = PBXGroup; children = (EA7B4BCC9EAA48CF94ADF09F, 596D2E6129214E95874E4E1F, 82C7043D46EB4FBCB41BFB87, 1F95F2F95BBB45589765B6C1); name = MatchLiveTvTests; sourceTree = ""; }; + 948F72C223D64471ABFCFE2F = {isa = PBXGroup; children = (9989F3DEB021437FB1D79792, 6F0D757BA4DF4A8BAA37E5A1); name = Products; sourceTree = ""; }; + 6C16E72ECFC141DABE1BF97D = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (63AA6E164A9D46F08CE75059, 9F4B82FECE1B4479BD5C70EA, 9F22BE9935F84C4F9B6DA971, 6EB025E38C2E45C985C7E315, EE457ABEF9B74DA28A2E2FFD, 2D699C6ABD2840B385194046, 2BF572CC8FBB420CAE651946, 71B88EC9E30A4866B3F7E20E, 0461E337AAD54BBFA537E7E1, 1BFEEBA978A54FC8A1A38D7D, 4A170EAF1F114BA1BD512C77, 8FCD261732D245B69839DAEB, A967247B226E4E86811D86DA, 25360BD296FA4D26AB936847, 77FF5239FE2A458F815117E5, 4CE31F1DC2A141BB97BBBF77, C741D7BBAEF842B1A5CE79F5, 033E9E19FA1D41ECBC6FE954, 9708F347932F4ED9B25F30D4, 6F21F50CE3394618BBDA9DB9, E1EBDFB0867C415FBC6D1267, 43B0ABA8C73547FBBA33F681, 9B29D4ECB04641B386D43176, 9FD1B70E6BC94A5B963C84E3, 53A1BD84C77844F9A5322D71, 55822A3378244F328DEC930B, 8C3A6D2934054AD685C6B17B, BB282D2B90F442FAA19957DE, 12D1F6F6647E4A02B5FAE6A5, 259CD50B331D435CAA934791, 597F0E27F4794C5B8EB4421D, 4CB7FA4311CD496DBAF590D2, 124E11045F024DB79725B158, 00E66D28E2C44685B08D3862, 0D823575D3C34A6AB0B1ADE7, 6826A0C8849B468089F89095, 85DD60C1258B4454879F3958, 33E82511BEF2435A832885B6, 63A3704385234F25B6B4D9DE, DF93C479029448FB98BB26D4, 1E7B2D233349405B9D8850B8, 573B97F0E4D74DCD90962950, 4B7C1844987C4C4A9331EFF6, 5791C4F50F0948E7993907B3, 2E8C344A733B462A87720E91, 2FB16045C6BD4A5E889DD484, 029149C11EA642BEBEA697D8, C9B7DFB707744641BD6CED07, A4D5F108E5374A108B5860B1, F04BF893D3C248C2AF7A546A, 7B19B54BCD0047F980097559, 814C7713F99942A896722EEE, BE9617BC3F2748A2B78AFF9A, 1341F3BB59E24B7DA0B3F492, DA875E6F04BA46C6BB5193DB, B95B2FF16A294D2A82E74BC9, 0AB2C536294B411EA229CFFF, 4EAEC68278E6487496AC81EE, C39AC5E9671941D89416F412, 68C7A87D2C5E49F2A7BCEF99, 85563DC5408842109B436A49, 5DE5E7C500304E16915076E3); runOnlyForDeploymentPostprocessing = 0; }; + A4BC052FDBD448CF8DCD2C26 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (0CB7A2DEBA8041A5B75A2F74, B97B7A37B48540A8BB2653A7, 34968BB5B4FD4727A1DB7DA6, 1FB1035FC78C4034BBC93FEF); runOnlyForDeploymentPostprocessing = 0; }; + 41F620F41CDA460CA5758D12 = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (5A54FCC515114BE19610F99F); runOnlyForDeploymentPostprocessing = 0; }; + 975D24005603476B9C9AD706 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }; + AA380510B8DA455C8ADB600E = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }; + C30FD711CB14404CA4ACF003 = {isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 21; @@ -171,7 +175,7 @@ ENABLE_TESTABILITY = YES; }; name = Debug; }; - 9E05E72AB8714F77936FEE82 = {isa = XCBuildConfiguration; buildSettings = { + 943E77C5BF47424D930F8289 = {isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 21; @@ -192,7 +196,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 7805CB3AE2314A48A5A5CC9D = {isa = XCBuildConfiguration; buildSettings = { + 490BD52EC789443397269936 = {isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 21; @@ -206,7 +210,7 @@ SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv"; }; name = Debug; }; - 9869C88802314492A8E9FFFF = {isa = XCBuildConfiguration; buildSettings = { + EC6EAD89012E49BA855E8B4C = {isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 21; @@ -220,17 +224,17 @@ SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv"; }; name = Release; }; - 33F5448B1E1349E2BAA5E3AE = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Debug; }; - D72B6CA2A24E4AC09A0B782D = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Release; }; - 75B30A2BA25E4139B5005285 = {isa = XCConfigurationList; buildConfigurations = (DA38CD9FFE6747869F6CDDC2, 9E05E72AB8714F77936FEE82); defaultConfigurationName = Release; }; - 01C6C81B18A7474DBD247E7A = {isa = XCConfigurationList; buildConfigurations = (7805CB3AE2314A48A5A5CC9D, 9869C88802314492A8E9FFFF); defaultConfigurationName = Release; }; - A30B7C4ACC1F47769F89A326 = {isa = XCConfigurationList; buildConfigurations = (33F5448B1E1349E2BAA5E3AE, D72B6CA2A24E4AC09A0B782D); defaultConfigurationName = Release; }; - DC6F777098DA4A41A83135DA = {isa = PBXNativeTarget; buildConfigurationList = 75B30A2BA25E4139B5005285; buildPhases = (F7A341AD112F40069CF86FD6, 8DA7499F824E4935BDED6CBC, 6D5FE4DF497646A4AE97C6BF); buildRules = (); dependencies = (); name = MatchLiveTv; packageProductDependencies = (DFA66CC46F824DCF9A20211C, 7CD46223721F489E8F2981D8); productName = MatchLiveTv; productReference = D28494E6896F4E25A06AE11E; productType = "com.apple.product-type.application"; }; - C445B2BC555541D7B69EB55F = {isa = PBXContainerItemProxy; containerPortal = 6E081DDA594D416BB8A07BE6 /* Project object */; proxyType = 1; remoteGlobalIDString = DC6F777098DA4A41A83135DA; remoteInfo = MatchLiveTv; }; - F804FB6411E24621B8A73A8F = {isa = PBXTargetDependency; target = DC6F777098DA4A41A83135DA /* MatchLiveTv */; targetProxy = C445B2BC555541D7B69EB55F /* PBXContainerItemProxy */; }; - 4BD1D70CB43840E58D45DF29 = {isa = PBXNativeTarget; buildConfigurationList = 01C6C81B18A7474DBD247E7A; buildPhases = (80C627F33BA84D3CA773051A, E9B2E37AC12D4AB087998F8D); buildRules = (); dependencies = (F804FB6411E24621B8A73A8F); name = MatchLiveTvTests; productName = MatchLiveTvTests; productReference = 1D8E2905DD6E42479BC4C10E; productType = "com.apple.product-type.bundle.unit-test"; }; - 80244AADE65A4F05B29ECFB5 = {isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/shogo4405/HaishinKit.swift"; requirement = {kind = upToNextMajorVersion; minimumVersion = 2.0.0;}; }; - 6E081DDA594D416BB8A07BE6 = {isa = PBXProject; attributes = {BuildIndependentTargetsInParallel = 0; LastSwiftUpdateCheck = 1600;}; buildConfigurationList = A30B7C4ACC1F47769F89A326; compatibilityVersion = "Xcode 15.0"; developmentRegion = it; mainGroup = CBBB3E15BBAD4B658EA9D569; packageReferences = (80244AADE65A4F05B29ECFB5); productRefGroup = 1B1C9B9F983C4571AD8488D4; targets = (DC6F777098DA4A41A83135DA, 4BD1D70CB43840E58D45DF29); }; + C496B00D0FBC453081E8D965 = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Debug; }; + 33B07D34DCE74B519F4207EB = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Release; }; + 95F893DE8F6A4FFBB614AE96 = {isa = XCConfigurationList; buildConfigurations = (C30FD711CB14404CA4ACF003, 943E77C5BF47424D930F8289); defaultConfigurationName = Release; }; + 2591CEDFD4D6441784591FCE = {isa = XCConfigurationList; buildConfigurations = (490BD52EC789443397269936, EC6EAD89012E49BA855E8B4C); defaultConfigurationName = Release; }; + 5F49CA34489E4C7F89ECD863 = {isa = XCConfigurationList; buildConfigurations = (C496B00D0FBC453081E8D965, 33B07D34DCE74B519F4207EB); defaultConfigurationName = Release; }; + 98BE277FE859401F8A05312C = {isa = PBXNativeTarget; buildConfigurationList = 95F893DE8F6A4FFBB614AE96; buildPhases = (6C16E72ECFC141DABE1BF97D, 975D24005603476B9C9AD706, 41F620F41CDA460CA5758D12); buildRules = (); dependencies = (); name = MatchLiveTv; packageProductDependencies = (1794E4AFEC524A2391E1F43A, 5A2B53779BB342F0A4EB1D50); productName = MatchLiveTv; productReference = 9989F3DEB021437FB1D79792; productType = "com.apple.product-type.application"; }; + BA0168680D5443B8A9521C5B = {isa = PBXContainerItemProxy; containerPortal = FF548150A3A24533BC87A2B6 /* Project object */; proxyType = 1; remoteGlobalIDString = 98BE277FE859401F8A05312C; remoteInfo = MatchLiveTv; }; + E8409388B8964E7B848E487D = {isa = PBXTargetDependency; target = 98BE277FE859401F8A05312C /* MatchLiveTv */; targetProxy = BA0168680D5443B8A9521C5B /* PBXContainerItemProxy */; }; + AF6564A25AD445BE97A401CC = {isa = PBXNativeTarget; buildConfigurationList = 2591CEDFD4D6441784591FCE; buildPhases = (A4BC052FDBD448CF8DCD2C26, AA380510B8DA455C8ADB600E); buildRules = (); dependencies = (E8409388B8964E7B848E487D); name = MatchLiveTvTests; productName = MatchLiveTvTests; productReference = 6F0D757BA4DF4A8BAA37E5A1; productType = "com.apple.product-type.bundle.unit-test"; }; + 144AB99B1DAD4D48925A7103 = {isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/shogo4405/HaishinKit.swift"; requirement = {kind = upToNextMajorVersion; minimumVersion = 2.0.0;}; }; + FF548150A3A24533BC87A2B6 = {isa = PBXProject; attributes = {BuildIndependentTargetsInParallel = 0; LastSwiftUpdateCheck = 1600;}; buildConfigurationList = 5F49CA34489E4C7F89ECD863; compatibilityVersion = "Xcode 15.0"; developmentRegion = it; mainGroup = 9E10ADDF29394FD6B200D2A7; packageReferences = (144AB99B1DAD4D48925A7103); productRefGroup = 948F72C223D64471ABFCFE2F; targets = (98BE277FE859401F8A05312C, AF6564A25AD445BE97A401CC); }; }; - rootObject = 6E081DDA594D416BB8A07BE6 /* Project object */; + rootObject = FF548150A3A24533BC87A2B6 /* Project object */; } diff --git a/native/ios/MatchLiveTv.xcodeproj/xcshareddata/xcschemes/MatchLiveTv.xcscheme b/native/ios/MatchLiveTv.xcodeproj/xcshareddata/xcschemes/MatchLiveTv.xcscheme index 6136466..d4172ff 100644 --- a/native/ios/MatchLiveTv.xcodeproj/xcshareddata/xcschemes/MatchLiveTv.xcscheme +++ b/native/ios/MatchLiveTv.xcodeproj/xcshareddata/xcschemes/MatchLiveTv.xcscheme @@ -3,28 +3,28 @@ - + - + - + - + - + diff --git a/native/ios/MatchLiveTv/Data/API/ApiDtos.swift b/native/ios/MatchLiveTv/Data/API/ApiDtos.swift index 6d49f46..ec963e3 100644 --- a/native/ios/MatchLiveTv/Data/API/ApiDtos.swift +++ b/native/ios/MatchLiveTv/Data/API/ApiDtos.swift @@ -199,38 +199,8 @@ struct SetPartialDto: Codable { let away: Int? } -/// Decodifica tollerante per campi JSONB (`score_states.data`). -private enum FlexibleJSON: Decodable { - case int(Int) - case bool(Bool) - case string(String) - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let v = try? container.decode(Bool.self) { self = .bool(v); return } - if let v = try? container.decode(Int.self) { self = .int(v); return } - if let v = try? container.decode(String.self) { self = .string(v); return } - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON value") - } - - var intValue: Int? { - switch self { - case .int(let v): return v - case .string(let s): return Int(s) - case .bool(let v): return v ? 1 : 0 - } - } - - var boolValue: Bool? { - switch self { - case .bool(let v): return v - case .int(let v): return v != 0 - case .string(let s): return (s as NSString).boolValue - } - } -} - struct ScoreStateDto: Decodable { + let type: String? let boardType: String? let homeSets: Int? let awaySets: Int? @@ -240,68 +210,16 @@ struct ScoreStateDto: Decodable { let setPartials: [SetPartialDto]? let timeoutHome: Bool? let timeoutAway: Bool? - let period: String? + let period: FlexiblePeriod? let clockSecs: Int? - let clockRunning: Bool? - - enum CodingKeys: String, CodingKey { - case boardType = "board_type" - case homeSets = "home_sets" - case awaySets = "away_sets" - case homePoints = "home_points" - case awayPoints = "away_points" - case currentSet = "current_set" - case setPartials = "set_partials" - case timeoutHome = "timeout_home" - case timeoutAway = "timeout_away" - case period - case clockSecs = "clock_secs" - case clockRunning = "clock_running" - case data - } - - init(from decoder: Decoder) throws { - let c = try decoder.container(keyedBy: CodingKeys.self) - boardType = try c.decodeIfPresent(String.self, forKey: .boardType) - homeSets = Self.decodeInt(c, key: .homeSets) - awaySets = Self.decodeInt(c, key: .awaySets) - homePoints = Self.decodeInt(c, key: .homePoints) - awayPoints = Self.decodeInt(c, key: .awayPoints) - currentSet = Self.decodeInt(c, key: .currentSet) - setPartials = try c.decodeIfPresent([SetPartialDto].self, forKey: .setPartials) - timeoutHome = Self.decodeBool(c, key: .timeoutHome) - timeoutAway = Self.decodeBool(c, key: .timeoutAway) - period = Self.decodeString(c, key: .period) - nestedData = try c.decodeIfPresent([String: FlexibleJSON].self, forKey: .data) - clockSecs = Self.decodeInt(c, key: .clockSecs) ?? nestedData?["clock_secs"]?.intValue - clockRunning = Self.decodeBool(c, key: .clockRunning) ?? nestedData?["clock_running"]?.boolValue - } - - private let nestedData: [String: FlexibleJSON]? - - private static func decodeInt(_ c: KeyedDecodingContainer, key: CodingKeys) -> Int? { - if let v = try? c.decodeIfPresent(Int.self, forKey: key) { return v } - if let s = try? c.decodeIfPresent(String.self, forKey: key), let v = Int(s) { return v } - return nil - } - - private static func decodeBool(_ c: KeyedDecodingContainer, key: CodingKeys) -> Bool? { - if let v = try? c.decodeIfPresent(Bool.self, forKey: key) { return v } - if let i = try? c.decodeIfPresent(Int.self, forKey: key) { return i != 0 } - return nil - } - - private static func decodeString(_ c: KeyedDecodingContainer, key: CodingKeys) -> String? { - if let s = try? c.decodeIfPresent(String.self, forKey: key) { return s } - if let i = try? c.decodeIfPresent(Int.self, forKey: key) { return String(i) } - return nil - } + let clockRunning: FlexibleBool? + let data: ScoreStateDataDto? func toDomain() -> ScoreState { let board = boardType ?? "volley" - let homePts = homePoints ?? nestedData?["home_score"]?.intValue ?? 0 - let awayPts = awayPoints ?? nestedData?["away_score"]?.intValue ?? 0 - let overtime = nestedData?["overtime"]?.boolValue ?? false + let homePts = homePoints ?? data?.homeScore ?? 0 + let awayPts = awayPoints ?? data?.awayScore ?? 0 + let periodLabel = period?.label return ScoreState( homeSets: homeSets ?? 0, awaySets: awaySets ?? 0, @@ -314,11 +232,11 @@ struct ScoreStateDto: Decodable { timeoutHome: timeoutHome ?? false, timeoutAway: timeoutAway ?? false, boardType: board, - period: parsePeriodNumber(period, currentSet: currentSet), - periodLabel: period, - clockSecs: clockSecs ?? 0, - clockRunning: clockRunning ?? false, - overtime: overtime + period: parsePeriodNumber(periodLabel, currentSet: currentSet), + periodLabel: periodLabel, + clockSecs: clockSecs ?? data?.clockSecs ?? 0, + clockRunning: clockRunning?.value ?? data?.clockRunning?.value ?? false, + overtime: data?.overtime?.value ?? false ) } @@ -334,6 +252,60 @@ struct ScoreStateDto: Decodable { } } +/// `period` può essere stringa ("Q2"), intero o null. +enum FlexiblePeriod: Decodable { + case string(String) + case int(Int) + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let v = try? container.decode(Int.self) { self = .int(v); return } + if let v = try? container.decode(String.self) { self = .string(v); return } + throw DecodingError.typeMismatch( + FlexiblePeriod.self, + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "period non valido") + ) + } + + var label: String? { + switch self { + case .string(let s): return s + case .int(let v): return String(v) + } + } +} + +enum FlexibleBool: Decodable { + case bool(Bool) + case int(Int) + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let v = try? container.decode(Bool.self) { self = .bool(v); return } + if let v = try? container.decode(Int.self) { self = .int(v); return } + throw DecodingError.typeMismatch( + FlexibleBool.self, + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "bool non valido") + ) + } + + var value: Bool { + switch self { + case .bool(let v): return v + case .int(let v): return v != 0 + } + } +} + +struct ScoreStateDataDto: Decodable { + let homeScore: Int? + let awayScore: Int? + let clockSecs: Int? + let clockRunning: FlexibleBool? + let period: Int? + let overtime: FlexibleBool? +} + struct ScoreActionRequest: Encodable { let scoreAction: String } diff --git a/native/ios/MatchLiveTv/Data/Scoring/ScoreController.swift b/native/ios/MatchLiveTv/Data/Scoring/ScoreController.swift index 84c0b72..6a8eb53 100644 --- a/native/ios/MatchLiveTv/Data/Scoring/ScoreController.swift +++ b/native/ios/MatchLiveTv/Data/Scoring/ScoreController.swift @@ -3,6 +3,7 @@ import Foundation @MainActor final class ScoreController: ObservableObject { @Published private(set) var score = ScoreState() + @Published private(set) var lastActionError: String? private let scoreRepository: ScoreRepository private let sessionCable: SessionCableService @@ -28,43 +29,57 @@ final class ScoreController: ObservableObject { } func incrementHome() { - score.homePoints += 1 + updateScore { $0.homePoints += 1 } schedulePush() } func incrementAway() { - score.awayPoints += 1 + updateScore { $0.awayPoints += 1 } schedulePush() } func decrementHome() { - if score.homePoints > 0 { score.homePoints -= 1 } + updateScore { if $0.homePoints > 0 { $0.homePoints -= 1 } } schedulePush() } func decrementAway() { - if score.awayPoints > 0 { score.awayPoints -= 1 } + updateScore { if $0.awayPoints > 0 { $0.awayPoints -= 1 } } schedulePush() } func applyAction(_ action: String) { - applyOptimistic(action) + updateScore { applyOptimistic(to: &$0, action: action) } scheduleAction(action) } - /// Azione punteggio via API (volley/racket): il server è fonte di verità. - func applyBoardAction(_ action: String) async { + /// Azione punteggio via API (volley/racket): aggiornamento ottimistico + conferma server. + @discardableResult + func applyBoardAction(_ action: String) async -> Bool { + lastActionError = nil + let previous = score + updateScore { applyOptimistic(to: &$0, action: action) } syncTask?.cancel() syncGeneration += 1 - guard let sessionId else { return } + guard let sessionId else { + score = previous + lastActionError = "Sessione non collegata" + return false + } do { if let updated = try await scoreRepository.applyScoreAction(sessionId: sessionId, action: action) { suppressRemoteUntil = Date().addingTimeInterval(0.8) score = updated sessionCable.sendScoreUpdate(updated) + return true } + score = previous + lastActionError = "Risposta punteggio non valida" + return false } catch { - // Mantieni lo stato locale; la UI può mostrare errore a livello schermata se serve. + score = previous + lastActionError = UserFacingError.message(for: error) + return false } } @@ -82,20 +97,27 @@ final class ScoreController: ObservableObject { } return false } catch { + lastActionError = UserFacingError.message(for: error) return false } } - private func applyOptimistic(_ action: String) { + private func updateScore(_ transform: (inout ScoreState) -> Void) { + var next = score + transform(&next) + score = next + } + + private func applyOptimistic(to state: inout ScoreState, action: String) { switch action { - case "home_point": score.homePoints += 1 - case "away_point": score.awayPoints += 1 - case "home_point_2": score.homePoints += 2 - case "away_point_2": score.awayPoints += 2 - case "home_point_3": score.homePoints += 3 - case "away_point_3": score.awayPoints += 3 - case "home_undo": score.homePoints = max(0, score.homePoints - 1) - case "away_undo": score.awayPoints = max(0, score.awayPoints - 1) + case "home_point": state.homePoints += 1 + case "away_point": state.awayPoints += 1 + case "home_point_2": state.homePoints += 2 + case "away_point_2": state.awayPoints += 2 + case "home_point_3": state.homePoints += 3 + case "away_point_3": state.awayPoints += 3 + case "home_undo": state.homePoints = max(0, state.homePoints - 1) + case "away_undo": state.awayPoints = max(0, state.awayPoints - 1) default: break } } @@ -106,11 +128,16 @@ final class ScoreController: ObservableObject { let generation = syncGeneration syncTask = Task { guard let sessionId else { return } - if let updated = try? await scoreRepository.applyScoreAction(sessionId: sessionId, action: action) { + do { + if let updated = try await scoreRepository.applyScoreAction(sessionId: sessionId, action: action) { + guard generation == syncGeneration else { return } + suppressRemoteUntil = Date().addingTimeInterval(0.5) + score = updated + sessionCable.sendScoreUpdate(updated) + } + } catch { guard generation == syncGeneration else { return } - suppressRemoteUntil = Date().addingTimeInterval(0.5) - score = updated - sessionCable.sendScoreUpdate(updated) + lastActionError = UserFacingError.message(for: error) } } } diff --git a/native/ios/MatchLiveTv/Streaming/Preview/LivePreviewView.swift b/native/ios/MatchLiveTv/Streaming/Preview/LivePreviewView.swift index 2bd3c3b..21fd4dc 100644 --- a/native/ios/MatchLiveTv/Streaming/Preview/LivePreviewView.swift +++ b/native/ios/MatchLiveTv/Streaming/Preview/LivePreviewView.swift @@ -8,6 +8,7 @@ struct LivePreviewView: UIViewRepresentable { func makeUIView(context: Context) -> MTHKView { let view = MTHKView(frame: .zero) view.videoGravity = .resizeAspectFill + view.isUserInteractionEnabled = false Task { await engine.bindPreview(to: view) } return view } diff --git a/native/ios/MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift b/native/ios/MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift index 7dfd7fb..3c41464 100644 --- a/native/ios/MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift +++ b/native/ios/MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift @@ -45,6 +45,8 @@ struct BroadcastControlsOverlay: View { var body: some View { Color.clear + .frame(maxWidth: .infinity, maxHeight: .infinity) + .contentShape(Rectangle()) .overlay(alignment: .topLeading) { MatchStatusBadge( text: statusText, diff --git a/native/ios/MatchLiveTv/UI/Broadcast/BroadcastScreen.swift b/native/ios/MatchLiveTv/UI/Broadcast/BroadcastScreen.swift index 703356f..ab29119 100644 --- a/native/ios/MatchLiveTv/UI/Broadcast/BroadcastScreen.swift +++ b/native/ios/MatchLiveTv/UI/Broadcast/BroadcastScreen.swift @@ -50,6 +50,7 @@ struct BroadcastScreen: View { .ignoresSafeArea() .allowsHitTesting(false) broadcastOverlay(session: session, match: match) + .id(scoreController.score.progressKey()) .zIndex(1) } } @@ -75,6 +76,9 @@ struct BroadcastScreen: View { await bootstrap() } .onChange(of: scoreController.score) { _ in updateOverlay() } + .onChange(of: scoreController.lastActionError) { message in + if let message { snackbarMessage = message } + } .onChange(of: broadcastCoordinator.metrics.phase) { _ in updateOverlay() } .alert("Errore", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) { Button("OK") { onFinished() } @@ -143,8 +147,10 @@ struct BroadcastScreen: View { if usesActionScoring { scoreController.applyAction("home_point") } else if usesSetScoring { - await scoreController.applyBoardAction("home_point") - await liveScoreActions(for: match).afterPointChange(score: scoreController.score) + let ok = await scoreController.applyBoardAction("home_point") + if ok { + await liveScoreActions(for: match).afterPointChange(score: scoreController.score) + } } else { scoreController.incrementHome() } @@ -155,8 +161,10 @@ struct BroadcastScreen: View { if usesActionScoring { scoreController.applyAction("away_point") } else if usesSetScoring { - await scoreController.applyBoardAction("away_point") - await liveScoreActions(for: match).afterPointChange(score: scoreController.score) + let ok = await scoreController.applyBoardAction("away_point") + if ok { + await liveScoreActions(for: match).afterPointChange(score: scoreController.score) + } } else { scoreController.incrementAway() } diff --git a/native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift b/native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift index 0f33ce4..f1694ab 100644 --- a/native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift +++ b/native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift @@ -105,7 +105,7 @@ final class MatchScoringRulesTests: XCTestCase { "data": { "home_score": 42, "away_score": 38, "period": 3, "overtime": false } } """.data(using: .utf8)! - let dto = try JSONDecoder().decode(ScoreStateDto.self, from: json) + let dto = try ApiInstant.decoder.decode(ScoreStateDto.self, from: json) let score = dto.toDomain() XCTAssertEqual(score.boardType, "basket") XCTAssertEqual(score.homePoints, 42) diff --git a/native/ios/MatchLiveTvTests/ScoreActionDecodeTests.swift b/native/ios/MatchLiveTvTests/ScoreActionDecodeTests.swift new file mode 100644 index 0000000..24fdb29 --- /dev/null +++ b/native/ios/MatchLiveTvTests/ScoreActionDecodeTests.swift @@ -0,0 +1,36 @@ +import XCTest +@testable import MatchLiveTv + +final class ScoreActionDecodeTests: XCTestCase { + func testDecodesScoreActionResponse() throws { + let json = """ + { + "id": "821ddb81-9dc2-466a-9ad5-2555e78f4653", + "match_id": "e887ce1d-10ee-4680-8323-79bb8f11e0aa", + "status": "live", + "platform": "matchlivetv", + "score": { + "type": "score_update", + "board_type": "volley", + "home_sets": 0, + "away_sets": 0, + "home_points": 1, + "away_points": 0, + "current_set": 1, + "set_partials": [], + "timeout_home": false, + "timeout_away": false, + "period": null, + "data": {} + }, + "set_won": false, + "match_won": false, + "winner": null + } + """.data(using: .utf8)! + let dto = try ApiInstant.decoder.decode(StreamSessionDto.self, from: json) + let score = try XCTUnwrap(dto.score?.toDomain()) + XCTAssertEqual(score.homePoints, 1) + XCTAssertEqual(score.boardType, "volley") + } +} diff --git a/native/ios/MatchLiveTvTests/ScoreControllerTests.swift b/native/ios/MatchLiveTvTests/ScoreControllerTests.swift new file mode 100644 index 0000000..557edc5 --- /dev/null +++ b/native/ios/MatchLiveTvTests/ScoreControllerTests.swift @@ -0,0 +1,28 @@ +import XCTest +@testable import MatchLiveTv + +@MainActor +final class ScoreControllerTests: XCTestCase { + func testIncrementHomeUpdatesPublishedScore() { + let controller = ScoreController( + scoreRepository: ScoreRepository(api: MatchLiveAPI()), + sessionCable: SessionCableService() + ) + controller.bind(sessionId: "session-1", initial: ScoreState()) + controller.incrementHome() + XCTAssertEqual(controller.score.homePoints, 1) + } + + func testApplyBoardActionOptimisticThenConfirm() async { + let api = MatchLiveAPI() + let controller = ScoreController( + scoreRepository: ScoreRepository(api: api), + sessionCable: SessionCableService() + ) + controller.bind(sessionId: "session-1", initial: ScoreState()) + // Senza backend: fallisce e ripristina — ma l'ottimistico deve almeno scattare durante la chiamata. + // Verifichiamo solo che applyOptimistic funzioni via applyAction locale. + controller.applyAction("home_point") + XCTAssertEqual(controller.score.homePoints, 1) + } +}