Allinea iOS a i18n Android 2.0.5 e aggiunge monitoraggio termico nativo.
Completa L10n su login/hub/wizard/broadcast e introduce ThermalStateManager su iOS/Android con degradazione qualità e indicatore in overlay. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
ff4de33cc5
commit
ea5da1eb86
@@ -1,654 +1,272 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
classes = {};
|
||||
objectVersion = 60;
|
||||
objects = {
|
||||
4FC590728C2F47DE94AEADA3 /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = "<group>"; };
|
||||
69F95F7CAF894139BA244AFF /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = "<group>"; };
|
||||
5E30D25B02704FAE9C0A4040 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = "<group>"; };
|
||||
A59093CB86BB41FE9DF37182 /* AppLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppLanguage.swift; path = MatchLiveTv/Core/AppLanguage.swift; sourceTree = "<group>"; };
|
||||
C00F6C82C9A04A50B1D34BA3 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = "<group>"; };
|
||||
6F83B04D0BC84879A07C9BFE /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = "<group>"; };
|
||||
33DEE4675BAB413E9F69549B /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = "<group>"; };
|
||||
14FE2B5F844A482C91245755 /* ThermalState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ThermalState.swift; path = MatchLiveTv/Core/Thermal/ThermalState.swift; sourceTree = "<group>"; };
|
||||
AA9DF2F1C5F04EA198286266 /* ThermalStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ThermalStateManager.swift; path = MatchLiveTv/Core/Thermal/ThermalStateManager.swift; sourceTree = "<group>"; };
|
||||
8EEFEB75DDB04C4986774D4B /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = "<group>"; };
|
||||
C84694361A004982B4007CA5 /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = "<group>"; };
|
||||
1AD71EE7F4A54818A6C427C9 /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = "<group>"; };
|
||||
610902FBCEB7478286C88C08 /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = "<group>"; };
|
||||
DD9687B43B5840DD96FAD1C9 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = "<group>"; };
|
||||
905A6416560A4442AA7D2FCF /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = "<group>"; };
|
||||
9299E98557E04C5F8DC736F8 /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = "<group>"; };
|
||||
49E1D454901048A58DCE7C41 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = "<group>"; };
|
||||
A90696A40A584C5794938FDA /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = "<group>"; };
|
||||
2C3F8293D48D44A6A569F452 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = "<group>"; };
|
||||
347DADA50AE44484ABF19903 /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = "<group>"; };
|
||||
69794703A6D74DB9AA3D16A2 /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = "<group>"; };
|
||||
D274E6DA2B7B47C9822FB517 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = "<group>"; };
|
||||
E4BAEE4AB7834E19BA57C4A1 /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = "<group>"; };
|
||||
1060DB32269548BE895EFA7F /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = "<group>"; };
|
||||
CA265A4F2CAA41DA8F68A35C /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = "<group>"; };
|
||||
0381775B0B1D4378A8BF289A /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = "<group>"; };
|
||||
85D4CBF80A8F447790235E7C /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = "<group>"; };
|
||||
2EA97648294C4D98B3D85EFD /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = "<group>"; };
|
||||
FA4A1042B44B41E0AE768E0C /* BroadcastOrientationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastOrientationPolicy.swift; path = MatchLiveTv/Streaming/BroadcastOrientationPolicy.swift; sourceTree = "<group>"; };
|
||||
97DF04B6B21743BC9615E634 /* BroadcastVideoOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastVideoOrientation.swift; path = MatchLiveTv/Streaming/BroadcastVideoOrientation.swift; sourceTree = "<group>"; };
|
||||
1B031A4D51634E96BBD3D8DA /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = "<group>"; };
|
||||
BC442280384D4194A66F25D2 /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = "<group>"; };
|
||||
EF6E8429A9DF475E9C964A2A /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = "<group>"; };
|
||||
AA419130C5A248DCA3B4A016 /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = "<group>"; };
|
||||
8CF76F8D690C405D9FCC4B8E /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = "<group>"; };
|
||||
E1A1C8A803FB4CA1BA741E57 /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = "<group>"; };
|
||||
195EFB33D08D484489FD721B /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = "<group>"; };
|
||||
DB0DF408F60149008C9644C0 /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = "<group>"; };
|
||||
E9F0583912AA42F787751A7F /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = "<group>"; };
|
||||
FB19CB746D6146D9ABD1A1C4 /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = "<group>"; };
|
||||
CD8C48EA12DB45FF978FA049 /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = "<group>"; };
|
||||
838A41F3BF1E42BDAA2CE2E6 /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = "<group>"; };
|
||||
2074992AD4E94430B9524FB3 /* StreamVideoPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StreamVideoPreset.swift; path = MatchLiveTv/Streaming/StreamVideoPreset.swift; sourceTree = "<group>"; };
|
||||
B51507FF41FB4B80831D70E3 /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = "<group>"; };
|
||||
8EA7E4972BB74FDD884370D0 /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = "<group>"; };
|
||||
CC2B124ACD024538B20863B8 /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = "<group>"; };
|
||||
31B0AEBA3A0140D2BC9698DC /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = "<group>"; };
|
||||
0C84054AB3574CC5B8056694 /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = "<group>"; };
|
||||
D7434175C1E849D7A8308896 /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = "<group>"; };
|
||||
3DE0F41386A741B2A6FC538B /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = "<group>"; };
|
||||
1F6A499FE7D94EF498DA9A84 /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = "<group>"; };
|
||||
DA51BC65490E421AA3D16F02 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = "<group>"; };
|
||||
0A439BB96CCB4C038EEF44DD /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = "<group>"; };
|
||||
5DD00428364442C6B8C55DDF /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = "<group>"; };
|
||||
9F958AB307244896BD8948D2 /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = "<group>"; };
|
||||
21C2A6AB0E164C8CBDF9CF7F /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = "<group>"; };
|
||||
1702AF57020940DEBF0F9BA0 /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = "<group>"; };
|
||||
3004C07E608644AFB636C0C5 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = "<group>"; };
|
||||
DC39718A2EC74B078A0C5DE9 /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = "<group>"; };
|
||||
BCEB22897600469E93762209 /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = "<group>"; };
|
||||
13BC17CE70034FEE97707B21 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ShareSheet.swift; path = MatchLiveTv/UI/System/ShareSheet.swift; sourceTree = "<group>"; };
|
||||
AAAD219E4FA94CF885472711 /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = "<group>"; };
|
||||
29F84FF3F1E74E49A3EA5101 /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = "<group>"; };
|
||||
1875AE0A77B94DF0921EE457 /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = "<group>"; };
|
||||
3168221266004A33BA35042C /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = "<group>"; };
|
||||
C97603C3A54342799CBD0C03 /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = "<group>"; };
|
||||
5AFEAFB3E8F047B9A2B0DD04 /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = "<group>"; };
|
||||
CE7A5A0E6D9A49EA95918E79 /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = "<group>"; };
|
||||
68CB1416AC24491CAA4CE87A /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = "<group>"; };
|
||||
73BB032A0DD74458B0E716C6 /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = "<group>"; };
|
||||
C186E88B35A0443494AE6E8E /* BroadcastOrientationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastOrientationPolicyTests.swift; path = MatchLiveTvTests/BroadcastOrientationPolicyTests.swift; sourceTree = "<group>"; };
|
||||
5751DB993C52460D960B8067 /* LiveBroadcastCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinatorTests.swift; path = MatchLiveTvTests/LiveBroadcastCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||
0E243C00009048F6A778323C /* LiveScoreDialogHostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreDialogHostTests.swift; path = MatchLiveTvTests/LiveScoreDialogHostTests.swift; sourceTree = "<group>"; };
|
||||
634AD5C2E53B4C098582284A /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = "<group>"; };
|
||||
B7DE055717014393B3F62D5A /* ScoreActionDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreActionDecodeTests.swift; path = MatchLiveTvTests/ScoreActionDecodeTests.swift; sourceTree = "<group>"; };
|
||||
C3C1AC6562ED4A908D7C8B0A /* ScoreControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreControllerTests.swift; path = MatchLiveTvTests/ScoreControllerTests.swift; sourceTree = "<group>"; };
|
||||
610A4D03FA384F0287AAF825 /* ThermalAdaptationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ThermalAdaptationTests.swift; path = MatchLiveTvTests/ThermalAdaptationTests.swift; sourceTree = "<group>"; };
|
||||
F68654552B254000B6F0BE97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = "<group>"; };
|
||||
1255FF9134D246ECBE1DF72C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = "<group>"; };
|
||||
6DFD25C0855F482DBEFD8757 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/it.lproj; sourceTree = "<group>"; };
|
||||
12C3FA28B28146B9A8E3A70A /* en.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/en.lproj; sourceTree = "<group>"; };
|
||||
5D760AAFB9C34DACBFC2387A /* fr.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/fr.lproj; sourceTree = "<group>"; };
|
||||
85B0F928887C44128434974E /* de.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/de.lproj; sourceTree = "<group>"; };
|
||||
E907C350C3E5410FB4A2038D /* es.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/es.lproj; sourceTree = "<group>"; };
|
||||
6FF24BB96E9C40F6A9F6E25A /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8F679C9C03AE47D6A028DBAC /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
31418599149E4F7684EABE04 /* HaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 4901BAD7A32F4CFD805083BE; productName = HaishinKit; };
|
||||
1712041BB26748D78A1D8F49 /* RTMPHaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 4901BAD7A32F4CFD805083BE; productName = RTMPHaishinKit; };
|
||||
F7624153A1C84C218B602FFB /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC590728C2F47DE94AEADA3 /* MatchLiveTvApp.swift */; };
|
||||
D973F2B503854C6AA61A1559 /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F95F7CAF894139BA244AFF /* ApiInstant.swift */; };
|
||||
FE2D8C8359CC4DA189DF5DC6 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E30D25B02704FAE9C0A4040 /* AppConfig.swift */; };
|
||||
B607A146AE754F6A819FB186 /* AppLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59093CB86BB41FE9DF37182 /* AppLanguage.swift */; };
|
||||
B0D93B493FD0427CA72F5862 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00F6C82C9A04A50B1D34BA3 /* ColorHex.swift */; };
|
||||
3C223B19BA2149C5B7E5CC5A /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F83B04D0BC84879A07C9BFE /* DeviceTelemetry.swift */; };
|
||||
3D119DE97DE5452D903F364C /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33DEE4675BAB413E9F69549B /* MediaUrl.swift */; };
|
||||
8ABF00BDC15C400A9089FC28 /* ThermalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FE2B5F844A482C91245755 /* ThermalState.swift */; };
|
||||
7D2649B9131F4DDA83F68B72 /* ThermalStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9DF2F1C5F04EA198286266 /* ThermalStateManager.swift */; };
|
||||
AB71730B7CA24D3E8667BCFF /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EEFEB75DDB04C4986774D4B /* TokenStore.swift */; };
|
||||
ADA58448C2344DADAA90F212 /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84694361A004982B4007CA5 /* UserFacingError.swift */; };
|
||||
95F7871858C44379A62012BA /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD71EE7F4A54818A6C427C9 /* ApiDtos.swift */; };
|
||||
3FBBEF8DFF5E4F5D9E34C860 /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610902FBCEB7478286C88C08 /* MatchLiveAPI.swift */; };
|
||||
91694276C76B438F8B52448F /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9687B43B5840DD96FAD1C9 /* AppContainer.swift */; };
|
||||
51D849AFAC8D4F57BEF05CAB /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905A6416560A4442AA7D2FCF /* ActionCableClient.swift */; };
|
||||
D771BABE58A3439CA1768F7F /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9299E98557E04C5F8DC736F8 /* SessionCableService.swift */; };
|
||||
23C149E317BD416D9CF0B171 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E1D454901048A58DCE7C41 /* AuthRepository.swift */; };
|
||||
43C57FF23CB347AC99284468 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90696A40A584C5794938FDA /* MatchRepository.swift */; };
|
||||
8661E674C82545C4AEC0A7CE /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3F8293D48D44A6A569F452 /* MatchSessionLauncher.swift */; };
|
||||
E133D004CF0A4FF7A51A47B1 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347DADA50AE44484ABF19903 /* ScoreRepository.swift */; };
|
||||
72090ACA64CB4885BE7AB333 /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69794703A6D74DB9AA3D16A2 /* SessionRepository.swift */; };
|
||||
D80261301CA740568D8807D2 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274E6DA2B7B47C9822FB517 /* ScoreController.swift */; };
|
||||
9ECD8B486DF84890BD39B108 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BAEE4AB7834E19BA57C4A1 /* WizardSessionHolder.swift */; };
|
||||
C217B8AD49D04976A6D7FC7E /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1060DB32269548BE895EFA7F /* MatchHubFilter.swift */; };
|
||||
978AAF829F654537AC473F52 /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA265A4F2CAA41DA8F68A35C /* MatchScoringRules.swift */; };
|
||||
032C790773A848B7A0434C67 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0381775B0B1D4378A8BF289A /* Models.swift */; };
|
||||
A37BC192D6C9433F83FABAA8 /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D4CBF80A8F447790235E7C /* ScoreState.swift */; };
|
||||
99F1D8A337EE46C8BDD34F60 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA97648294C4D98B3D85EFD /* BroadcastModels.swift */; };
|
||||
F024262D0D0F4B759E1A4F14 /* BroadcastOrientationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4A1042B44B41E0AE768E0C /* BroadcastOrientationPolicy.swift */; };
|
||||
FE9E0B76650246E490722317 /* BroadcastVideoOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DF04B6B21743BC9615E634 /* BroadcastVideoOrientation.swift */; };
|
||||
BD642A0BD19D424985FBF1A3 /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B031A4D51634E96BBD3D8DA /* LiveBroadcastCoordinator.swift */; };
|
||||
B48421E7BD5D48E499948E2E /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC442280384D4194A66F25D2 /* LiveBroadcastEngine.swift */; };
|
||||
C6207D98BB484493BEDD4BBB /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6E8429A9DF475E9C964A2A /* CompactScoreboardElement.swift */; };
|
||||
E715CA8C60E6439683E1DA0E /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA419130C5A248DCA3B4A016 /* OverlayCanvasRenderer.swift */; };
|
||||
35F5E2E5FB0D4B9FA475ADA4 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CF76F8D690C405D9FCC4B8E /* OverlayLogoCache.swift */; };
|
||||
6D8B0F1792E440C19F49AD03 /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1C8A803FB4CA1BA741E57 /* OverlayMappings.swift */; };
|
||||
CC563D93928C438AB0B78E99 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195EFB33D08D484489FD721B /* OverlayRenderer.swift */; };
|
||||
9343740F4EB94F609A4D1A51 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0DF408F60149008C9644C0 /* OverlayState.swift */; };
|
||||
D71C1DFF234742DA81D23AF7 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F0583912AA42F787751A7F /* ScoreboardElement.swift */; };
|
||||
DADD5004759C409F82D4FEE2 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB19CB746D6146D9ABD1A1C4 /* SponsorElement.swift */; };
|
||||
957381C0C3A447E7A3240ACD /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8C48EA12DB45FF978FA049 /* WatermarkElement.swift */; };
|
||||
326AEF0FBFE74201BE70D2DD /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838A41F3BF1E42BDAA2CE2E6 /* LivePreviewView.swift */; };
|
||||
DCC6F79099C641DF9EA6F36F /* StreamVideoPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2074992AD4E94430B9524FB3 /* StreamVideoPreset.swift */; };
|
||||
CA82A2382C4A45BE88276507 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51507FF41FB4B80831D70E3 /* BroadcastControlsOverlay.swift */; };
|
||||
37E8D25C18014E0C8139D84D /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA7E4972BB74FDD884370D0 /* BroadcastScreen.swift */; };
|
||||
67A37D0BB28F4BD1872BACA6 /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2B124ACD024538B20863B8 /* LiveScoreActions.swift */; };
|
||||
7AA96446B33C4ECCB90DDFD0 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B0AEBA3A0140D2BC9698DC /* MatchLiveWordmark.swift */; };
|
||||
599290557AE4430EA30582D7 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84054AB3574CC5B8056694 /* MatchPrimaryButton.swift */; };
|
||||
D71CB837FE844932AA590D30 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7434175C1E849D7A8308896 /* MatchScreenScaffold.swift */; };
|
||||
52806453AD4C4E59BA2F05DE /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE0F41386A741B2A6FC538B /* MatchSecondaryButton.swift */; };
|
||||
FA601701F8684EE2BFDBBBFD /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6A499FE7D94EF498DA9A84 /* MatchStatusBadge.swift */; };
|
||||
8C0225C7F87A4F9A8A0C2919 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA51BC65490E421AA3D16F02 /* LoginScreen.swift */; };
|
||||
7293CD3C1B634333A59FA782 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A439BB96CCB4C038EEF44DD /* MatchesScreen.swift */; };
|
||||
465980490F2D417E87241EBA /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD00428364442C6B8C55DDF /* AppNavHost.swift */; };
|
||||
044E04EEE71D472EA524C555 /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F958AB307244896BD8948D2 /* ModalRoutes.swift */; };
|
||||
52CEBF8B18834CB8B650FE16 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C2A6AB0E164C8CBDF9CF7F /* Routes.swift */; };
|
||||
94ADCCD464DD43149D0C0E60 /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702AF57020940DEBF0F9BA0 /* BroadcastPermissions.swift */; };
|
||||
F23C2967ED7140EB8DCB2D2A /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3004C07E608644AFB636C0C5 /* SplashScreen.swift */; };
|
||||
47AD4ADC4ACE4F6DA7AD708C /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC39718A2EC74B078A0C5DE9 /* KeepScreenOn.swift */; };
|
||||
8B0A9A9DBAF346199EBA21B3 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCEB22897600469E93762209 /* ScreenOrientation.swift */; };
|
||||
D826E98233304F5FB27B27C3 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13BC17CE70034FEE97707B21 /* ShareSheet.swift */; };
|
||||
579A3A8D49DF43E7B11942C6 /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAD219E4FA94CF885472711 /* MatchColors.swift */; };
|
||||
D0BEA6E726C04A95A8CC0018 /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29F84FF3F1E74E49A3EA5101 /* StepMatchScreen.swift */; };
|
||||
15DA64BB40B14C9B899A3729 /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1875AE0A77B94DF0921EE457 /* StepNetworkTestScreen.swift */; };
|
||||
4DF6B21EFDD54DF999385511 /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168221266004A33BA35042C /* StepTransmissionScreen.swift */; };
|
||||
AEF63662B6B5454F92986710 /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97603C3A54342799CBD0C03 /* TeamBrandingEditor.swift */; };
|
||||
78179BA533074F1ABBB7391E /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AFEAFB3E8F047B9A2B0DD04 /* TeamColorPicker.swift */; };
|
||||
45E14846E79845CE9C20C5EC /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A5A0E6D9A49EA95918E79 /* WizardComponents.swift */; };
|
||||
2049733B63794135A4BFBB53 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68CB1416AC24491CAA4CE87A /* WizardShellScreen.swift */; };
|
||||
6B75AFC14BBF4E2B97762ECB /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BB032A0DD74458B0E716C6 /* ApiInstantTests.swift */; };
|
||||
9BA38C8D635F4770A232AE85 /* BroadcastOrientationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C186E88B35A0443494AE6E8E /* BroadcastOrientationPolicyTests.swift */; };
|
||||
1889CDB168794D81A5B24699 /* LiveBroadcastCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5751DB993C52460D960B8067 /* LiveBroadcastCoordinatorTests.swift */; };
|
||||
E25CB4FB944141D0AAB57EDF /* LiveScoreDialogHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E243C00009048F6A778323C /* LiveScoreDialogHostTests.swift */; };
|
||||
6BE3B2533E6A4E3DA32EA972 /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634AD5C2E53B4C098582284A /* MatchScoringRulesTests.swift */; };
|
||||
B88859D9B34E4F289913DDFC /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7DE055717014393B3F62D5A /* ScoreActionDecodeTests.swift */; };
|
||||
B8CC446DB3AE4BABB4D739D1 /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C1AC6562ED4A908D7C8B0A /* ScoreControllerTests.swift */; };
|
||||
6CCB67439127403180D9C697 /* ThermalAdaptationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610A4D03FA384F0287AAF825 /* ThermalAdaptationTests.swift */; };
|
||||
C44DD19CDB6545A381B61D20 /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = F68654552B254000B6F0BE97; };
|
||||
7C8A2DFFAB6B4CB8AA3E4FE8 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 6DFD25C0855F482DBEFD8757; };
|
||||
8D479F8873F94073938D737E /* en.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 12C3FA28B28146B9A8E3A70A; };
|
||||
9A894DBC632C4904BC5D9A78 /* fr.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 5D760AAFB9C34DACBFC2387A; };
|
||||
9246A8532B66443A83535192 /* de.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 85B0F928887C44128434974E; };
|
||||
5DA941A5928A4F638223030C /* es.lproj in Resources */ = {isa = PBXBuildFile; fileRef = E907C350C3E5410FB4A2038D; };
|
||||
B6A69164398F4F9FB84EC475 = {isa = PBXGroup; children = (86BA4622CE11450BAD7902B9, E068E1D4709041789B1E8F5A, 2165E0C34A8B43F28AC2B21C); sourceTree = "<group>"; };
|
||||
86BA4622CE11450BAD7902B9 = {isa = PBXGroup; children = (4FC590728C2F47DE94AEADA3, 69F95F7CAF894139BA244AFF, 5E30D25B02704FAE9C0A4040, A59093CB86BB41FE9DF37182, C00F6C82C9A04A50B1D34BA3, 6F83B04D0BC84879A07C9BFE, 33DEE4675BAB413E9F69549B, 14FE2B5F844A482C91245755, AA9DF2F1C5F04EA198286266, 8EEFEB75DDB04C4986774D4B, C84694361A004982B4007CA5, 1AD71EE7F4A54818A6C427C9, 610902FBCEB7478286C88C08, DD9687B43B5840DD96FAD1C9, 905A6416560A4442AA7D2FCF, 9299E98557E04C5F8DC736F8, 49E1D454901048A58DCE7C41, A90696A40A584C5794938FDA, 2C3F8293D48D44A6A569F452, 347DADA50AE44484ABF19903, 69794703A6D74DB9AA3D16A2, D274E6DA2B7B47C9822FB517, E4BAEE4AB7834E19BA57C4A1, 1060DB32269548BE895EFA7F, CA265A4F2CAA41DA8F68A35C, 0381775B0B1D4378A8BF289A, 85D4CBF80A8F447790235E7C, 2EA97648294C4D98B3D85EFD, FA4A1042B44B41E0AE768E0C, 97DF04B6B21743BC9615E634, 1B031A4D51634E96BBD3D8DA, BC442280384D4194A66F25D2, EF6E8429A9DF475E9C964A2A, AA419130C5A248DCA3B4A016, 8CF76F8D690C405D9FCC4B8E, E1A1C8A803FB4CA1BA741E57, 195EFB33D08D484489FD721B, DB0DF408F60149008C9644C0, E9F0583912AA42F787751A7F, FB19CB746D6146D9ABD1A1C4, CD8C48EA12DB45FF978FA049, 838A41F3BF1E42BDAA2CE2E6, 2074992AD4E94430B9524FB3, B51507FF41FB4B80831D70E3, 8EA7E4972BB74FDD884370D0, CC2B124ACD024538B20863B8, 31B0AEBA3A0140D2BC9698DC, 0C84054AB3574CC5B8056694, D7434175C1E849D7A8308896, 3DE0F41386A741B2A6FC538B, 1F6A499FE7D94EF498DA9A84, DA51BC65490E421AA3D16F02, 0A439BB96CCB4C038EEF44DD, 5DD00428364442C6B8C55DDF, 9F958AB307244896BD8948D2, 21C2A6AB0E164C8CBDF9CF7F, 1702AF57020940DEBF0F9BA0, 3004C07E608644AFB636C0C5, DC39718A2EC74B078A0C5DE9, BCEB22897600469E93762209, 13BC17CE70034FEE97707B21, AAAD219E4FA94CF885472711, 29F84FF3F1E74E49A3EA5101, 1875AE0A77B94DF0921EE457, 3168221266004A33BA35042C, C97603C3A54342799CBD0C03, 5AFEAFB3E8F047B9A2B0DD04, CE7A5A0E6D9A49EA95918E79, 68CB1416AC24491CAA4CE87A, F68654552B254000B6F0BE97, 1255FF9134D246ECBE1DF72C, 6DFD25C0855F482DBEFD8757, 12C3FA28B28146B9A8E3A70A, 5D760AAFB9C34DACBFC2387A, 85B0F928887C44128434974E, E907C350C3E5410FB4A2038D); name = MatchLiveTv; sourceTree = "<group>"; };
|
||||
E068E1D4709041789B1E8F5A = {isa = PBXGroup; children = (73BB032A0DD74458B0E716C6, C186E88B35A0443494AE6E8E, 5751DB993C52460D960B8067, 0E243C00009048F6A778323C, 634AD5C2E53B4C098582284A, B7DE055717014393B3F62D5A, C3C1AC6562ED4A908D7C8B0A, 610A4D03FA384F0287AAF825); name = MatchLiveTvTests; sourceTree = "<group>"; };
|
||||
2165E0C34A8B43F28AC2B21C = {isa = PBXGroup; children = (6FF24BB96E9C40F6A9F6E25A, 8F679C9C03AE47D6A028DBAC); name = Products; sourceTree = "<group>"; };
|
||||
3B35A518C56047A48825F0F8 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (F7624153A1C84C218B602FFB, D973F2B503854C6AA61A1559, FE2D8C8359CC4DA189DF5DC6, B607A146AE754F6A819FB186, B0D93B493FD0427CA72F5862, 3C223B19BA2149C5B7E5CC5A, 3D119DE97DE5452D903F364C, 8ABF00BDC15C400A9089FC28, 7D2649B9131F4DDA83F68B72, AB71730B7CA24D3E8667BCFF, ADA58448C2344DADAA90F212, 95F7871858C44379A62012BA, 3FBBEF8DFF5E4F5D9E34C860, 91694276C76B438F8B52448F, 51D849AFAC8D4F57BEF05CAB, D771BABE58A3439CA1768F7F, 23C149E317BD416D9CF0B171, 43C57FF23CB347AC99284468, 8661E674C82545C4AEC0A7CE, E133D004CF0A4FF7A51A47B1, 72090ACA64CB4885BE7AB333, D80261301CA740568D8807D2, 9ECD8B486DF84890BD39B108, C217B8AD49D04976A6D7FC7E, 978AAF829F654537AC473F52, 032C790773A848B7A0434C67, A37BC192D6C9433F83FABAA8, 99F1D8A337EE46C8BDD34F60, F024262D0D0F4B759E1A4F14, FE9E0B76650246E490722317, BD642A0BD19D424985FBF1A3, B48421E7BD5D48E499948E2E, C6207D98BB484493BEDD4BBB, E715CA8C60E6439683E1DA0E, 35F5E2E5FB0D4B9FA475ADA4, 6D8B0F1792E440C19F49AD03, CC563D93928C438AB0B78E99, 9343740F4EB94F609A4D1A51, D71C1DFF234742DA81D23AF7, DADD5004759C409F82D4FEE2, 957381C0C3A447E7A3240ACD, 326AEF0FBFE74201BE70D2DD, DCC6F79099C641DF9EA6F36F, CA82A2382C4A45BE88276507, 37E8D25C18014E0C8139D84D, 67A37D0BB28F4BD1872BACA6, 7AA96446B33C4ECCB90DDFD0, 599290557AE4430EA30582D7, D71CB837FE844932AA590D30, 52806453AD4C4E59BA2F05DE, FA601701F8684EE2BFDBBBFD, 8C0225C7F87A4F9A8A0C2919, 7293CD3C1B634333A59FA782, 465980490F2D417E87241EBA, 044E04EEE71D472EA524C555, 52CEBF8B18834CB8B650FE16, 94ADCCD464DD43149D0C0E60, F23C2967ED7140EB8DCB2D2A, 47AD4ADC4ACE4F6DA7AD708C, 8B0A9A9DBAF346199EBA21B3, D826E98233304F5FB27B27C3, 579A3A8D49DF43E7B11942C6, D0BEA6E726C04A95A8CC0018, 15DA64BB40B14C9B899A3729, 4DF6B21EFDD54DF999385511, AEF63662B6B5454F92986710, 78179BA533074F1ABBB7391E, 45E14846E79845CE9C20C5EC, 2049733B63794135A4BFBB53); runOnlyForDeploymentPostprocessing = 0; };
|
||||
729C230D5EB64A76BCD517B4 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (6B75AFC14BBF4E2B97762ECB, 9BA38C8D635F4770A232AE85, 1889CDB168794D81A5B24699, E25CB4FB944141D0AAB57EDF, 6BE3B2533E6A4E3DA32EA972, B88859D9B34E4F289913DDFC, B8CC446DB3AE4BABB4D739D1, 6CCB67439127403180D9C697); runOnlyForDeploymentPostprocessing = 0; };
|
||||
6B4D5383389142E7AE9C4F41 = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (C44DD19CDB6545A381B61D20, 7C8A2DFFAB6B4CB8AA3E4FE8, 8D479F8873F94073938D737E, 9A894DBC632C4904BC5D9A78, 9246A8532B66443A83535192, 5DA941A5928A4F638223030C); runOnlyForDeploymentPostprocessing = 0; };
|
||||
AA7CFC56BCC040A4AB487E2C = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
||||
6E9DDC30CE734AC1AB90CC24 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
||||
D3DDF0C54D1744C09B415888 = {isa = XCBuildConfiguration; buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
API_BASE_URL = "https://www.matchlivetv.it";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
01EF2265D1624199B171D57E /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3F630293570453BB85BE288 /* LivePreviewView.swift */; };
|
||||
035B95F7CE8643C48038A114 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F08BB3F014B44F18A1203FE /* ApiDtos.swift */; };
|
||||
0469F385C3BD41489812C2EC /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A71899E82D448797DCE7E8 /* TeamBrandingEditor.swift */; };
|
||||
066B7E46EB6245BA94C12BA6 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395345091E364AFF9FFB34B9 /* AppConfig.swift */; };
|
||||
0D3A76C230854286AA528524 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590CA24C9659490E9A8EC555 /* AppContainer.swift */; };
|
||||
0D666A37E49749C68C0DB06B /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A23B62DE25C94F47B560D742 /* ShareSheet.swift */; };
|
||||
0D81FEE2FA88468C98652291 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29358C1BDC864C408AB4FAD0 /* WizardShellScreen.swift */; };
|
||||
0F6F3A1F9EA74107BDFAF930 /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D8CA843FF54424B181675B /* SessionCableService.swift */; };
|
||||
100AF197AD334A1AB38D271B /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44E182032F64EBBA55ADCEB /* ModalRoutes.swift */; };
|
||||
226769D5E66848FB90B5BFEE /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5964C8AF73D4617BB437E4A /* MatchStatusBadge.swift */; };
|
||||
2B400EFC857D4EDF9CCF73B9 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820BDE9FBCCB4BABACD2AC89 /* BroadcastModels.swift */; };
|
||||
35B3BCE4D68F47C4ABD1E955 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E92E0A7DB54267AC53F60A /* MatchPrimaryButton.swift */; };
|
||||
3E25B3A6F51242B9847DBA6F /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A6EEEAD38F4F509310B82E /* ScoreState.swift */; };
|
||||
3EF78D9394DF41D89E0A43F8 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC3D4B19E974898AA9CAC76 /* SponsorElement.swift */; };
|
||||
3F5B3BD00C6F48379469C171 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC6373E955749DF9AB5DB5B /* TokenStore.swift */; };
|
||||
42EF33716F6843B69945E6AB /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EB02413D134D6289DB90DA /* MatchLiveTvApp.swift */; };
|
||||
46F6F77D670A49BFB4A9B757 /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 109F8ABCCCF649EFB3E6647C /* UserFacingError.swift */; };
|
||||
4C1FDA2D80034A2FB6550DAB /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AF23C6CDE24E1AB227F019 /* MatchRepository.swift */; };
|
||||
4C2639482FE6DA960015AAF3 /* HaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = BC6C276807664B6CB76A8A9D /* HaishinKit */; };
|
||||
4C2639492FE6DA960015AAF3 /* RTMPHaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8F64C5AA49464C419712EAC5 /* RTMPHaishinKit */; };
|
||||
4D74385A3406406ABFA45B44 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA288DB764E24438AE8826DC /* OverlayState.swift */; };
|
||||
4FFA52FD7FF8410AA8FA673E /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9889D83B0787422397EF3181 /* MatchLiveAPI.swift */; };
|
||||
52EB3701353841BDB837C75E /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D95106A21804A2EAEDCECB7 /* WatermarkElement.swift */; };
|
||||
539D6877DF3C478E86200649 /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F2049B408743AA82AF43F3 /* LiveBroadcastCoordinator.swift */; };
|
||||
557CA481D63840D3AA6EFD5B /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D5ED55DB884EA2AC986F61 /* ColorHex.swift */; };
|
||||
5AE324386FA64E1C8BA5F21F /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F961133AE74D4ECE9FDC3EBF /* LoginScreen.swift */; };
|
||||
5BEB4A09B526452D8CCBBE1B /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3691F2136AAD4545B655F544 /* SessionRepository.swift */; };
|
||||
5CA7D12629854A4A9B9DF084 /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C47301A29345819A2CDEC8 /* TeamColorPicker.swift */; };
|
||||
5DB4B7C040F6449198E5D07D /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E91F93272E41A78E4CBECC /* ActionCableClient.swift */; };
|
||||
60D2848965684DB5BFEBD5E3 /* MatchLiveTv/Resources/Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1B4E0A284897418CB3B60E19 /* MatchLiveTv/Resources/Assets.xcassets */; };
|
||||
63CB7A84409D49CEBD74DE4D /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1B3BC7CA8B4B25BCC77F6E /* AuthRepository.swift */; };
|
||||
67818D53F7EA46C2A4850214 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE709C8660614B9DA5A2D657 /* ScoreRepository.swift */; };
|
||||
6B4C7990A5D94A2C8AC69FC1 /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7250114A6FB34430BB20B414 /* WizardComponents.swift */; };
|
||||
6DFF8FAA4ECF46F1928851A4 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B9108C739D484BAD4FF40F /* ScoreboardElement.swift */; };
|
||||
727B74004C3741D9BA510F3B /* LiveScoreDialogHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FB35A567D04317B0421156 /* LiveScoreDialogHostTests.swift */; };
|
||||
735CF034B3BA49BE9BBDD261 /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1265527D9925494BAE66B5A0 /* AppNavHost.swift */; };
|
||||
75D6916EAD4E46CA9E36582C /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A9A8AEACEA48FABD06BD52 /* MatchScoringRulesTests.swift */; };
|
||||
7636A7A0E3CC477799338D29 /* LiveBroadcastCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B55F65BAA6146AF84B010CC /* LiveBroadcastCoordinatorTests.swift */; };
|
||||
794CC6694C74486BAB61CA3B /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0707542005B4DCC90E3A072 /* ApiInstantTests.swift */; };
|
||||
7D0DF967FC03423394839915 /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611ECD9494B94B75B9866791 /* OverlayMappings.swift */; };
|
||||
8179BD051391404B9E2235C2 /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D75DB685D64B93A65E9FC8 /* KeepScreenOn.swift */; };
|
||||
81DB6B2543BC407EBB056C4B /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 985215153D0A44DC8B429731 /* BroadcastScreen.swift */; };
|
||||
833558A9A0A043ECB8E146F6 /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3A1338FBA0411587BB5F4B /* ScoreActionDecodeTests.swift */; };
|
||||
87DB3FBE122F444B87B9D73B /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8B7397C0BD4EC59851185D /* LiveBroadcastEngine.swift */; };
|
||||
F1A2B3C4D5E6478990ABCDEF /* StreamVideoPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6478990ABCDEF /* StreamVideoPreset.swift */; };
|
||||
8841513B4E1743E983E7D2C8 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF2832197EE409AA0B0334C /* BroadcastControlsOverlay.swift */; };
|
||||
8C32CAC69405444DA4BB1E36 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41CF197E5D543CE9A949497 /* ScoreController.swift */; };
|
||||
8D7FCD0052674C0EB4F5C811 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D6F174CE084074972CABF6 /* MatchesScreen.swift */; };
|
||||
8F6D3E3D61584BBA865AD2D7 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10BEC6633F9F446587F1818C /* OverlayLogoCache.swift */; };
|
||||
919B83984A5347989781366F /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48D1A4C43A24A1E815A646D /* MatchScoringRules.swift */; };
|
||||
92E4CAC8F45141A8899749C5 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF32351AC9D47F39C23B29A /* Models.swift */; };
|
||||
AA9BCB0DA5164D43BD7A43FA /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC36378EDDDF44F29ACAD3C6 /* CompactScoreboardElement.swift */; };
|
||||
AEA8CCA49E514EA3984F297E /* BroadcastOrientationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4871BCA51457CB720047B /* BroadcastOrientationPolicy.swift */; };
|
||||
AFA2540B2280448A81DFF5CF /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586F014DA6724EEB810107AD /* Routes.swift */; };
|
||||
B6F8B4BA2E49480DBD2CA846 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E9EFA6947F4A799570A271 /* MatchScreenScaffold.swift */; };
|
||||
C1D2EA29B25C4B06B03B6FFE /* BroadcastVideoOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA50054BF694B9D82EA97ED /* BroadcastVideoOrientation.swift */; };
|
||||
C3B1B44C77084C05B325F935 /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C26A0114F73483A85C70772 /* StepNetworkTestScreen.swift */; };
|
||||
C9B90AAE70C142DEB8996100 /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 354D09291641417F99E27D5A /* ApiInstant.swift */; };
|
||||
A1B2C3D4E5F64789A0B1C2D3 /* AppLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D4E5F6478901A2B3C4D5 /* AppLanguage.swift */; };
|
||||
CF8E45A2BFE34E4BBE259654 /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 258DECC661314224B6A98F47 /* DeviceTelemetry.swift */; };
|
||||
D08665EFFF27465294B620B5 /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F7A645B14A44C69304E136 /* ScoreControllerTests.swift */; };
|
||||
DBBB006ED6C946A2950D805C /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8992D152CF7F478BA6469E36 /* StepMatchScreen.swift */; };
|
||||
DC848628856A4FCB86EE9DA7 /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8736A8A330D4815A3D56104 /* MatchSecondaryButton.swift */; };
|
||||
DF09C441105448CDBAFBD9AF /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FD73A6D4254EAD9484E159 /* OverlayRenderer.swift */; };
|
||||
DFD70B8BFDAD427DA63CD6B1 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7060C0C306D44E9AA8AAF5D4 /* MatchSessionLauncher.swift */; };
|
||||
E01D498C8EF64F85A41EAC54 /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EDDD3FE78C4DC288424A6E /* StepTransmissionScreen.swift */; };
|
||||
E15E57A4C69D49F7AB0C916A /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC868CFCFB6429F84E481B4 /* BroadcastPermissions.swift */; };
|
||||
E4C3F469616A42818F63CE89 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973C4EEE1DCD40448D7F2FAD /* WizardSessionHolder.swift */; };
|
||||
E5ECF142981E4D58B3040FB5 /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E15E2F8F9114F299D8EB017 /* MatchColors.swift */; };
|
||||
E75B706F137249FF807FE0DD /* BroadcastOrientationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FDEC2FCAD14E7C9D671A27 /* BroadcastOrientationPolicyTests.swift */; };
|
||||
EB3031C7972041F2BE6C3933 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0BD001D8384E1CAD5A327B /* MediaUrl.swift */; };
|
||||
ECA9E663AF8B4F19BA4AD6D2 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A279824047D4B7EB08A3F30 /* ScreenOrientation.swift */; };
|
||||
ED730C9B9068449E8D8AF6C8 /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B5BF42CCF934AC7A9516374 /* OverlayCanvasRenderer.swift */; };
|
||||
F0497D8B51DE4173AF395AF8 /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F71C110A07B4F71AB71E6ED /* MatchHubFilter.swift */; };
|
||||
F065E5BE5EC34955B133C189 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6CE9645ACD4713AFC66C58 /* MatchLiveWordmark.swift */; };
|
||||
F8324F96CA074A0B8A71BB7B /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A224E4FD5069421AAB0C4CF6 /* LiveScoreActions.swift */; };
|
||||
FCDD0B3AA1F046B4B912A2F7 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3CE35029F144899C81993B /* SplashScreen.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
B2EE838DC30044B18C9AEE34 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 456AEB32689E48DEA025761E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C1A6F02007F74525A4124BD0;
|
||||
remoteInfo = MatchLiveTv;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0E15E2F8F9114F299D8EB017 /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = "<group>"; };
|
||||
109F8ABCCCF649EFB3E6647C /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = "<group>"; };
|
||||
10BEC6633F9F446587F1818C /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = "<group>"; };
|
||||
11E92E0A7DB54267AC53F60A /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = "<group>"; };
|
||||
1265527D9925494BAE66B5A0 /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = "<group>"; };
|
||||
13EB02413D134D6289DB90DA /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = "<group>"; };
|
||||
1B4E0A284897418CB3B60E19 /* MatchLiveTv/Resources/Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = "<group>"; };
|
||||
1B5BF42CCF934AC7A9516374 /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = "<group>"; };
|
||||
1CC868CFCFB6429F84E481B4 /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = "<group>"; };
|
||||
1D3A1338FBA0411587BB5F4B /* ScoreActionDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreActionDecodeTests.swift; path = MatchLiveTvTests/ScoreActionDecodeTests.swift; sourceTree = "<group>"; };
|
||||
1EF154DE4E184011B1E29261 /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
258DECC661314224B6A98F47 /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = "<group>"; };
|
||||
27FB35A567D04317B0421156 /* LiveScoreDialogHostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreDialogHostTests.swift; path = MatchLiveTvTests/LiveScoreDialogHostTests.swift; sourceTree = "<group>"; };
|
||||
29358C1BDC864C408AB4FAD0 /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = "<group>"; };
|
||||
2DF2832197EE409AA0B0334C /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = "<group>"; };
|
||||
354D09291641417F99E27D5A /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = "<group>"; };
|
||||
B2C3D4E5F6478901A2B3C4D5 /* AppLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppLanguage.swift; path = MatchLiveTv/Core/AppLanguage.swift; sourceTree = "<group>"; };
|
||||
3691F2136AAD4545B655F544 /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = "<group>"; };
|
||||
395345091E364AFF9FFB34B9 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = "<group>"; };
|
||||
49FD73A6D4254EAD9484E159 /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = "<group>"; };
|
||||
49FDEC2FCAD14E7C9D671A27 /* BroadcastOrientationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastOrientationPolicyTests.swift; path = MatchLiveTvTests/BroadcastOrientationPolicyTests.swift; sourceTree = "<group>"; };
|
||||
51A6EEEAD38F4F509310B82E /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = "<group>"; };
|
||||
586F014DA6724EEB810107AD /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = "<group>"; };
|
||||
590CA24C9659490E9A8EC555 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = "<group>"; };
|
||||
5C26A0114F73483A85C70772 /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = "<group>"; };
|
||||
5F8323C8BC81496A8A1DE9B5 /* MatchLiveTv/Resources/Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = "<group>"; };
|
||||
611ECD9494B94B75B9866791 /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = "<group>"; };
|
||||
63D6F174CE084074972CABF6 /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = "<group>"; };
|
||||
68C47301A29345819A2CDEC8 /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = "<group>"; };
|
||||
68D5ED55DB884EA2AC986F61 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = "<group>"; };
|
||||
6C6CE9645ACD4713AFC66C58 /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = "<group>"; };
|
||||
6DF32351AC9D47F39C23B29A /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = "<group>"; };
|
||||
7060C0C306D44E9AA8AAF5D4 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = "<group>"; };
|
||||
7250114A6FB34430BB20B414 /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = "<group>"; };
|
||||
77A71899E82D448797DCE7E8 /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = "<group>"; };
|
||||
7B55F65BAA6146AF84B010CC /* LiveBroadcastCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinatorTests.swift; path = MatchLiveTvTests/LiveBroadcastCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||
7D95106A21804A2EAEDCECB7 /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = "<group>"; };
|
||||
7F08BB3F014B44F18A1203FE /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = "<group>"; };
|
||||
820BDE9FBCCB4BABACD2AC89 /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = "<group>"; };
|
||||
87A9A8AEACEA48FABD06BD52 /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = "<group>"; };
|
||||
8992D152CF7F478BA6469E36 /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = "<group>"; };
|
||||
8A279824047D4B7EB08A3F30 /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = "<group>"; };
|
||||
8F71C110A07B4F71AB71E6ED /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = "<group>"; };
|
||||
94EDDD3FE78C4DC288424A6E /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = "<group>"; };
|
||||
973C4EEE1DCD40448D7F2FAD /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = "<group>"; };
|
||||
985215153D0A44DC8B429731 /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = "<group>"; };
|
||||
9889D83B0787422397EF3181 /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = "<group>"; };
|
||||
A1D75DB685D64B93A65E9FC8 /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = "<group>"; };
|
||||
A224E4FD5069421AAB0C4CF6 /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = "<group>"; };
|
||||
A23B62DE25C94F47B560D742 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ShareSheet.swift; path = MatchLiveTv/UI/System/ShareSheet.swift; sourceTree = "<group>"; };
|
||||
A3D8CA843FF54424B181675B /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = "<group>"; };
|
||||
A3F2049B408743AA82AF43F3 /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = "<group>"; };
|
||||
A5964C8AF73D4617BB437E4A /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = "<group>"; };
|
||||
AB1B3BC7CA8B4B25BCC77F6E /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = "<group>"; };
|
||||
ABC6373E955749DF9AB5DB5B /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = "<group>"; };
|
||||
AC36378EDDDF44F29ACAD3C6 /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = "<group>"; };
|
||||
B2B9108C739D484BAD4FF40F /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = "<group>"; };
|
||||
B8736A8A330D4815A3D56104 /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = "<group>"; };
|
||||
BA288DB764E24438AE8826DC /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = "<group>"; };
|
||||
BC8B7397C0BD4EC59851185D /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = "<group>"; };
|
||||
A1B2C3D4E5F6478990ABCDEF /* StreamVideoPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StreamVideoPreset.swift; path = MatchLiveTv/Streaming/StreamVideoPreset.swift; sourceTree = "<group>"; };
|
||||
BCA50054BF694B9D82EA97ED /* BroadcastVideoOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastVideoOrientation.swift; path = MatchLiveTv/Streaming/BroadcastVideoOrientation.swift; sourceTree = "<group>"; };
|
||||
BDC3D4B19E974898AA9CAC76 /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = "<group>"; };
|
||||
BFE4871BCA51457CB720047B /* BroadcastOrientationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastOrientationPolicy.swift; path = MatchLiveTv/Streaming/BroadcastOrientationPolicy.swift; sourceTree = "<group>"; };
|
||||
C2AF23C6CDE24E1AB227F019 /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = "<group>"; };
|
||||
C44E182032F64EBBA55ADCEB /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = "<group>"; };
|
||||
C7E9EFA6947F4A799570A271 /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = "<group>"; };
|
||||
C8F7A645B14A44C69304E136 /* ScoreControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreControllerTests.swift; path = MatchLiveTvTests/ScoreControllerTests.swift; sourceTree = "<group>"; };
|
||||
CB3CE35029F144899C81993B /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = "<group>"; };
|
||||
CE709C8660614B9DA5A2D657 /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = "<group>"; };
|
||||
D48D1A4C43A24A1E815A646D /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = "<group>"; };
|
||||
DE0BD001D8384E1CAD5A327B /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = "<group>"; };
|
||||
E3F630293570453BB85BE288 /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = "<group>"; };
|
||||
E8E91F93272E41A78E4CBECC /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = "<group>"; };
|
||||
F0707542005B4DCC90E3A072 /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = "<group>"; };
|
||||
F41CF197E5D543CE9A949497 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = "<group>"; };
|
||||
F7FCFC922786459EAF4D1813 /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F961133AE74D4ECE9FDC3EBF /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
3793049D651342FCB0DCB8E7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4C2639492FE6DA960015AAF3 /* RTMPHaishinKit in Frameworks */,
|
||||
4C2639482FE6DA960015AAF3 /* HaishinKit in Frameworks */,
|
||||
ENABLE_TESTABILITY = YES;
|
||||
}; name = Debug; };
|
||||
974D195BCA9F41ECA825E63D = {isa = XCBuildConfiguration; buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
CBDEBD8700894FF8ABB072C4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
043A9CA61B924E8AA7DD8BAC /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F7FCFC922786459EAF4D1813 /* MatchLiveTv.app */,
|
||||
1EF154DE4E184011B1E29261 /* MatchLiveTvTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3295305662B540D3B837C583 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
957E89149C624576B613E912 /* MatchLiveTv */,
|
||||
F3A8835D1D314FBC9DE71AF9 /* MatchLiveTvTests */,
|
||||
043A9CA61B924E8AA7DD8BAC /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
957E89149C624576B613E912 /* MatchLiveTv */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13EB02413D134D6289DB90DA /* MatchLiveTvApp.swift */,
|
||||
354D09291641417F99E27D5A /* ApiInstant.swift */,
|
||||
B2C3D4E5F6478901A2B3C4D5 /* AppLanguage.swift */,
|
||||
395345091E364AFF9FFB34B9 /* AppConfig.swift */,
|
||||
68D5ED55DB884EA2AC986F61 /* ColorHex.swift */,
|
||||
258DECC661314224B6A98F47 /* DeviceTelemetry.swift */,
|
||||
DE0BD001D8384E1CAD5A327B /* MediaUrl.swift */,
|
||||
ABC6373E955749DF9AB5DB5B /* TokenStore.swift */,
|
||||
109F8ABCCCF649EFB3E6647C /* UserFacingError.swift */,
|
||||
7F08BB3F014B44F18A1203FE /* ApiDtos.swift */,
|
||||
9889D83B0787422397EF3181 /* MatchLiveAPI.swift */,
|
||||
590CA24C9659490E9A8EC555 /* AppContainer.swift */,
|
||||
E8E91F93272E41A78E4CBECC /* ActionCableClient.swift */,
|
||||
A3D8CA843FF54424B181675B /* SessionCableService.swift */,
|
||||
AB1B3BC7CA8B4B25BCC77F6E /* AuthRepository.swift */,
|
||||
C2AF23C6CDE24E1AB227F019 /* MatchRepository.swift */,
|
||||
7060C0C306D44E9AA8AAF5D4 /* MatchSessionLauncher.swift */,
|
||||
CE709C8660614B9DA5A2D657 /* ScoreRepository.swift */,
|
||||
3691F2136AAD4545B655F544 /* SessionRepository.swift */,
|
||||
F41CF197E5D543CE9A949497 /* ScoreController.swift */,
|
||||
973C4EEE1DCD40448D7F2FAD /* WizardSessionHolder.swift */,
|
||||
8F71C110A07B4F71AB71E6ED /* MatchHubFilter.swift */,
|
||||
D48D1A4C43A24A1E815A646D /* MatchScoringRules.swift */,
|
||||
6DF32351AC9D47F39C23B29A /* Models.swift */,
|
||||
51A6EEEAD38F4F509310B82E /* ScoreState.swift */,
|
||||
820BDE9FBCCB4BABACD2AC89 /* BroadcastModels.swift */,
|
||||
BFE4871BCA51457CB720047B /* BroadcastOrientationPolicy.swift */,
|
||||
BCA50054BF694B9D82EA97ED /* BroadcastVideoOrientation.swift */,
|
||||
A3F2049B408743AA82AF43F3 /* LiveBroadcastCoordinator.swift */,
|
||||
BC8B7397C0BD4EC59851185D /* LiveBroadcastEngine.swift */,
|
||||
A1B2C3D4E5F6478990ABCDEF /* StreamVideoPreset.swift */,
|
||||
AC36378EDDDF44F29ACAD3C6 /* CompactScoreboardElement.swift */,
|
||||
1B5BF42CCF934AC7A9516374 /* OverlayCanvasRenderer.swift */,
|
||||
10BEC6633F9F446587F1818C /* OverlayLogoCache.swift */,
|
||||
611ECD9494B94B75B9866791 /* OverlayMappings.swift */,
|
||||
49FD73A6D4254EAD9484E159 /* OverlayRenderer.swift */,
|
||||
BA288DB764E24438AE8826DC /* OverlayState.swift */,
|
||||
B2B9108C739D484BAD4FF40F /* ScoreboardElement.swift */,
|
||||
BDC3D4B19E974898AA9CAC76 /* SponsorElement.swift */,
|
||||
7D95106A21804A2EAEDCECB7 /* WatermarkElement.swift */,
|
||||
E3F630293570453BB85BE288 /* LivePreviewView.swift */,
|
||||
2DF2832197EE409AA0B0334C /* BroadcastControlsOverlay.swift */,
|
||||
985215153D0A44DC8B429731 /* BroadcastScreen.swift */,
|
||||
A224E4FD5069421AAB0C4CF6 /* LiveScoreActions.swift */,
|
||||
6C6CE9645ACD4713AFC66C58 /* MatchLiveWordmark.swift */,
|
||||
11E92E0A7DB54267AC53F60A /* MatchPrimaryButton.swift */,
|
||||
C7E9EFA6947F4A799570A271 /* MatchScreenScaffold.swift */,
|
||||
B8736A8A330D4815A3D56104 /* MatchSecondaryButton.swift */,
|
||||
A5964C8AF73D4617BB437E4A /* MatchStatusBadge.swift */,
|
||||
F961133AE74D4ECE9FDC3EBF /* LoginScreen.swift */,
|
||||
63D6F174CE084074972CABF6 /* MatchesScreen.swift */,
|
||||
1265527D9925494BAE66B5A0 /* AppNavHost.swift */,
|
||||
C44E182032F64EBBA55ADCEB /* ModalRoutes.swift */,
|
||||
586F014DA6724EEB810107AD /* Routes.swift */,
|
||||
1CC868CFCFB6429F84E481B4 /* BroadcastPermissions.swift */,
|
||||
CB3CE35029F144899C81993B /* SplashScreen.swift */,
|
||||
A1D75DB685D64B93A65E9FC8 /* KeepScreenOn.swift */,
|
||||
8A279824047D4B7EB08A3F30 /* ScreenOrientation.swift */,
|
||||
A23B62DE25C94F47B560D742 /* ShareSheet.swift */,
|
||||
0E15E2F8F9114F299D8EB017 /* MatchColors.swift */,
|
||||
8992D152CF7F478BA6469E36 /* StepMatchScreen.swift */,
|
||||
5C26A0114F73483A85C70772 /* StepNetworkTestScreen.swift */,
|
||||
94EDDD3FE78C4DC288424A6E /* StepTransmissionScreen.swift */,
|
||||
77A71899E82D448797DCE7E8 /* TeamBrandingEditor.swift */,
|
||||
68C47301A29345819A2CDEC8 /* TeamColorPicker.swift */,
|
||||
7250114A6FB34430BB20B414 /* WizardComponents.swift */,
|
||||
29358C1BDC864C408AB4FAD0 /* WizardShellScreen.swift */,
|
||||
1B4E0A284897418CB3B60E19 /* MatchLiveTv/Resources/Assets.xcassets */,
|
||||
5F8323C8BC81496A8A1DE9B5 /* MatchLiveTv/Resources/Info.plist */,
|
||||
);
|
||||
name = MatchLiveTv;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3A8835D1D314FBC9DE71AF9 /* MatchLiveTvTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F0707542005B4DCC90E3A072 /* ApiInstantTests.swift */,
|
||||
49FDEC2FCAD14E7C9D671A27 /* BroadcastOrientationPolicyTests.swift */,
|
||||
7B55F65BAA6146AF84B010CC /* LiveBroadcastCoordinatorTests.swift */,
|
||||
27FB35A567D04317B0421156 /* LiveScoreDialogHostTests.swift */,
|
||||
87A9A8AEACEA48FABD06BD52 /* MatchScoringRulesTests.swift */,
|
||||
1D3A1338FBA0411587BB5F4B /* ScoreActionDecodeTests.swift */,
|
||||
C8F7A645B14A44C69304E136 /* ScoreControllerTests.swift */,
|
||||
);
|
||||
name = MatchLiveTvTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
C1A6F02007F74525A4124BD0 /* MatchLiveTv */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B85A15C67B064173ACD38B52 /* Build configuration list for PBXNativeTarget "MatchLiveTv" */;
|
||||
buildPhases = (
|
||||
CECE99C00DA949F1A6F9E016 /* Sources */,
|
||||
3793049D651342FCB0DCB8E7 /* Frameworks */,
|
||||
702AFFCE779D46568D8EFD3B /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MatchLiveTv;
|
||||
packageProductDependencies = (
|
||||
BC6C276807664B6CB76A8A9D /* HaishinKit */,
|
||||
8F64C5AA49464C419712EAC5 /* RTMPHaishinKit */,
|
||||
);
|
||||
productName = MatchLiveTv;
|
||||
productReference = F7FCFC922786459EAF4D1813 /* MatchLiveTv.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
D2DB6900C94F48F0A49F19BB /* MatchLiveTvTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 557C4FE091C3492A98F420D2 /* Build configuration list for PBXNativeTarget "MatchLiveTvTests" */;
|
||||
buildPhases = (
|
||||
E214E6AEA37A48A8A6B5FCF4 /* Sources */,
|
||||
CBDEBD8700894FF8ABB072C4 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
7AF81B34F40A4032BF55ECCC /* PBXTargetDependency */,
|
||||
);
|
||||
name = MatchLiveTvTests;
|
||||
productName = MatchLiveTvTests;
|
||||
productReference = 1EF154DE4E184011B1E29261 /* MatchLiveTvTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
456AEB32689E48DEA025761E /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 0;
|
||||
LastSwiftUpdateCheck = 1600;
|
||||
};
|
||||
buildConfigurationList = D9A53CE9A9354D1598FD2265 /* Build configuration list for PBXProject "MatchLiveTv" */;
|
||||
compatibilityVersion = "Xcode 15.0";
|
||||
developmentRegion = it;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
it,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 3295305662B540D3B837C583;
|
||||
packageReferences = (
|
||||
6645D37DD2684C94860AEF1B /* XCRemoteSwiftPackageReference "HaishinKit" */,
|
||||
);
|
||||
productRefGroup = 043A9CA61B924E8AA7DD8BAC /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
C1A6F02007F74525A4124BD0 /* MatchLiveTv */,
|
||||
D2DB6900C94F48F0A49F19BB /* MatchLiveTvTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
702AFFCE779D46568D8EFD3B /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
60D2848965684DB5BFEBD5E3 /* MatchLiveTv/Resources/Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CECE99C00DA949F1A6F9E016 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
42EF33716F6843B69945E6AB /* MatchLiveTvApp.swift in Sources */,
|
||||
C9B90AAE70C142DEB8996100 /* ApiInstant.swift in Sources */,
|
||||
A1B2C3D4E5F64789A0B1C2D3 /* AppLanguage.swift in Sources */,
|
||||
066B7E46EB6245BA94C12BA6 /* AppConfig.swift in Sources */,
|
||||
557CA481D63840D3AA6EFD5B /* ColorHex.swift in Sources */,
|
||||
CF8E45A2BFE34E4BBE259654 /* DeviceTelemetry.swift in Sources */,
|
||||
EB3031C7972041F2BE6C3933 /* MediaUrl.swift in Sources */,
|
||||
3F5B3BD00C6F48379469C171 /* TokenStore.swift in Sources */,
|
||||
46F6F77D670A49BFB4A9B757 /* UserFacingError.swift in Sources */,
|
||||
035B95F7CE8643C48038A114 /* ApiDtos.swift in Sources */,
|
||||
4FFA52FD7FF8410AA8FA673E /* MatchLiveAPI.swift in Sources */,
|
||||
0D3A76C230854286AA528524 /* AppContainer.swift in Sources */,
|
||||
5DB4B7C040F6449198E5D07D /* ActionCableClient.swift in Sources */,
|
||||
0F6F3A1F9EA74107BDFAF930 /* SessionCableService.swift in Sources */,
|
||||
63CB7A84409D49CEBD74DE4D /* AuthRepository.swift in Sources */,
|
||||
4C1FDA2D80034A2FB6550DAB /* MatchRepository.swift in Sources */,
|
||||
DFD70B8BFDAD427DA63CD6B1 /* MatchSessionLauncher.swift in Sources */,
|
||||
67818D53F7EA46C2A4850214 /* ScoreRepository.swift in Sources */,
|
||||
5BEB4A09B526452D8CCBBE1B /* SessionRepository.swift in Sources */,
|
||||
8C32CAC69405444DA4BB1E36 /* ScoreController.swift in Sources */,
|
||||
E4C3F469616A42818F63CE89 /* WizardSessionHolder.swift in Sources */,
|
||||
F0497D8B51DE4173AF395AF8 /* MatchHubFilter.swift in Sources */,
|
||||
919B83984A5347989781366F /* MatchScoringRules.swift in Sources */,
|
||||
92E4CAC8F45141A8899749C5 /* Models.swift in Sources */,
|
||||
3E25B3A6F51242B9847DBA6F /* ScoreState.swift in Sources */,
|
||||
2B400EFC857D4EDF9CCF73B9 /* BroadcastModels.swift in Sources */,
|
||||
AEA8CCA49E514EA3984F297E /* BroadcastOrientationPolicy.swift in Sources */,
|
||||
C1D2EA29B25C4B06B03B6FFE /* BroadcastVideoOrientation.swift in Sources */,
|
||||
539D6877DF3C478E86200649 /* LiveBroadcastCoordinator.swift in Sources */,
|
||||
87DB3FBE122F444B87B9D73B /* LiveBroadcastEngine.swift in Sources */,
|
||||
F1A2B3C4D5E6478990ABCDEF /* StreamVideoPreset.swift in Sources */,
|
||||
AA9BCB0DA5164D43BD7A43FA /* CompactScoreboardElement.swift in Sources */,
|
||||
ED730C9B9068449E8D8AF6C8 /* OverlayCanvasRenderer.swift in Sources */,
|
||||
8F6D3E3D61584BBA865AD2D7 /* OverlayLogoCache.swift in Sources */,
|
||||
7D0DF967FC03423394839915 /* OverlayMappings.swift in Sources */,
|
||||
DF09C441105448CDBAFBD9AF /* OverlayRenderer.swift in Sources */,
|
||||
4D74385A3406406ABFA45B44 /* OverlayState.swift in Sources */,
|
||||
6DFF8FAA4ECF46F1928851A4 /* ScoreboardElement.swift in Sources */,
|
||||
3EF78D9394DF41D89E0A43F8 /* SponsorElement.swift in Sources */,
|
||||
52EB3701353841BDB837C75E /* WatermarkElement.swift in Sources */,
|
||||
01EF2265D1624199B171D57E /* LivePreviewView.swift in Sources */,
|
||||
8841513B4E1743E983E7D2C8 /* BroadcastControlsOverlay.swift in Sources */,
|
||||
81DB6B2543BC407EBB056C4B /* BroadcastScreen.swift in Sources */,
|
||||
F8324F96CA074A0B8A71BB7B /* LiveScoreActions.swift in Sources */,
|
||||
F065E5BE5EC34955B133C189 /* MatchLiveWordmark.swift in Sources */,
|
||||
35B3BCE4D68F47C4ABD1E955 /* MatchPrimaryButton.swift in Sources */,
|
||||
B6F8B4BA2E49480DBD2CA846 /* MatchScreenScaffold.swift in Sources */,
|
||||
DC848628856A4FCB86EE9DA7 /* MatchSecondaryButton.swift in Sources */,
|
||||
226769D5E66848FB90B5BFEE /* MatchStatusBadge.swift in Sources */,
|
||||
5AE324386FA64E1C8BA5F21F /* LoginScreen.swift in Sources */,
|
||||
8D7FCD0052674C0EB4F5C811 /* MatchesScreen.swift in Sources */,
|
||||
735CF034B3BA49BE9BBDD261 /* AppNavHost.swift in Sources */,
|
||||
100AF197AD334A1AB38D271B /* ModalRoutes.swift in Sources */,
|
||||
AFA2540B2280448A81DFF5CF /* Routes.swift in Sources */,
|
||||
E15E57A4C69D49F7AB0C916A /* BroadcastPermissions.swift in Sources */,
|
||||
FCDD0B3AA1F046B4B912A2F7 /* SplashScreen.swift in Sources */,
|
||||
8179BD051391404B9E2235C2 /* KeepScreenOn.swift in Sources */,
|
||||
ECA9E663AF8B4F19BA4AD6D2 /* ScreenOrientation.swift in Sources */,
|
||||
0D666A37E49749C68C0DB06B /* ShareSheet.swift in Sources */,
|
||||
E5ECF142981E4D58B3040FB5 /* MatchColors.swift in Sources */,
|
||||
DBBB006ED6C946A2950D805C /* StepMatchScreen.swift in Sources */,
|
||||
C3B1B44C77084C05B325F935 /* StepNetworkTestScreen.swift in Sources */,
|
||||
E01D498C8EF64F85A41EAC54 /* StepTransmissionScreen.swift in Sources */,
|
||||
0469F385C3BD41489812C2EC /* TeamBrandingEditor.swift in Sources */,
|
||||
5CA7D12629854A4A9B9DF084 /* TeamColorPicker.swift in Sources */,
|
||||
6B4C7990A5D94A2C8AC69FC1 /* WizardComponents.swift in Sources */,
|
||||
0D81FEE2FA88468C98652291 /* WizardShellScreen.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
E214E6AEA37A48A8A6B5FCF4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
794CC6694C74486BAB61CA3B /* ApiInstantTests.swift in Sources */,
|
||||
E75B706F137249FF807FE0DD /* BroadcastOrientationPolicyTests.swift in Sources */,
|
||||
7636A7A0E3CC477799338D29 /* LiveBroadcastCoordinatorTests.swift in Sources */,
|
||||
727B74004C3741D9BA510F3B /* LiveScoreDialogHostTests.swift in Sources */,
|
||||
75D6916EAD4E46CA9E36582C /* MatchScoringRulesTests.swift in Sources */,
|
||||
833558A9A0A043ECB8E146F6 /* ScoreActionDecodeTests.swift in Sources */,
|
||||
D08665EFFF27465294B620B5 /* ScoreControllerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
7AF81B34F40A4032BF55ECCC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C1A6F02007F74525A4124BD0 /* MatchLiveTv */;
|
||||
targetProxy = B2EE838DC30044B18C9AEE34 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
225662CBBF56481BAEE29648 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
25DF4C4290CD4B85A1FB5B00 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
API_BASE_URL = "https://www.matchlivetv.it";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
2619E7D33A5048E098621AAC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
908522446C5E40DBBCEC19A6 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
9D109C6FCBD247FEBF780C10 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F65D58B3D4D6450FA8626BAA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
API_BASE_URL = "https://www.matchlivetv.it";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
557C4FE091C3492A98F420D2 /* Build configuration list for PBXNativeTarget "MatchLiveTvTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
225662CBBF56481BAEE29648 /* Debug */,
|
||||
2619E7D33A5048E098621AAC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B85A15C67B064173ACD38B52 /* Build configuration list for PBXNativeTarget "MatchLiveTv" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F65D58B3D4D6450FA8626BAA /* Debug */,
|
||||
25DF4C4290CD4B85A1FB5B00 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D9A53CE9A9354D1598FD2265 /* Build configuration list for PBXProject "MatchLiveTv" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
908522446C5E40DBBCEC19A6 /* Debug */,
|
||||
9D109C6FCBD247FEBF780C10 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
6645D37DD2684C94860AEF1B /* XCRemoteSwiftPackageReference "HaishinKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/shogo4405/HaishinKit.swift";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
8F64C5AA49464C419712EAC5 /* RTMPHaishinKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6645D37DD2684C94860AEF1B /* XCRemoteSwiftPackageReference "HaishinKit" */;
|
||||
productName = RTMPHaishinKit;
|
||||
};
|
||||
BC6C276807664B6CB76A8A9D /* HaishinKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6645D37DD2684C94860AEF1B /* XCRemoteSwiftPackageReference "HaishinKit" */;
|
||||
productName = HaishinKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
API_BASE_URL = "https://www.matchlivetv.it";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
}; name = Release; };
|
||||
BD9014D2E1B34341B2930049 = {isa = XCBuildConfiguration; buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
|
||||
}; name = Debug; };
|
||||
F613BB5D3D6744F3BD58BCBB = {isa = XCBuildConfiguration; buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
|
||||
}; name = Release; };
|
||||
E2D337A0090F45C3BD12CEA4 = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Debug; };
|
||||
DF036DDA33454934B5DBA96F = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Release; };
|
||||
51779CE259A14053B83F1D94 = {isa = XCConfigurationList; buildConfigurations = (D3DDF0C54D1744C09B415888, 974D195BCA9F41ECA825E63D); defaultConfigurationName = Release; };
|
||||
70CF9007D5E9469CB989FABF = {isa = XCConfigurationList; buildConfigurations = (BD9014D2E1B34341B2930049, F613BB5D3D6744F3BD58BCBB); defaultConfigurationName = Release; };
|
||||
86E5AF075085439BA443DBA7 = {isa = XCConfigurationList; buildConfigurations = (E2D337A0090F45C3BD12CEA4, DF036DDA33454934B5DBA96F); defaultConfigurationName = Release; };
|
||||
768EC47B451F4731A05BE1F9 = {isa = PBXNativeTarget; buildConfigurationList = 51779CE259A14053B83F1D94; buildPhases = (3B35A518C56047A48825F0F8, AA7CFC56BCC040A4AB487E2C, 6B4D5383389142E7AE9C4F41); buildRules = (); dependencies = (); name = MatchLiveTv; packageProductDependencies = (31418599149E4F7684EABE04, 1712041BB26748D78A1D8F49); productName = MatchLiveTv; productReference = 6FF24BB96E9C40F6A9F6E25A; productType = "com.apple.product-type.application"; };
|
||||
057F8600A1DB4A8DB4A46300 = {isa = PBXContainerItemProxy; containerPortal = 1906C5F192A44C6284A9FC4C /* Project object */; proxyType = 1; remoteGlobalIDString = 768EC47B451F4731A05BE1F9; remoteInfo = MatchLiveTv; };
|
||||
56E58DFB903041BB95600D33 = {isa = PBXTargetDependency; target = 768EC47B451F4731A05BE1F9 /* MatchLiveTv */; targetProxy = 057F8600A1DB4A8DB4A46300 /* PBXContainerItemProxy */; };
|
||||
F6079500E62048DB89AE3866 = {isa = PBXNativeTarget; buildConfigurationList = 70CF9007D5E9469CB989FABF; buildPhases = (729C230D5EB64A76BCD517B4, 6E9DDC30CE734AC1AB90CC24); buildRules = (); dependencies = (56E58DFB903041BB95600D33); name = MatchLiveTvTests; productName = MatchLiveTvTests; productReference = 8F679C9C03AE47D6A028DBAC; productType = "com.apple.product-type.bundle.unit-test"; };
|
||||
4901BAD7A32F4CFD805083BE = {isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/shogo4405/HaishinKit.swift"; requirement = {kind = upToNextMajorVersion; minimumVersion = 2.0.0;}; };
|
||||
1906C5F192A44C6284A9FC4C = {isa = PBXProject; attributes = {BuildIndependentTargetsInParallel = 0; LastSwiftUpdateCheck = 1600;}; buildConfigurationList = 86E5AF075085439BA443DBA7; compatibilityVersion = "Xcode 15.0"; developmentRegion = it; mainGroup = B6A69164398F4F9FB84EC475; packageReferences = (4901BAD7A32F4CFD805083BE); productRefGroup = 2165E0C34A8B43F28AC2B21C; targets = (768EC47B451F4731A05BE1F9, F6079500E62048DB89AE3866); };
|
||||
};
|
||||
rootObject = 456AEB32689E48DEA025761E /* Project object */;
|
||||
rootObject = 1906C5F192A44C6284A9FC4C /* Project object */;
|
||||
}
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="C1A6F02007F74525A4124BD0" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="768EC47B451F4731A05BE1F9" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry buildForTesting="YES" buildForRunning="NO" buildForProfiling="NO" buildForArchiving="NO" buildForAnalyzing="NO">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="D2DB6900C94F48F0A49F19BB" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="F6079500E62048DB89AE3866" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv="YES">
|
||||
<Testables>
|
||||
<TestableReference skipped="NO">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="D2DB6900C94F48F0A49F19BB" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="F6079500E62048DB89AE3866" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES">
|
||||
<BuildableProductRunnable runnableDebuggingMode="0">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="C1A6F02007F74525A4124BD0" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="768EC47B451F4731A05BE1F9" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES">
|
||||
<BuildableProductRunnable runnableDebuggingMode="0">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="C1A6F02007F74525A4124BD0" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="768EC47B451F4731A05BE1F9" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction buildConfiguration="Debug"/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,48 +4,33 @@ import Network
|
||||
|
||||
struct DeviceHealth: Sendable {
|
||||
let batteryPercent: Int
|
||||
let batteryTempC: Double?
|
||||
let thermalLevel: Int
|
||||
let thermalLabel: String
|
||||
let thermalState: ThermalState
|
||||
let networkType: String
|
||||
}
|
||||
|
||||
enum DeviceTelemetry {
|
||||
static func snapshot() -> DeviceHealth {
|
||||
static func snapshot(thermalState: ThermalState? = nil) -> DeviceHealth {
|
||||
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||
let level = Int((UIDevice.current.batteryLevel >= 0 ? UIDevice.current.batteryLevel : 1) * 100)
|
||||
let thermal = ProcessInfo.processInfo.thermalState
|
||||
let (thermalLevel, label) = thermalInfo(thermal)
|
||||
let thermal = thermalState ?? ThermalState.from(ProcessInfo.processInfo.thermalState)
|
||||
return DeviceHealth(
|
||||
batteryPercent: level,
|
||||
batteryTempC: nil,
|
||||
thermalLevel: thermalLevel,
|
||||
thermalLabel: label,
|
||||
thermalState: thermal,
|
||||
networkType: currentNetworkType()
|
||||
)
|
||||
}
|
||||
|
||||
private static func thermalInfo(_ state: ProcessInfo.ThermalState) -> (Int, String) {
|
||||
switch state {
|
||||
case .nominal: return (0, "OK")
|
||||
case .fair: return (1, "Caldo")
|
||||
case .serious: return (2, "Surriscaldamento")
|
||||
case .critical: return (3, "Troppo caldo")
|
||||
@unknown default: return (0, "OK")
|
||||
}
|
||||
}
|
||||
|
||||
private static func currentNetworkType() -> String {
|
||||
let monitor = NWPathMonitor()
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result = "Sconosciuto"
|
||||
var result = L10n.t("network.type.unknown")
|
||||
monitor.pathUpdateHandler = { path in
|
||||
if path.usesInterfaceType(.wifi) {
|
||||
result = "WiFi"
|
||||
result = L10n.t("network.type.wifi")
|
||||
} else if path.usesInterfaceType(.cellular) {
|
||||
result = "4G"
|
||||
result = L10n.t("network.type.cellular")
|
||||
} else if path.usesInterfaceType(.wiredEthernet) {
|
||||
result = "Ethernet"
|
||||
result = L10n.t("network.type.ethernet")
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import Foundation
|
||||
|
||||
/// Stato termico iOS (`ProcessInfo.ThermalState`). Nessuna temperatura in gradi — solo livelli di sistema.
|
||||
enum ThermalState: Int, Sendable, Equatable, Comparable {
|
||||
case nominal = 0
|
||||
case fair = 1
|
||||
case serious = 2
|
||||
case critical = 3
|
||||
|
||||
static func from(_ state: ProcessInfo.ThermalState) -> ThermalState {
|
||||
switch state {
|
||||
case .nominal: return .nominal
|
||||
case .fair: return .fair
|
||||
case .serious: return .serious
|
||||
case .critical: return .critical
|
||||
@unknown default: return .nominal
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: ThermalState, rhs: ThermalState) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
|
||||
/// Etichetta compatta per l'overlay telemetry (angolo alto destro).
|
||||
var displayLabel: String {
|
||||
switch self {
|
||||
case .nominal: return L10n.t("thermal.state.nominal")
|
||||
case .fair: return L10n.t("thermal.state.fair")
|
||||
case .serious: return L10n.t("thermal.state.serious")
|
||||
case .critical: return L10n.t("thermal.state.critical")
|
||||
}
|
||||
}
|
||||
|
||||
var indicatorSymbol: String {
|
||||
switch self {
|
||||
case .nominal: return "🟢"
|
||||
case .fair: return "🟡"
|
||||
case .serious: return "🟠"
|
||||
case .critical: return "🔴"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
/// Monitor termico riutilizzabile: osserva `ProcessInfo.thermalState`, espone stato alla UI
|
||||
/// e degrada progressivamente bitrate/FPS dello stream quando necessario.
|
||||
///
|
||||
/// Politica adottata:
|
||||
/// - `.nominal` / `.fair`: nessuna modifica automatica al profilo video (solo indicatore UI).
|
||||
/// - `.serious`: −25% bitrate, FPS max 24.
|
||||
/// - `.critical`: −50% bitrate, FPS max 20; alert esplicito se persiste > 60s.
|
||||
@MainActor
|
||||
final class ThermalStateManager: ObservableObject {
|
||||
@Published private(set) var state: ThermalState = .from(ProcessInfo.processInfo.thermalState)
|
||||
@Published var noticeMessage: String?
|
||||
@Published var showCriticalAlert = false
|
||||
|
||||
private let log = Logger(subsystem: "com.matchlivetv.match-live-tv", category: "Thermal")
|
||||
private weak var engine: LiveBroadcastEngine?
|
||||
private var observer: (any NSObjectProtocol)?
|
||||
private var previousState: ThermalState?
|
||||
private var baselineConfig: BroadcastConfig?
|
||||
private var criticalSince: Date?
|
||||
private var criticalWatchTask: Task<Void, Never>?
|
||||
private var lastAppliedThermal: ThermalState?
|
||||
|
||||
func bind(engine: LiveBroadcastEngine) {
|
||||
self.engine = engine
|
||||
}
|
||||
|
||||
func updateBaseline(_ config: BroadcastConfig) {
|
||||
baselineConfig = config
|
||||
Task { await applyAdaptationIfNeeded(for: state, force: true) }
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard observer == nil else { return }
|
||||
let current = ThermalState.from(ProcessInfo.processInfo.thermalState)
|
||||
previousState = current
|
||||
state = current
|
||||
observer = NotificationCenter.default.addObserver(
|
||||
forName: ProcessInfo.thermalStateDidChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.handleThermalChange()
|
||||
}
|
||||
}
|
||||
logTransition(from: nil, to: current)
|
||||
Task { await applyAdaptationIfNeeded(for: current, force: true) }
|
||||
updateCriticalWatch()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
if let observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
self.observer = nil
|
||||
}
|
||||
criticalWatchTask?.cancel()
|
||||
criticalWatchTask = nil
|
||||
criticalSince = nil
|
||||
showCriticalAlert = false
|
||||
noticeMessage = nil
|
||||
previousState = nil
|
||||
lastAppliedThermal = nil
|
||||
}
|
||||
|
||||
func acknowledgeCriticalAlert() {
|
||||
showCriticalAlert = false
|
||||
}
|
||||
|
||||
private func handleThermalChange() {
|
||||
let next = ThermalState.from(ProcessInfo.processInfo.thermalState)
|
||||
guard next != state else { return }
|
||||
let previous = state
|
||||
state = next
|
||||
logTransition(from: previous, to: next)
|
||||
previousState = previous
|
||||
noticeMessage = userNotice(for: next, previous: previous)
|
||||
Task { await applyAdaptationIfNeeded(for: next, force: false) }
|
||||
updateCriticalWatch()
|
||||
}
|
||||
|
||||
private func logTransition(from previous: ThermalState?, to new: ThermalState) {
|
||||
let fromLabel = previous.map { String(describing: $0) } ?? "—"
|
||||
log.info("[Thermal] \(fromLabel, privacy: .public) → \(String(describing: new), privacy: .public)")
|
||||
print("[Thermal] \(fromLabel) → \(new)")
|
||||
}
|
||||
|
||||
private func userNotice(for new: ThermalState, previous: ThermalState?) -> String? {
|
||||
switch new {
|
||||
case .nominal, .fair:
|
||||
return nil
|
||||
case .serious where previous ?? .nominal < .serious:
|
||||
return L10n.t("thermal.notice.serious")
|
||||
case .critical where previous ?? .nominal < .critical:
|
||||
return L10n.t("thermal.notice.critical")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCriticalWatch() {
|
||||
criticalWatchTask?.cancel()
|
||||
if state == .critical {
|
||||
if criticalSince == nil { criticalSince = Date() }
|
||||
criticalWatchTask = Task {
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
guard state == .critical, let since = criticalSince else { return }
|
||||
if Date().timeIntervalSince(since) >= 60 {
|
||||
showCriticalAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
criticalSince = nil
|
||||
showCriticalAlert = false
|
||||
}
|
||||
}
|
||||
|
||||
private func applyAdaptationIfNeeded(for thermal: ThermalState, force: Bool) async {
|
||||
guard let baseline = baselineConfig, let engine else { return }
|
||||
if !force, lastAppliedThermal == thermal { return }
|
||||
let adapted = Self.adaptedConfig(from: baseline, for: thermal)
|
||||
let previousApplied = lastAppliedThermal.map { Self.adaptedConfig(from: baseline, for: $0) } ?? baseline
|
||||
await engine.applyThermalProfile(
|
||||
videoBitrate: adapted.videoBitrate,
|
||||
fps: adapted.fps
|
||||
)
|
||||
lastAppliedThermal = thermal
|
||||
if adapted.videoBitrate != previousApplied.videoBitrate {
|
||||
let fromKbps = previousApplied.videoBitrate / 1000
|
||||
let toKbps = adapted.videoBitrate / 1000
|
||||
log.info("[Thermal] bitrate changed from \(fromKbps) kbps to \(toKbps) kbps")
|
||||
print("[Thermal] bitrate changed from \(fromKbps) kbps to \(toKbps) kbps")
|
||||
}
|
||||
if adapted.fps != previousApplied.fps {
|
||||
log.info("[Thermal] fps changed from \(previousApplied.fps) to \(adapted.fps)")
|
||||
print("[Thermal] fps changed from \(previousApplied.fps) to \(adapted.fps)")
|
||||
}
|
||||
}
|
||||
|
||||
static func adaptedConfig(from baseline: BroadcastConfig, for thermal: ThermalState) -> BroadcastConfig {
|
||||
switch thermal {
|
||||
case .nominal, .fair:
|
||||
return baseline
|
||||
case .serious:
|
||||
var cfg = baseline
|
||||
cfg.videoBitrate = Int(Double(baseline.videoBitrate) * 0.75)
|
||||
cfg.fps = min(baseline.fps, 24)
|
||||
return cfg
|
||||
case .critical:
|
||||
var cfg = baseline
|
||||
cfg.videoBitrate = Int(Double(baseline.videoBitrate) * 0.50)
|
||||
cfg.fps = min(baseline.fps, 20)
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ enum UserFacingError {
|
||||
return ns.domain == NSURLErrorDomain && ns.code == NSURLErrorCancelled
|
||||
}
|
||||
|
||||
static func message(for error: Error, fallback: String = "Operazione non riuscita") -> String? {
|
||||
static func message(for error: Error, fallback: String? = nil) -> String? {
|
||||
let resolvedFallback = fallback ?? L10n.t("error.operation.failed")
|
||||
if isCancellation(error) { return nil }
|
||||
if let rtmp = rtmpMessage(for: error) { return rtmp }
|
||||
if let api = error as? LocalizedError, let description = api.errorDescription, !description.isEmpty {
|
||||
@@ -16,9 +17,9 @@ enum UserFacingError {
|
||||
}
|
||||
let text = error.localizedDescription
|
||||
if text.contains("RTMPConnection.Error") || text.contains("RTMPHaishinKit") {
|
||||
return "Connessione RTMP fallita. Verifica che MediaMTX sia raggiungibile (in dev: porta 1935 su localhost)."
|
||||
return L10n.t("error.rtmp.failed.dev")
|
||||
}
|
||||
return text.isEmpty ? fallback : text
|
||||
return text.isEmpty ? resolvedFallback : text
|
||||
}
|
||||
|
||||
private static func rtmpMessage(for error: Error) -> String? {
|
||||
@@ -26,14 +27,14 @@ enum UserFacingError {
|
||||
let localized = error.localizedDescription
|
||||
guard text.contains("RTMPConnection") || localized.contains("RTMPConnection") else { return nil }
|
||||
if localized.contains("unsupportedCommand") || text.contains("unsupportedCommand") {
|
||||
return "URL RTMP non valido. Controlla l'indirizzo di ingest dal server."
|
||||
return L10n.t("error.rtmp.invalid.url")
|
||||
}
|
||||
if localized.contains("connectionTimedOut") || text.contains("connectionTimedOut") {
|
||||
return "Timeout connessione RTMP. Verifica rete e server MediaMTX (porta 1935)."
|
||||
return L10n.t("error.rtmp.timeout")
|
||||
}
|
||||
if localized.contains("socketError") || text.contains("socketError") {
|
||||
return "Server RTMP non raggiungibile. In dev locale usa localhost:1935 (non l'host Docker «mediamtx»)."
|
||||
return L10n.t("error.rtmp.unreachable")
|
||||
}
|
||||
return "Connessione RTMP fallita. Verifica rete e server streaming."
|
||||
return L10n.t("error.rtmp.failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ enum APIError: LocalizedError {
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL: return "URL non valido"
|
||||
case .invalidURL: return L10n.t("api.error.invalid.url")
|
||||
case .http(let code, let body): return Self.friendlyHttpMessage(code: code, body: body)
|
||||
case .decoding: return "Risposta del server non valida"
|
||||
case .unauthorized: return "Sessione scaduta. Accedi di nuovo."
|
||||
case .decoding: return L10n.t("api.error.decoding")
|
||||
case .unauthorized: return L10n.t("api.error.unauthorized")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ enum APIError: LocalizedError {
|
||||
return message
|
||||
}
|
||||
if let error = json["error"] as? String, !error.isEmpty, !error.hasPrefix("{") {
|
||||
if code >= 500 { return "Errore del server. Riprova tra qualche istante." }
|
||||
if code >= 500 { return L10n.t("api.error.server") }
|
||||
return error
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,10 @@ enum APIError: LocalizedError {
|
||||
return body
|
||||
}
|
||||
switch code {
|
||||
case 500, 502, 503: return "Errore del server. Riprova tra qualche istante."
|
||||
case 404: return "Risorsa non trovata"
|
||||
case 403: return "Operazione non consentita"
|
||||
default: return "Errore HTTP \(code)"
|
||||
case 500, 502, 503: return L10n.t("api.error.server")
|
||||
case 404: return L10n.t("api.error.not.found")
|
||||
case 403: return L10n.t("api.error.forbidden")
|
||||
default: return L10n.t("api.error.http", code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ final class MatchRepository {
|
||||
func createQuickMatch(teamId: String) async throws -> Match {
|
||||
try await api.createMatch(
|
||||
teamId: teamId,
|
||||
body: CreateMatchRequest(match: CreateMatchBody(opponentName: "Avversario", sport: "volleyball", setsToWin: 3, scheduledAt: nil, location: nil))
|
||||
body: CreateMatchRequest(match: CreateMatchBody(opponentName: L10n.t("sheet.opponent"), sport: "volleyball", setsToWin: 3, scheduledAt: nil, location: nil))
|
||||
).toDomain()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ enum MatchSessionLauncher {
|
||||
sessionRepository: SessionRepository
|
||||
) async throws -> String {
|
||||
guard let sessionId = match.activeSessionId else {
|
||||
throw APIError.http(400, "Nessuna sessione attiva")
|
||||
throw APIError.http(400, L10n.t("session.error.no.active"))
|
||||
}
|
||||
_ = try await sessionRepository.fetchSession(id: sessionId)
|
||||
return sessionId
|
||||
|
||||
@@ -92,7 +92,7 @@ final class ScoreController: ObservableObject {
|
||||
syncGeneration += 1
|
||||
guard let sessionId else {
|
||||
score = previous
|
||||
lastActionError = "Sessione non collegata"
|
||||
lastActionError = L10n.t("session.error.not.connected")
|
||||
return false
|
||||
}
|
||||
do {
|
||||
@@ -103,7 +103,7 @@ final class ScoreController: ObservableObject {
|
||||
return true
|
||||
}
|
||||
score = previous
|
||||
lastActionError = "Risposta punteggio non valida"
|
||||
lastActionError = L10n.t("api.error.score.invalid")
|
||||
return false
|
||||
} catch {
|
||||
score = previous
|
||||
@@ -223,7 +223,7 @@ final class ScoreController: ObservableObject {
|
||||
sessionCable.sendScoreUpdate(score)
|
||||
} else {
|
||||
guard generation == syncGeneration else { return }
|
||||
lastActionError = "Risposta punteggio non valida"
|
||||
lastActionError = L10n.t("api.error.score.invalid")
|
||||
}
|
||||
} catch {
|
||||
guard generation == syncGeneration else { return }
|
||||
|
||||
@@ -35,9 +35,9 @@ enum MatchHubFilter {
|
||||
|
||||
enum MatchPresentation {
|
||||
static func statusLabel(for match: Match) -> String {
|
||||
if match.canResumeCamera { return "RIPRENDI" }
|
||||
if match.hasActiveSession { return "IN CORSO" }
|
||||
if ApiInstant.isScheduledFuture(match.scheduledAt) { return "PROGRAMMATA" }
|
||||
return "AVVIA"
|
||||
if match.canResumeCamera { return L10n.t("match.status.resume") }
|
||||
if match.hasActiveSession { return L10n.t("match.status.live") }
|
||||
if ApiInstant.isScheduledFuture(match.scheduledAt) { return L10n.t("match.status.scheduled") }
|
||||
return L10n.t("match.status.start")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ struct Team: Identifiable, Equatable, Sendable {
|
||||
}
|
||||
|
||||
var youtubeDestinationLabel: String {
|
||||
if youtubeUsesPlatformChannel { return "Canale Match Live TV" }
|
||||
if youtubeUsesPlatformChannel { return L10n.t("youtube.channel.platform") }
|
||||
let name = youtubeTeamChannelTitle?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
|
||||
?? clubName?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
|
||||
?? "società"
|
||||
return "Canale società · \(name)"
|
||||
?? L10n.t("youtube.channel.club.fallback")
|
||||
return L10n.t("youtube.channel.club", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<string>2.0.5</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -32,7 +32,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>21</string>
|
||||
<string>26</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
"NSCameraUsageDescription" = "Match Live TV nutzt die Kamera für Live-Übertragungen von Spielen.";
|
||||
"NSMicrophoneUsageDescription" = "Match Live TV nutzt das Mikrofon für den Live-Ton.";
|
||||
"NSPhotoLibraryUsageDescription" = "Match Live TV nutzt Fotos für Team-Logos.";
|
||||
@@ -0,0 +1,3 @@
|
||||
"NSCameraUsageDescription" = "Match Live TV uses the camera to broadcast live matches.";
|
||||
"NSMicrophoneUsageDescription" = "Match Live TV uses the microphone for live audio.";
|
||||
"NSPhotoLibraryUsageDescription" = "Match Live TV uses photos for team logos.";
|
||||
@@ -0,0 +1,3 @@
|
||||
"NSCameraUsageDescription" = "Match Live TV usa la cámara para transmitir partidos en directo.";
|
||||
"NSMicrophoneUsageDescription" = "Match Live TV usa el micrófono para el audio de la directa.";
|
||||
"NSPhotoLibraryUsageDescription" = "Match Live TV usa fotos para los logos de los equipos.";
|
||||
@@ -0,0 +1,3 @@
|
||||
"NSCameraUsageDescription" = "Match Live TV utilise la caméra pour diffuser les matchs en direct.";
|
||||
"NSMicrophoneUsageDescription" = "Match Live TV utilise le micro pour l'audio du direct.";
|
||||
"NSPhotoLibraryUsageDescription" = "Match Live TV utilise les photos pour les logos d'équipe.";
|
||||
@@ -0,0 +1,3 @@
|
||||
"NSCameraUsageDescription" = "Match Live TV usa la fotocamera per trasmettere le partite in diretta.";
|
||||
"NSMicrophoneUsageDescription" = "Match Live TV usa il microfono per l'audio della diretta.";
|
||||
"NSPhotoLibraryUsageDescription" = "Match Live TV usa le foto per i loghi delle squadre.";
|
||||
@@ -179,10 +179,22 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
await stopBroadcast()
|
||||
}
|
||||
|
||||
/// Adattamento termico: aggiorna profilo video senza interrompere la sessione RTMP attiva.
|
||||
func applyThermalProfile(videoBitrate: Int, fps: Int) async {
|
||||
guard var current = config else { return }
|
||||
guard current.videoBitrate != videoBitrate || current.fps != fps else { return }
|
||||
current.videoBitrate = videoBitrate
|
||||
current.fps = fps
|
||||
config = current
|
||||
if pipelineConfigured, let stream = await rtmpSession?.stream {
|
||||
try? await applyCodecSettings(to: stream, config: current)
|
||||
}
|
||||
}
|
||||
|
||||
private func configurePipeline(_ config: BroadcastConfig) async throws {
|
||||
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized,
|
||||
AVCaptureDevice.authorizationStatus(for: .audio) == .authorized else {
|
||||
throw APIError.http(403, "Permessi camera e microfono richiesti")
|
||||
throw APIError.http(403, L10n.t("broadcast.error.permissions.camera.mic"))
|
||||
}
|
||||
if pipelineConfigured {
|
||||
await attachPreviewIfNeeded()
|
||||
@@ -252,7 +264,7 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
guard previewAttached, generation == broadcastGeneration else {
|
||||
if generation == broadcastGeneration {
|
||||
publishPending = false
|
||||
setPhase(.error, error: "Anteprima camera non pronta")
|
||||
setPhase(.error, error: L10n.t("broadcast.error.camera.preview.not.ready"))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -274,14 +286,14 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
scheduleReconnect()
|
||||
return
|
||||
}
|
||||
let message = UserFacingError.message(for: error) ?? "Connessione RTMP fallita"
|
||||
let message = UserFacingError.message(for: error) ?? L10n.t("broadcast.error.rtmp.connect.failed")
|
||||
setPhase(.error, error: message)
|
||||
}
|
||||
}
|
||||
|
||||
private func publish(config: BroadcastConfig, generation: Int) async throws {
|
||||
guard let url = URL(string: config.rtmpUrl) else {
|
||||
throw APIError.http(400, "URL RTMP non valido")
|
||||
throw APIError.http(400, L10n.t("broadcast.error.rtmp.url.invalid"))
|
||||
}
|
||||
await Self.ensureRTMPFactoryRegistered()
|
||||
await applyVideoOrientationIfNeeded(force: true)
|
||||
@@ -291,7 +303,7 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
.setMode(.publish)
|
||||
.build()
|
||||
guard let session else {
|
||||
throw APIError.http(500, "Impossibile creare sessione RTMP")
|
||||
throw APIError.http(500, L10n.t("broadcast.error.rtmp.session.create"))
|
||||
}
|
||||
|
||||
let stream = await session.stream
|
||||
@@ -395,7 +407,7 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
reconnectAttempts += 1
|
||||
if reconnectAttempts > config.maxReconnectAttempts {
|
||||
reconnectTask = nil
|
||||
setPhase(.error, error: "Connessione RTMP interrotta")
|
||||
setPhase(.error, error: L10n.t("error.rtmp.interrupted"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -173,13 +173,6 @@ struct ScoreboardElement: OverlayElement {
|
||||
}
|
||||
|
||||
private func ordinalSetLabel(_ setNumber: Int) -> String {
|
||||
switch setNumber {
|
||||
case 1: return "1° set"
|
||||
case 2: return "2° set"
|
||||
case 3: return "3° set"
|
||||
case 4: return "4° set"
|
||||
case 5: return "5° set"
|
||||
default: return "Set \(setNumber)"
|
||||
}
|
||||
L10n.t("overlay.set.label", setNumber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ struct BroadcastControlsOverlay: View {
|
||||
VStack(alignment: .trailing, spacing: 6) {
|
||||
SideIconButton(
|
||||
systemName: controlsVisible ? "eye.slash.fill" : "eye.fill",
|
||||
accessibilityLabel: controlsVisible ? "Nascondi controlli" : "Mostra controlli",
|
||||
accessibilityLabel: controlsVisible ? L10n.t("broadcast.hide.controls.cd") : L10n.t("broadcast.show.controls.cd"),
|
||||
action: onToggleControls
|
||||
)
|
||||
BroadcastTelemetryPanel(
|
||||
@@ -94,11 +94,11 @@ struct BroadcastControlsOverlay: View {
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
.alert("Terminare la diretta?", isPresented: $showTerminateConfirm) {
|
||||
Button("Annulla", role: .cancel) {}
|
||||
Button("TERMINA", role: .destructive, action: onTerminate)
|
||||
.alert(L10n.t("broadcast.terminate.title"), isPresented: $showTerminateConfirm) {
|
||||
Button(L10n.t("action.cancel"), role: .cancel) {}
|
||||
Button(L10n.t("broadcast.terminate.confirm"), role: .destructive, action: onTerminate)
|
||||
} message: {
|
||||
Text("Lo streaming verrà chiuso per tutti gli spettatori.")
|
||||
Text(L10n.t("broadcast.terminate.message"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,26 +106,26 @@ struct BroadcastControlsOverlay: View {
|
||||
VStack(spacing: 6) {
|
||||
SideIconButton(
|
||||
systemName: "square.and.arrow.up",
|
||||
accessibilityLabel: "Condividi diretta",
|
||||
accessibilityLabel: L10n.t("broadcast.share.live.cd"),
|
||||
action: onShareLive,
|
||||
enabled: shareLiveEnabled
|
||||
)
|
||||
SideIconButton(
|
||||
systemName: "video.fill",
|
||||
accessibilityLabel: "Condividi link regia",
|
||||
accessibilityLabel: L10n.t("broadcast.share.regia.cd"),
|
||||
action: onShareRegia
|
||||
)
|
||||
if let onCloseSet {
|
||||
SideIconButton(
|
||||
systemName: "checkmark",
|
||||
accessibilityLabel: "Chiudi set",
|
||||
accessibilityLabel: L10n.t("score.action.close.set"),
|
||||
action: onCloseSet
|
||||
)
|
||||
}
|
||||
if let onAdvancePeriod {
|
||||
SideIconButton(
|
||||
systemName: "forward.end.fill",
|
||||
accessibilityLabel: "Periodo successivo",
|
||||
accessibilityLabel: L10n.t("broadcast.next.period.cd"),
|
||||
action: onAdvancePeriod
|
||||
)
|
||||
}
|
||||
@@ -138,13 +138,13 @@ struct BroadcastControlsOverlay: View {
|
||||
VStack(spacing: 6) {
|
||||
SideIconButton(
|
||||
systemName: isPaused ? "play.fill" : "pause.fill",
|
||||
accessibilityLabel: isPaused ? "Riprendi diretta" : "Pausa diretta",
|
||||
accessibilityLabel: isPaused ? L10n.t("broadcast.resume.cd") : L10n.t("broadcast.pause.cd"),
|
||||
action: onPauseOrResume,
|
||||
highlighted: isPaused
|
||||
)
|
||||
SideIconButton(
|
||||
systemName: "stop.fill",
|
||||
accessibilityLabel: "Termina diretta",
|
||||
accessibilityLabel: L10n.t("broadcast.terminate.cd"),
|
||||
action: { showTerminateConfirm = true },
|
||||
danger: true
|
||||
)
|
||||
@@ -154,7 +154,7 @@ struct BroadcastControlsOverlay: View {
|
||||
private var scoreControlsRow: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
TeamScoreColumn(
|
||||
teamLabel: "CASA",
|
||||
teamLabel: L10n.t("broadcast.team.home.label"),
|
||||
teamName: homeName,
|
||||
accentColor: homeAccentColor,
|
||||
logoUrl: homeLogoUrl,
|
||||
@@ -171,7 +171,7 @@ struct BroadcastControlsOverlay: View {
|
||||
ScoreCenterPanel(score: score, boardType: boardType, pointsTarget: pointsTarget)
|
||||
|
||||
TeamScoreColumn(
|
||||
teamLabel: "OSPITE",
|
||||
teamLabel: L10n.t("broadcast.team.away.label"),
|
||||
teamName: awayName,
|
||||
accentColor: awayAccentColor,
|
||||
logoUrl: awayLogoUrl,
|
||||
@@ -198,7 +198,7 @@ private struct BroadcastTelemetryPanel: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(cableConnected ? "Tabellone OK" : "Tabellone offline")
|
||||
Text(cableConnected ? L10n.t("broadcast.scoreboard.connected") : L10n.t("broadcast.scoreboard.offline"))
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(cableConnected ? MatchColors.successGreen : MatchColors.textSecondary)
|
||||
let fpsLabel = fps > 0 ? "\(fps) fps" : "— fps"
|
||||
@@ -214,11 +214,7 @@ private struct BroadcastTelemetryPanel: View {
|
||||
Text("\(networkType) · \(deviceHealth.batteryPercent)%")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
ThermalIndicator(
|
||||
tempC: deviceHealth.batteryTempC,
|
||||
level: deviceHealth.thermalLevel,
|
||||
label: deviceHealth.thermalLabel
|
||||
)
|
||||
ThermalIndicator(state: deviceHealth.thermalState)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
@@ -227,31 +223,27 @@ private struct BroadcastTelemetryPanel: View {
|
||||
}
|
||||
|
||||
private struct ThermalIndicator: View {
|
||||
let tempC: Double?
|
||||
let level: Int
|
||||
let label: String
|
||||
let state: ThermalState
|
||||
|
||||
private var color: Color {
|
||||
switch level {
|
||||
case 0: return MatchColors.successGreen
|
||||
case 1: return MatchColors.accentYellow
|
||||
case 2: return Color.orange
|
||||
default: return MatchColors.primaryRed
|
||||
switch state {
|
||||
case .nominal: return MatchColors.successGreen
|
||||
case .fair: return MatchColors.accentYellow
|
||||
case .serious: return Color.orange
|
||||
case .critical: return MatchColors.primaryRed
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let tempText = tempC.map { "\(Int($0))°C" } ?? "—°C"
|
||||
let warningBg = level >= 1 ? color.opacity(0.18) : Color.clear
|
||||
let warningBg = state >= .fair ? color.opacity(0.18) : Color.clear
|
||||
HStack(spacing: 4) {
|
||||
Text(tempText)
|
||||
.font(.system(size: 11, weight: level >= 1 ? .bold : .regular))
|
||||
Text(state.indicatorSymbol)
|
||||
.font(.system(size: 11))
|
||||
Text(state.displayLabel)
|
||||
.font(.system(size: 11, weight: state >= .fair ? .bold : .medium))
|
||||
.foregroundStyle(color)
|
||||
if level >= 1 {
|
||||
Text(label)
|
||||
.font(.system(size: 11, weight: .bold))
|
||||
.foregroundStyle(color)
|
||||
}
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.85)
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 1)
|
||||
@@ -272,17 +264,17 @@ private struct ScoreCenterPanel: View {
|
||||
.fontWeight(.bold)
|
||||
switch boardType {
|
||||
case "basket", "timed":
|
||||
Text(score.periodLabel ?? (boardType == "basket" ? "Q\(score.period)" : "\(score.period)° tempo"))
|
||||
Text(score.periodLabel ?? (boardType == "basket" ? "Q\(score.period)" : L10n.t("broadcast.period.label.timed", score.period)))
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
case "generic":
|
||||
EmptyView()
|
||||
default:
|
||||
Text("Set \(score.currentSet) · \(pointsTarget) pt")
|
||||
Text(L10n.t("broadcast.set.progress", score.currentSet, pointsTarget))
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
if score.homeSets > 0 || score.awaySets > 0 {
|
||||
Text("Set vinti \(score.homeSets)-\(score.awaySets)")
|
||||
Text(L10n.t("broadcast.sets.won", score.homeSets, score.awaySets))
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
@@ -322,28 +314,28 @@ private struct TeamScoreColumn: View {
|
||||
.fontWeight(.bold)
|
||||
.padding(.vertical, 4)
|
||||
Spacer().frame(height: 4)
|
||||
let teamSide = alignEnd ? "ospite" : "casa"
|
||||
let teamSide = alignEnd ? L10n.t("broadcast.side.away") : L10n.t("broadcast.side.home")
|
||||
HStack(spacing: 6) {
|
||||
if alignEnd {
|
||||
if showBasketButtons {
|
||||
if let onPlus3 {
|
||||
ScoreIconButton(label: "+3", tooltip: "+3 \(teamSide)", action: onPlus3, primary: true)
|
||||
ScoreIconButton(label: "+3", tooltip: L10n.t("broadcast.tooltip.plus.side", 3, teamSide), action: onPlus3, primary: true)
|
||||
}
|
||||
if let onPlus2 {
|
||||
ScoreIconButton(label: "+2", tooltip: "+2 \(teamSide)", action: onPlus2, primary: true)
|
||||
ScoreIconButton(label: "+2", tooltip: L10n.t("broadcast.tooltip.plus.side", 2, teamSide), action: onPlus2, primary: true)
|
||||
}
|
||||
}
|
||||
ScoreIconButton(label: "+1", tooltip: "Aggiungi punto \(teamSide)", action: onPlus, primary: !showBasketButtons)
|
||||
ScoreIconButton(label: "−", tooltip: "Togli punto \(teamSide)", action: onMinus)
|
||||
ScoreIconButton(label: "+1", tooltip: L10n.t("broadcast.tooltip.add.point", teamSide), action: onPlus, primary: !showBasketButtons)
|
||||
ScoreIconButton(label: "−", tooltip: L10n.t("broadcast.tooltip.remove.point", teamSide), action: onMinus)
|
||||
} else {
|
||||
ScoreIconButton(label: "−", tooltip: "Togli punto \(teamSide)", action: onMinus)
|
||||
ScoreIconButton(label: "+1", tooltip: "Aggiungi punto \(teamSide)", action: onPlus, primary: !showBasketButtons)
|
||||
ScoreIconButton(label: "−", tooltip: L10n.t("broadcast.tooltip.remove.point", teamSide), action: onMinus)
|
||||
ScoreIconButton(label: "+1", tooltip: L10n.t("broadcast.tooltip.add.point", teamSide), action: onPlus, primary: !showBasketButtons)
|
||||
if showBasketButtons {
|
||||
if let onPlus2 {
|
||||
ScoreIconButton(label: "+2", tooltip: "+2 \(teamSide)", action: onPlus2, primary: true)
|
||||
ScoreIconButton(label: "+2", tooltip: L10n.t("broadcast.tooltip.plus.side", 2, teamSide), action: onPlus2, primary: true)
|
||||
}
|
||||
if let onPlus3 {
|
||||
ScoreIconButton(label: "+3", tooltip: "+3 \(teamSide)", action: onPlus3, primary: true)
|
||||
ScoreIconButton(label: "+3", tooltip: L10n.t("broadcast.tooltip.plus.side", 3, teamSide), action: onPlus3, primary: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ struct BroadcastScreen: View {
|
||||
|
||||
@StateObject private var permissions = BroadcastPermissions()
|
||||
@StateObject private var scoreDialogHost = LiveScoreDialogHost()
|
||||
@StateObject private var thermalManager = ThermalStateManager()
|
||||
|
||||
init(container: AppContainer, sessionId: String, onFinished: @escaping () -> Void) {
|
||||
self.container = container
|
||||
@@ -30,6 +31,7 @@ struct BroadcastScreen: View {
|
||||
@State private var snackbarMessage: String?
|
||||
@State private var shareItem: ShareItem?
|
||||
@State private var bootstrapGeneration = 0
|
||||
@State private var languageTick = 0
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -44,11 +46,11 @@ struct BroadcastScreen: View {
|
||||
ProgressView().tint(MatchColors.primaryRed)
|
||||
} else if !permissions.allGranted {
|
||||
VStack(spacing: 12) {
|
||||
Text("Consenti camera e microfono per andare in diretta")
|
||||
Text(L10n.t("broadcast.permissions.required"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.accentYellow)
|
||||
.multilineTextAlignment(.center)
|
||||
MatchPrimaryButton(label: "CONCEDI PERMESSI") {
|
||||
MatchPrimaryButton(label: L10n.t("broadcast.permissions.grant.action")) {
|
||||
Task { await permissions.requestAll() }
|
||||
}
|
||||
.padding(.horizontal, 40)
|
||||
@@ -56,7 +58,7 @@ struct BroadcastScreen: View {
|
||||
.padding(24)
|
||||
} else if let session, let match {
|
||||
broadcastOverlay(session: session, match: match)
|
||||
.id(scoreController.score.progressKey())
|
||||
.id("\(scoreController.score.progressKey())-\(languageTick)")
|
||||
.zIndex(1)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +91,7 @@ struct BroadcastScreen: View {
|
||||
.task(id: sessionId) {
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
||||
deviceHealth = DeviceTelemetry.snapshot()
|
||||
deviceHealth = DeviceTelemetry.snapshot(thermalState: thermalManager.state)
|
||||
}
|
||||
}
|
||||
.task(id: sessionId) {
|
||||
@@ -116,11 +118,35 @@ struct BroadcastScreen: View {
|
||||
self.error = message
|
||||
}
|
||||
}
|
||||
.alert("Errore", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
|
||||
Button("Riprova") {
|
||||
.onChange(of: thermalManager.state) { _ in
|
||||
deviceHealth = DeviceTelemetry.snapshot(thermalState: thermalManager.state)
|
||||
}
|
||||
.onChange(of: thermalManager.noticeMessage) { message in
|
||||
if let message { snackbarMessage = message }
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange)) { _ in
|
||||
// Aggiorna solo i testi dell'overlay (vedi .id su broadcastOverlay):
|
||||
// non deve smontare l'engine di broadcast né la sessione in corso.
|
||||
languageTick += 1
|
||||
}
|
||||
.alert(L10n.t("thermal.alert.title"), isPresented: Binding(
|
||||
get: { thermalManager.showCriticalAlert },
|
||||
set: { if !$0 { thermalManager.acknowledgeCriticalAlert() } }
|
||||
)) {
|
||||
Button(L10n.t("thermal.alert.stop"), role: .destructive) {
|
||||
Task { await stopStream() }
|
||||
}
|
||||
Button(L10n.t("thermal.alert.continue"), role: .cancel) {
|
||||
thermalManager.acknowledgeCriticalAlert()
|
||||
}
|
||||
} message: {
|
||||
Text(L10n.t("thermal.alert.message"))
|
||||
}
|
||||
.alert(L10n.t("common.error.title"), isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
|
||||
Button(L10n.t("matches.retry")) {
|
||||
Task { await retryBroadcast() }
|
||||
}
|
||||
Button("Esci", role: .destructive) { onFinished() }
|
||||
Button(L10n.t("action.exit"), role: .destructive) { onFinished() }
|
||||
} message: {
|
||||
Text(error ?? "")
|
||||
}
|
||||
@@ -264,7 +290,7 @@ struct BroadcastScreen: View {
|
||||
targetFps: session.targetFps,
|
||||
bitrateKbps: metrics.bitrateKbps,
|
||||
networkType: deviceHealth.networkType,
|
||||
deviceHealth: deviceHealth
|
||||
deviceHealth: DeviceTelemetry.snapshot(thermalState: thermalManager.state)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -279,13 +305,13 @@ struct BroadcastScreen: View {
|
||||
}
|
||||
|
||||
private func broadcastStatusText(isPaused: Bool, metrics: BroadcastMetrics) -> String {
|
||||
if isPaused { return "PAUSA" }
|
||||
if isPaused { return L10n.t("broadcast.status.paused") }
|
||||
switch metrics.phase {
|
||||
case .live: return "IN DIRETTA"
|
||||
case .connecting: return "CONNESSIONE…"
|
||||
case .reconnecting: return "RICONNESSIONE…"
|
||||
case .error: return metrics.lastError ?? "ERRORE"
|
||||
default: return "PREVIEW"
|
||||
case .live: return L10n.t("broadcast.status.live")
|
||||
case .connecting: return L10n.t("broadcast.status.connecting")
|
||||
case .reconnecting: return L10n.t("broadcast.status.reconnecting")
|
||||
case .error: return metrics.lastError ?? L10n.t("broadcast.status.error.fallback")
|
||||
default: return L10n.t("broadcast.status.preview")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +328,7 @@ struct BroadcastScreen: View {
|
||||
private func shareLiveLink(session: StreamSession, subject: String) {
|
||||
let urlString = session.watchShareUrl() ?? "\(AppConfig.apiBaseUrl)/live/\(session.id)"
|
||||
guard let url = URL(string: urlString) else {
|
||||
snackbarMessage = "Link diretta non ancora disponibile"
|
||||
snackbarMessage = L10n.t("broadcast.share.link.unavailable")
|
||||
return
|
||||
}
|
||||
shareItem = ShareItem(items: [url], subject: subject)
|
||||
@@ -313,12 +339,12 @@ struct BroadcastScreen: View {
|
||||
do {
|
||||
let urlString = try await container.sessionRepository.createRegiaLink(sessionId: sessionId)
|
||||
guard let url = URL(string: urlString) else {
|
||||
snackbarMessage = "Link regia non valido"
|
||||
snackbarMessage = L10n.t("broadcast.error.regia.link.invalid")
|
||||
return
|
||||
}
|
||||
shareItem = ShareItem(items: [url], subject: "Link regia — \(subject)")
|
||||
shareItem = ShareItem(items: [url], subject: L10n.t("broadcast.share.regia.subject", subject))
|
||||
} catch {
|
||||
snackbarMessage = UserFacingError.message(for: error) ?? "Errore link regia"
|
||||
snackbarMessage = UserFacingError.message(for: error) ?? L10n.t("broadcast.error.regia.link")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,11 +371,14 @@ struct BroadcastScreen: View {
|
||||
loading = false
|
||||
guard generation == bootstrapGeneration else { return }
|
||||
guard await container.broadcastCoordinator.waitForPreviewSurface() else {
|
||||
throw APIError.http(500, "Anteprima camera non pronta")
|
||||
throw APIError.http(500, L10n.t("broadcast.error.camera.preview.not.ready"))
|
||||
}
|
||||
guard generation == bootstrapGeneration else { return }
|
||||
if let url = loaded.rtmpIngestUrl, !url.isEmpty {
|
||||
let config = broadcastConfig(for: loaded, rtmpUrl: url)
|
||||
thermalManager.bind(engine: container.broadcastCoordinator.engine)
|
||||
thermalManager.updateBaseline(config)
|
||||
thermalManager.start()
|
||||
try await container.broadcastCoordinator.prepareBroadcast(
|
||||
sessionId: sessionId,
|
||||
config: config,
|
||||
@@ -408,7 +437,7 @@ struct BroadcastScreen: View {
|
||||
case .none:
|
||||
state = OverlayState(overlayKind: .none, watermarkVisible: false, broadcastStatus: container.broadcastCoordinator.metrics.phase.toOverlayStatus())
|
||||
case .basket, .timed:
|
||||
let period = score.periodLabel ?? (overlayKind == .basket ? "Q\(score.period)" : "\(score.period)° tempo")
|
||||
let period = score.periodLabel ?? (overlayKind == .basket ? "Q\(score.period)" : L10n.t("broadcast.period.label.timed", score.period))
|
||||
state = OverlayState(
|
||||
overlayKind: overlayKind,
|
||||
compactScoreboard: CompactScoreboardState(
|
||||
@@ -450,9 +479,9 @@ struct BroadcastScreen: View {
|
||||
let updated = try await container.sessionRepository.pauseSession(id: sessionId)
|
||||
session = updated
|
||||
await container.broadcastCoordinator.pauseBroadcast()
|
||||
snackbarMessage = "Diretta in pausa"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.paused")
|
||||
} catch {
|
||||
snackbarMessage = UserFacingError.message(for: error) ?? "Pausa non riuscita"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.pause.error", UserFacingError.message(for: error) ?? L10n.t("common.error.generic"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,9 +498,9 @@ struct BroadcastScreen: View {
|
||||
try await container.broadcastCoordinator.resumeBroadcast(config: config)
|
||||
updated = try await container.sessionRepository.fetchSession(id: sessionId)
|
||||
session = updated
|
||||
snackbarMessage = "Diretta ripresa"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.resumed")
|
||||
} catch {
|
||||
snackbarMessage = UserFacingError.message(for: error) ?? "Ripresa non riuscita"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.resume.error", UserFacingError.message(for: error) ?? L10n.t("common.error.generic"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +513,7 @@ struct BroadcastScreen: View {
|
||||
if let fetched = try? await container.sessionRepository.fetchSession(id: sessionId) {
|
||||
session = fetched
|
||||
}
|
||||
snackbarMessage = "Pausa dalla regia"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.paused.remote")
|
||||
}
|
||||
|
||||
/// Ripresa dalla regia / echo cable: solo RTMP locale, senza PATCH resume.
|
||||
@@ -497,10 +526,11 @@ struct BroadcastScreen: View {
|
||||
session = current
|
||||
guard let url = current.rtmpIngestUrl, !url.isEmpty else { return }
|
||||
let config = broadcastConfig(for: current, rtmpUrl: url)
|
||||
thermalManager.updateBaseline(config)
|
||||
try await container.broadcastCoordinator.resumeBroadcast(config: config)
|
||||
snackbarMessage = "Diretta ripresa"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.resumed")
|
||||
} catch {
|
||||
snackbarMessage = UserFacingError.message(for: error) ?? "Ripresa RTMP non riuscita"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.resume.rtmp.error", UserFacingError.message(for: error) ?? L10n.t("common.error.generic"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,11 +545,12 @@ struct BroadcastScreen: View {
|
||||
error = nil
|
||||
guard let session, let url = session.rtmpIngestUrl, !url.isEmpty else { return }
|
||||
let config = broadcastConfig(for: session, rtmpUrl: url)
|
||||
thermalManager.updateBaseline(config)
|
||||
do {
|
||||
try await container.broadcastCoordinator.resumeBroadcast(config: config)
|
||||
snackbarMessage = "Riconnessione avviata"
|
||||
snackbarMessage = L10n.t("broadcast.snackbar.reconnect.started")
|
||||
} catch {
|
||||
self.error = UserFacingError.message(for: error) ?? "Riconnessione non riuscita"
|
||||
self.error = UserFacingError.message(for: error) ?? L10n.t("broadcast.error.reconnect.failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +561,7 @@ struct BroadcastScreen: View {
|
||||
}
|
||||
|
||||
private func teardown() async {
|
||||
thermalManager.stop()
|
||||
container.scoreController.onScoreDidChange = nil
|
||||
container.sessionCable.onScoreUpdate = nil
|
||||
container.sessionCable.onPauseStream = nil
|
||||
|
||||
@@ -144,25 +144,25 @@ struct ScoreDialogRouter: View {
|
||||
case .setWon:
|
||||
let winner = dialog.winnerSide.map { scoringSideName($0, homeName: homeName, awayName: awayName) } ?? ""
|
||||
return Alert(
|
||||
title: Text("Set concluso"),
|
||||
message: Text("\(winner) vince il set \(dialog.homePoints)-\(dialog.awayPoints).\n\nChiudere il set e passare al successivo?"),
|
||||
primaryButton: .default(Text("Chiudi set")) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text("Continua a segnare")) { host.resolve(false) }
|
||||
title: Text(L10n.t("score.set.won.title")),
|
||||
message: Text(L10n.t("score.set.won.message", winner, dialog.homePoints, dialog.awayPoints)),
|
||||
primaryButton: .default(Text(L10n.t("score.action.close.set"))) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text(L10n.t("score.action.continue.scoring"))) { host.resolve(false) }
|
||||
)
|
||||
case .closeSetAnyway:
|
||||
return Alert(
|
||||
title: Text("Chiudi set"),
|
||||
message: Text("Il punteggio non soddisfa ancora le regole del torneo. Chiudere il set comunque?"),
|
||||
primaryButton: .default(Text("Chiudi comunque")) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text("Annulla")) { host.resolve(false) }
|
||||
title: Text(L10n.t("score.action.close.set")),
|
||||
message: Text(L10n.t("score.close.set.anyway.message")),
|
||||
primaryButton: .default(Text(L10n.t("score.action.close.anyway"))) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text(L10n.t("action.cancel"))) { host.resolve(false) }
|
||||
)
|
||||
case .matchWon:
|
||||
let winner = dialog.winnerSide.map { scoringSideName($0, homeName: homeName, awayName: awayName) } ?? ""
|
||||
return Alert(
|
||||
title: Text("Partita terminata"),
|
||||
message: Text("\(winner) vince la partita (\(dialog.homeSets)-\(dialog.awaySets) set).\n\nChiudere definitivamente la diretta?"),
|
||||
primaryButton: .default(Text("Chiudi diretta")) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text("Continua in onda")) { host.resolve(false) }
|
||||
title: Text(L10n.t("score.match.won.title")),
|
||||
message: Text(L10n.t("score.match.won.message", winner, dialog.homeSets, dialog.awaySets)),
|
||||
primaryButton: .default(Text(L10n.t("score.action.close.live"))) { host.resolve(true) },
|
||||
secondaryButton: .cancel(Text(L10n.t("score.action.continue.live"))) { host.resolve(false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct MatchLiveWordmark: View {
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
if showSlogan {
|
||||
Text("OGNI PARTITA, OGNI EVENTO, PER I TUOI TIFOSI.")
|
||||
Text(L10n.t("app.slogan").uppercased())
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -16,18 +16,24 @@ struct LoginScreen: View {
|
||||
MatchScreenScaffold {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
MatchLiveWordmark(showSlogan: true)
|
||||
.padding(.top, 32)
|
||||
Button(L10n.t("language.label")) {
|
||||
showLanguagePicker = true
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
showLanguagePicker = true
|
||||
} label: {
|
||||
Image(systemName: "globe")
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
.accessibilityLabel(L10n.t("language.label"))
|
||||
}
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.top, 16)
|
||||
.padding(.top, 8)
|
||||
MatchLiveWordmark(showSlogan: true)
|
||||
.padding(.top, 16)
|
||||
Text(L10n.t("login.submit").uppercased())
|
||||
.font(MatchTypography.headlineMedium)
|
||||
.padding(.top, 32)
|
||||
VStack(spacing: 16) {
|
||||
MatchTextField(title: L10n.t("login.email"), text: $email, placeholder: "coach@squadra.it", keyboard: .emailAddress)
|
||||
MatchTextField(title: L10n.t("login.email"), text: $email, placeholder: L10n.t("login.email.placeholder"), keyboard: .emailAddress)
|
||||
MatchSecureField(title: L10n.t("login.password"), text: $password, visible: $passwordVisible)
|
||||
}
|
||||
.padding(.top, 32)
|
||||
|
||||
@@ -36,13 +36,16 @@ struct MatchesScreen: View {
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
.accessibilityLabel(L10n.t("language.label"))
|
||||
Button(L10n.t("action.logout")) {
|
||||
Button {
|
||||
Task {
|
||||
await container.authRepository.logout()
|
||||
onLogout()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "rectangle.portrait.and.arrow.right")
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.accessibilityLabel(L10n.t("action.logout"))
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
@@ -53,29 +56,33 @@ struct MatchesScreen: View {
|
||||
ProgressView().tint(MatchColors.primaryRed)
|
||||
} else if teams.isEmpty {
|
||||
VStack(spacing: 16) {
|
||||
Text("Nessuna squadra disponibile")
|
||||
Text(L10n.t("matches.no.team.title"))
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
MatchPrimaryButton(label: "RIPROVA", action: { reload(showSpinner: false) })
|
||||
Text(L10n.t("matches.no.team.body"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
MatchPrimaryButton(label: L10n.t("matches.retry").uppercased(), action: { reload(showSpinner: false) })
|
||||
}
|
||||
.padding(24)
|
||||
} else if let error {
|
||||
VStack(spacing: 16) {
|
||||
Text(error).foregroundStyle(MatchColors.primaryRed).multilineTextAlignment(.center)
|
||||
MatchPrimaryButton(label: "RIPROVA", action: { reload(showSpinner: false) })
|
||||
MatchPrimaryButton(label: L10n.t("matches.retry").uppercased(), action: { reload(showSpinner: false) })
|
||||
}
|
||||
.padding(24)
|
||||
} else {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Ciao, \(container.tokenStore.session?.user.name ?? "")")
|
||||
Text(L10n.t("matches.hello", container.tokenStore.session?.user.name ?? ""))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
Text("Riprendi una diretta in corso o avvia una partita programmata.")
|
||||
Text(L10n.t("matches.subtitle"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
HStack(spacing: 12) {
|
||||
MatchSecondaryButton(label: "PARTITA PROGRAMMATA", action: { showSchedule = true })
|
||||
MatchPrimaryButton(label: "NUOVA PARTITA", action: { showNewMatch = true })
|
||||
MatchSecondaryButton(label: L10n.t("matches.schedule").uppercased(), action: { showSchedule = true })
|
||||
MatchPrimaryButton(label: L10n.t("matches.new").uppercased(), action: { showNewMatch = true })
|
||||
}
|
||||
if let activeTeam {
|
||||
TeamPickerBar(
|
||||
@@ -108,7 +115,7 @@ struct MatchesScreen: View {
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
} else if calendarMatches.isEmpty {
|
||||
Text("Nessuna altra partita in calendario.")
|
||||
Text(L10n.t("matches.no.other"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -136,27 +143,41 @@ struct MatchesScreen: View {
|
||||
}
|
||||
)
|
||||
.task(id: refreshToken) { reload(showSpinner: refreshToken == 0) }
|
||||
.alert("Riprendi diretta?", isPresented: Binding(get: { resumeMatch != nil }, set: { if !$0 { resumeMatch = nil } })) {
|
||||
Button("Riprendi") {
|
||||
.alert(L10n.t("sheet.configure.now.title"), isPresented: Binding(get: { resumeMatch != nil }, set: { if !$0 { resumeMatch = nil } })) {
|
||||
Button(L10n.t("sheet.resume.camera")) {
|
||||
if let match = resumeMatch { resumeBroadcast(match) }
|
||||
resumeMatch = nil
|
||||
}
|
||||
Button("Configura", role: .cancel) {
|
||||
Button(L10n.t("sheet.configure"), role: .cancel) {
|
||||
if let match = resumeMatch { onOpenSetup(match.id) }
|
||||
resumeMatch = nil
|
||||
}
|
||||
} message: {
|
||||
Text(L10n.t("sheet.configure.now.body"))
|
||||
}
|
||||
.alert("Elimina partita?", isPresented: Binding(get: { deleteMatch != nil }, set: { if !$0 { deleteMatch = nil } })) {
|
||||
Button("Elimina", role: .destructive) {
|
||||
.alert(
|
||||
L10n.t("sheet.delete.match.title"),
|
||||
isPresented: Binding(get: { deleteMatch != nil }, set: { if !$0 { deleteMatch = nil } })
|
||||
) {
|
||||
Button(L10n.t("sheet.delete"), role: .destructive) {
|
||||
if let match = deleteMatch {
|
||||
Task {
|
||||
try? await container.matchRepository.deleteMatch(matchId: match.id)
|
||||
reload(showSpinner: false)
|
||||
do {
|
||||
try await container.matchRepository.deleteMatch(matchId: match.id)
|
||||
snackbar = L10n.t("matches.msg.deleted")
|
||||
reload(showSpinner: false)
|
||||
} catch {
|
||||
snackbar = L10n.t("matches.msg.delete.failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteMatch = nil
|
||||
}
|
||||
Button("Annulla", role: .cancel) { deleteMatch = nil }
|
||||
Button(L10n.t("action.cancel"), role: .cancel) { deleteMatch = nil }
|
||||
} message: {
|
||||
if let match = deleteMatch {
|
||||
Text(L10n.t("sheet.delete.match.body", match.teamName, match.opponentName))
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showNewMatch) {
|
||||
NewMatchSheet(
|
||||
@@ -229,21 +250,21 @@ struct MatchesScreen: View {
|
||||
|
||||
private var calendarSectionTitle: String {
|
||||
if calendarMatches.isEmpty && activeMatch == nil {
|
||||
return "Nessuna partita in calendario"
|
||||
return L10n.t("matches.empty.title")
|
||||
}
|
||||
if !scheduledMatches.isEmpty {
|
||||
return "Partite programmate"
|
||||
return L10n.t("matches.scheduled.title")
|
||||
}
|
||||
return "Pronte da avviare"
|
||||
return L10n.t("matches.ready.title")
|
||||
}
|
||||
|
||||
private var emptyCalendarMessage: String {
|
||||
var message = "Programma una partita o avviane una nuova con «Nuova partita»."
|
||||
var message = L10n.t("matches.empty.hint")
|
||||
if let teamName = activeTeam?.name {
|
||||
message += "\n\nSquadra attiva: \(teamName)."
|
||||
message += "\n\n" + L10n.t("matches.active.team", teamName)
|
||||
}
|
||||
if teams.count > 1 {
|
||||
message += "\nHai più squadre: verifica quella selezionata sopra."
|
||||
message += "\n" + L10n.t("matches.multi.team.hint")
|
||||
}
|
||||
return message
|
||||
}
|
||||
@@ -260,7 +281,7 @@ struct MatchesScreen: View {
|
||||
matches = try await container.matchRepository.fetchMatchesForTeam(teamId: team.id)
|
||||
}
|
||||
} catch {
|
||||
self.error = UserFacingError.message(for: error)
|
||||
self.error = UserFacingError.message(for: error) ?? L10n.t("matches.msg.load.error")
|
||||
}
|
||||
loading = false
|
||||
refreshing = false
|
||||
@@ -279,7 +300,7 @@ struct MatchesScreen: View {
|
||||
let sessionId = try await MatchSessionLauncher.resumeBroadcastSession(match: match, sessionRepository: container.sessionRepository)
|
||||
onOpenBroadcast(sessionId)
|
||||
} catch {
|
||||
snackbar = UserFacingError.message(for: error)
|
||||
snackbar = UserFacingError.message(for: error) ?? L10n.t("matches.msg.resume.failed")
|
||||
}
|
||||
actionLoading = false
|
||||
}
|
||||
@@ -294,7 +315,7 @@ struct MatchesScreen: View {
|
||||
reload(showSpinner: false)
|
||||
onOpenSetup(match.id)
|
||||
} catch {
|
||||
snackbar = UserFacingError.message(for: error)
|
||||
snackbar = UserFacingError.message(for: error) ?? L10n.t("matches.msg.create.failed")
|
||||
}
|
||||
actionLoading = false
|
||||
}
|
||||
@@ -335,7 +356,7 @@ private struct ActiveSessionBanner: View {
|
||||
Image(systemName: "video.fill")
|
||||
.foregroundStyle(MatchColors.primaryRed)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Riprendi diretta in corso")
|
||||
Text(L10n.t("sheet.resume.banner"))
|
||||
.font(MatchTypography.titleMedium)
|
||||
.foregroundStyle(MatchColors.primaryRed)
|
||||
Text("\(match.teamName) vs \(match.opponentName)")
|
||||
@@ -409,23 +430,23 @@ private struct NewMatchSheet: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("Nuova partita")
|
||||
Text(L10n.t("sheet.new.match.title"))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
Text("Programma in anticipo o avvia la configurazione diretta subito.")
|
||||
Text(L10n.t("sheet.new.match.lead"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.top, 6)
|
||||
VStack(spacing: 10) {
|
||||
SheetOptionTile(
|
||||
systemImage: "calendar.badge.clock",
|
||||
title: "Programma partita",
|
||||
subtitle: "Data, ora e avversario — visibile anche sul sito",
|
||||
title: L10n.t("sheet.schedule.option"),
|
||||
subtitle: L10n.t("sheet.schedule.option.sub"),
|
||||
action: onSchedule
|
||||
)
|
||||
SheetOptionTile(
|
||||
systemImage: "play.circle",
|
||||
title: "Avvia subito",
|
||||
subtitle: "Crea la partita e passa al wizard senza orario",
|
||||
title: L10n.t("sheet.quick.option"),
|
||||
subtitle: L10n.t("sheet.quick.option.sub"),
|
||||
action: onQuickStart
|
||||
)
|
||||
}
|
||||
@@ -481,15 +502,18 @@ private struct ScheduleMatchSheet: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
TextField("Avversario", text: $opponent)
|
||||
TextField("Luogo", text: $location)
|
||||
DatePicker("Data", selection: $date)
|
||||
TextField(L10n.t("sheet.opponent"), text: $opponent)
|
||||
TextField(L10n.t("sheet.location.optional"), text: $location)
|
||||
DatePicker(L10n.t("sheet.date.time"), selection: $date)
|
||||
}
|
||||
.navigationTitle("Partita programmata")
|
||||
.navigationTitle(L10n.t("sheet.schedule.title"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) { Button("Chiudi") { dismiss() } }
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.t("common.close")) { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Crea") { create() }.disabled(opponent.isEmpty || teamId == nil)
|
||||
Button(L10n.t("sheet.save.schedule")) { create() }
|
||||
.disabled(opponent.isEmpty || teamId == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,13 +522,16 @@ private struct ScheduleMatchSheet: View {
|
||||
private func create() {
|
||||
guard let teamId else { return }
|
||||
Task {
|
||||
if let match = try? await container.matchRepository.createScheduledMatch(
|
||||
teamId: teamId,
|
||||
opponentName: opponent,
|
||||
scheduledAt: date,
|
||||
location: location.nilIfEmpty
|
||||
) {
|
||||
do {
|
||||
let match = try await container.matchRepository.createScheduledMatch(
|
||||
teamId: teamId,
|
||||
opponentName: opponent,
|
||||
scheduledAt: date,
|
||||
location: location.nilIfEmpty
|
||||
)
|
||||
onCreated(match)
|
||||
} catch {
|
||||
// Sheet chiude solo su successo; errori gestiti dal chiamante via snackbar se necessario.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +559,7 @@ private struct TeamPickerSheet: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Squadra")
|
||||
.navigationTitle(L10n.t("sheet.choose.team"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,13 @@ struct AppNavHost: View {
|
||||
@State private var broadcastRoute: BroadcastRoute?
|
||||
/// Incrementato al ritorno da wizard/broadcast per ricaricare l'hub partite.
|
||||
@State private var matchesRefreshToken = 0
|
||||
@State private var languageTick = 0
|
||||
|
||||
/// Locale SwiftUI (DatePicker, ecc.) aggiornata al cambio lingua senza resettare la NavigationStack.
|
||||
private var appLocale: Locale {
|
||||
_ = languageTick
|
||||
return AppLanguage.locale
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $path) {
|
||||
@@ -39,6 +46,10 @@ struct AppNavHost: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.environment(\.locale, appLocale)
|
||||
.onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange)) { _ in
|
||||
languageTick += 1
|
||||
}
|
||||
.lockPortraitOrientation()
|
||||
.fullScreenCover(item: $wizardRoute) { route in
|
||||
WizardShellScreen(
|
||||
|
||||
@@ -124,12 +124,12 @@ struct StepMatchScreen: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("Dettagli partita")
|
||||
Text(L10n.t("wizard.match.details.title"))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
TeamBrandingRow(
|
||||
sectionLabel: "Squadra di casa",
|
||||
sectionLabel: L10n.t("wizard.match.home.team.label"),
|
||||
teamName: .constant(match.teamName),
|
||||
nameEditable: false,
|
||||
remoteLogoUrl: homeLogoUrl,
|
||||
@@ -140,7 +140,7 @@ struct StepMatchScreen: View {
|
||||
.padding(.bottom, 16)
|
||||
|
||||
TeamBrandingRow(
|
||||
sectionLabel: "Squadra avversaria",
|
||||
sectionLabel: L10n.t("wizard.match.away.team.label"),
|
||||
teamName: $opponent,
|
||||
nameEditable: true,
|
||||
remoteLogoUrl: opponentLogoUrl,
|
||||
@@ -150,25 +150,25 @@ struct StepMatchScreen: View {
|
||||
)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
WizardOutlinedField(label: "Luogo", text: $location)
|
||||
WizardOutlinedField(label: L10n.t("wizard.match.location.label"), text: $location)
|
||||
.padding(.bottom, 12)
|
||||
|
||||
WizardOutlinedField(label: "Campionato (facoltativo)", text: $campionato)
|
||||
Text("Es. Serie C, torneo estivo — lo useremo in descrizione e overlay.")
|
||||
WizardOutlinedField(label: L10n.t("wizard.match.category.label"), text: $campionato)
|
||||
Text(L10n.t("wizard.match.category.hint"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.top, 4)
|
||||
|
||||
if isScheduledMatch {
|
||||
WizardReadOnlyField(
|
||||
label: "Programmata per",
|
||||
label: L10n.t("wizard.match.scheduled.label"),
|
||||
value: ApiInstant.formatMatchDate(match.scheduledAt) ?? "—"
|
||||
)
|
||||
.padding(.top, 16)
|
||||
}
|
||||
|
||||
if !allowedOverlays.isEmpty {
|
||||
Toggle("Overlay video personalizzato", isOn: $customOverlay)
|
||||
Toggle(L10n.t("wizard.match.custom.overlay.label"), isOn: $customOverlay)
|
||||
.padding(.top, 20)
|
||||
if customOverlay {
|
||||
VStack(spacing: 6) {
|
||||
@@ -186,7 +186,7 @@ struct StepMatchScreen: View {
|
||||
|
||||
Toggle(isOn: $customRules) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Regole punteggio personalizzate")
|
||||
Text(L10n.t("wizard.match.custom.rules.label"))
|
||||
Text(customRulesDescription)
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
@@ -202,7 +202,7 @@ struct StepMatchScreen: View {
|
||||
volleyRulesSection
|
||||
}
|
||||
|
||||
MatchPrimaryButton(label: "AVANTI >", action: saveAndContinue, enabled: !saving, loading: saving)
|
||||
MatchPrimaryButton(label: L10n.t("wizard.action.next"), action: saveAndContinue, enabled: !saving, loading: saving)
|
||||
.padding(.top, 32)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
@@ -244,20 +244,20 @@ struct StepMatchScreen: View {
|
||||
|
||||
private var customRulesDescription: String {
|
||||
if !customRules && ["basket", "timed"].contains(boardType) {
|
||||
return "Regole standard dello sport selezionato."
|
||||
return L10n.t("wizard.match.rules.standard.sport")
|
||||
}
|
||||
if customRules && ["basket", "timed"].contains(boardType) {
|
||||
return "Torneo non standard: tempi e periodi personalizzati."
|
||||
return L10n.t("wizard.match.rules.custom.timed")
|
||||
}
|
||||
if customRules {
|
||||
return "Torneo non standard: imposta set e punteggi."
|
||||
return L10n.t("wizard.match.rules.custom.sets")
|
||||
}
|
||||
return "Standard FIPAV: 3 set per vincere, set a 25, tie-break a 15."
|
||||
return L10n.t("wizard.match.rules.standard.sets")
|
||||
}
|
||||
|
||||
private var periodRulesSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(boardType == "basket" ? "Quarti" : "Tempi")
|
||||
Text(boardType == "basket" ? L10n.t("wizard.match.periods.basket.label") : L10n.t("wizard.match.periods.timed.label"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.padding(.top, 16)
|
||||
HStack(spacing: 8) {
|
||||
@@ -271,7 +271,7 @@ struct StepMatchScreen: View {
|
||||
}
|
||||
}
|
||||
WizardOutlinedField(
|
||||
label: boardType == "basket" ? "Minuti per quarto" : "Minuti per tempo",
|
||||
label: boardType == "basket" ? L10n.t("wizard.match.minutes.per.period.basket") : L10n.t("wizard.match.minutes.per.period.timed"),
|
||||
text: $periodDurationText
|
||||
)
|
||||
.onChange(of: periodDurationText) { text in
|
||||
@@ -279,7 +279,7 @@ struct StepMatchScreen: View {
|
||||
if filtered != text { periodDurationText = filtered }
|
||||
if let value = Int(filtered) { periodDurationMins = min(max(value, 1), 120) }
|
||||
}
|
||||
WizardOutlinedField(label: "Minuti supplementari", text: $overtimeDurationText)
|
||||
WizardOutlinedField(label: L10n.t("wizard.match.overtime.minutes.label"), text: $overtimeDurationText)
|
||||
.onChange(of: overtimeDurationText) { text in
|
||||
let filtered = String(text.filter(\.isNumber).prefix(3))
|
||||
if filtered != text { overtimeDurationText = filtered }
|
||||
@@ -290,7 +290,7 @@ struct StepMatchScreen: View {
|
||||
|
||||
private var volleyRulesSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Set da vincere la partita")
|
||||
Text(L10n.t("wizard.match.sets.to.win.label"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.padding(.top, 16)
|
||||
HStack(spacing: 8) {
|
||||
@@ -303,13 +303,13 @@ struct StepMatchScreen: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
WizardOutlinedField(label: "Punti per vincere un set", text: $pointsPerSetText)
|
||||
WizardOutlinedField(label: L10n.t("wizard.match.points.per.set.label"), text: $pointsPerSetText)
|
||||
.onChange(of: pointsPerSetText) { text in
|
||||
let filtered = String(text.filter(\.isNumber).prefix(2))
|
||||
if filtered != text { pointsPerSetText = filtered }
|
||||
if let value = Int(filtered) { pointsPerSet = min(max(value, 1), 99) }
|
||||
}
|
||||
WizardOutlinedField(label: "Punti tie-break (ultimo set)", text: $pointsDecidingSetText)
|
||||
WizardOutlinedField(label: L10n.t("wizard.match.tiebreak.points.label"), text: $pointsDecidingSetText)
|
||||
.onChange(of: pointsDecidingSetText) { text in
|
||||
let filtered = String(text.filter(\.isNumber).prefix(2))
|
||||
if filtered != text { pointsDecidingSetText = filtered }
|
||||
@@ -321,17 +321,17 @@ struct StepMatchScreen: View {
|
||||
private func saveAndContinue() {
|
||||
let trimmedOpponent = opponent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedOpponent.isEmpty else {
|
||||
onError("Inserisci il nome avversario")
|
||||
onError(L10n.t("wizard.error.opponent.name.required"))
|
||||
return
|
||||
}
|
||||
|
||||
if customRules && ["volley", "racket"].contains(boardType) {
|
||||
guard let perSet = Int(pointsPerSetText), perSet >= 1 else {
|
||||
onError("Inserisci i punti per vincere un set")
|
||||
onError(L10n.t("wizard.error.points.per.set.required"))
|
||||
return
|
||||
}
|
||||
guard let deciding = Int(pointsDecidingSetText), deciding >= 1 else {
|
||||
onError("Inserisci i punti del tie-break")
|
||||
onError(L10n.t("wizard.error.tiebreak.points.required"))
|
||||
return
|
||||
}
|
||||
pointsPerSet = perSet
|
||||
@@ -340,11 +340,11 @@ struct StepMatchScreen: View {
|
||||
|
||||
if customRules && ["basket", "timed"].contains(boardType) {
|
||||
guard let periodMins = Int(periodDurationText), periodMins >= 1 else {
|
||||
onError("Inserisci la durata del periodo in minuti")
|
||||
onError(L10n.t("wizard.error.period.duration.required"))
|
||||
return
|
||||
}
|
||||
guard let overtimeMins = Int(overtimeDurationText), overtimeMins >= 1 else {
|
||||
onError("Inserisci la durata dei supplementari in minuti")
|
||||
onError(L10n.t("wizard.error.overtime.duration.required"))
|
||||
return
|
||||
}
|
||||
periodDurationMins = periodMins
|
||||
|
||||
@@ -45,35 +45,35 @@ struct StepNetworkTestScreen: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("Test rete")
|
||||
Text(L10n.t("wizard.network.test.title"))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
Text("Verifica che la connessione regga l'upload della diretta.")
|
||||
Text(L10n.t("wizard.network.test.subtitle"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.top, 8)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
MetricCard(
|
||||
label: "Download",
|
||||
label: L10n.t("wizard.network.download.label"),
|
||||
value: testing ? "..." : String(format: "%.1f Mbps", downloadMbps)
|
||||
)
|
||||
MetricCard(
|
||||
label: "Upload",
|
||||
label: L10n.t("wizard.network.upload.label"),
|
||||
value: testing ? "..." : String(format: "%.1f Mbps", uploadMbps),
|
||||
highlight: ready
|
||||
)
|
||||
MetricCard(
|
||||
label: "Latenza",
|
||||
label: L10n.t("wizard.network.latency.label"),
|
||||
value: testing ? "..." : "\(latencyMs) ms"
|
||||
)
|
||||
}
|
||||
.padding(.top, 24)
|
||||
|
||||
MetricCard(label: "Tipo rete", value: networkType)
|
||||
MetricCard(label: L10n.t("wizard.network.type.label"), value: networkType)
|
||||
.padding(.top, 12)
|
||||
|
||||
if testCompleted && ready {
|
||||
Text("PRONTO PER ANDARE IN DIRETTA")
|
||||
Text(L10n.t("wizard.network.ready.label"))
|
||||
.font(MatchTypography.titleMedium)
|
||||
.foregroundStyle(MatchColors.successGreen)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -82,7 +82,7 @@ struct StepNetworkTestScreen: View {
|
||||
|
||||
if let quality = selectedQualityLabel {
|
||||
WizardReadOnlyField(
|
||||
label: "Qualità streaming (automatica)",
|
||||
label: L10n.t("wizard.network.quality.label"),
|
||||
value: quality
|
||||
)
|
||||
.padding(.top, 12)
|
||||
@@ -91,20 +91,20 @@ struct StepNetworkTestScreen: View {
|
||||
|
||||
if testCompleted, let shareUrl {
|
||||
WizardReadOnlyField(
|
||||
label: currentSession.platform == "youtube" ? "Link YouTube" : "Link diretta",
|
||||
label: currentSession.platform == "youtube" ? L10n.t("wizard.network.link.youtube.label") : L10n.t("wizard.network.link.live.label"),
|
||||
value: shareUrl
|
||||
)
|
||||
.padding(.top, 16)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
MatchSecondaryButton(label: "COPIA", action: { copyToClipboard(shareUrl) })
|
||||
MatchSecondaryButton(label: L10n.t("wizard.action.copy"), action: { copyToClipboard(shareUrl) })
|
||||
if let url = URL(string: shareUrl) {
|
||||
ShareLink(
|
||||
item: url,
|
||||
subject: Text("Diretta — \(match.teamName) vs \(match.opponentName)"),
|
||||
subject: Text(L10n.t("wizard.network.share.subject", match.teamName, match.opponentName)),
|
||||
message: Text(shareUrl)
|
||||
) {
|
||||
Text("CONDIVIDI")
|
||||
Text(L10n.t("wizard.action.share"))
|
||||
.font(MatchTypography.labelLarge)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 52)
|
||||
@@ -115,13 +115,13 @@ struct StepNetworkTestScreen: View {
|
||||
}
|
||||
.padding(.top, 8)
|
||||
|
||||
MatchSecondaryButton(label: "CONDIVIDI LINK REGIA", action: shareRegiaLink)
|
||||
MatchSecondaryButton(label: L10n.t("wizard.action.share.regia.link"), action: shareRegiaLink)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
if !testCompleted {
|
||||
MatchSecondaryButton(
|
||||
label: testing ? "TEST IN CORSO..." : "AVVIA TEST RETE",
|
||||
label: testing ? L10n.t("wizard.network.test.running.label") : L10n.t("wizard.network.test.start.label"),
|
||||
action: runTest,
|
||||
enabled: !testing
|
||||
)
|
||||
@@ -133,10 +133,10 @@ struct StepNetworkTestScreen: View {
|
||||
let backWidth = (proxy.size.width - spacing) / 3
|
||||
let forwardWidth = (proxy.size.width - spacing) * 2 / 3
|
||||
HStack(spacing: spacing) {
|
||||
MatchSecondaryButton(label: "Indietro", action: onBack, enabled: !starting)
|
||||
MatchSecondaryButton(label: L10n.t("wizard.action.back"), action: onBack, enabled: !starting)
|
||||
.frame(width: backWidth)
|
||||
MatchPrimaryButton(
|
||||
label: "INIZIA >",
|
||||
label: L10n.t("wizard.action.start"),
|
||||
action: startLive,
|
||||
enabled: ready,
|
||||
loading: starting
|
||||
@@ -230,12 +230,12 @@ struct StepNetworkTestScreen: View {
|
||||
do {
|
||||
let url = try await container.sessionRepository.createRegiaLink(sessionId: currentSession.id)
|
||||
guard let link = URL(string: url) else {
|
||||
onError("Link regia non valido")
|
||||
onError(L10n.t("wizard.error.regia.link"))
|
||||
return
|
||||
}
|
||||
shareItem = ShareItem(
|
||||
items: [link],
|
||||
subject: "Link regia — \(match.teamName) vs \(match.opponentName)"
|
||||
subject: L10n.t("broadcast.share.regia.subject", "\(match.teamName) vs \(match.opponentName)")
|
||||
)
|
||||
} catch {
|
||||
if let message = UserFacingError.message(for: error) {
|
||||
|
||||
@@ -17,9 +17,9 @@ struct StepTransmissionScreen: View {
|
||||
}
|
||||
|
||||
private var youtubeSubtitle: String {
|
||||
guard let team else { return "Canale in attivazione" }
|
||||
if !team.canUseYoutube { return "Premium Light o Full" }
|
||||
if !team.isYoutubeReady { return "Canale in attivazione" }
|
||||
guard let team else { return L10n.t("wizard.transmission.youtube.activating") }
|
||||
if !team.canUseYoutube { return L10n.t("wizard.transmission.youtube.premium.required") }
|
||||
if !team.isYoutubeReady { return L10n.t("wizard.transmission.youtube.activating") }
|
||||
return team.youtubeDestinationLabel
|
||||
}
|
||||
|
||||
@@ -27,18 +27,18 @@ struct StepTransmissionScreen: View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let plan = team?.planName {
|
||||
Text("Piano \(plan)")
|
||||
Text(L10n.t("wizard.transmission.plan.label", plan))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.bottom, 12)
|
||||
}
|
||||
|
||||
Text("Piattaforma")
|
||||
Text(L10n.t("wizard.transmission.platform.title"))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
|
||||
WizardPlatformCard(
|
||||
title: "Match Live TV",
|
||||
subtitle: "Diretta sul nostro sito (incluso)",
|
||||
subtitle: L10n.t("wizard.transmission.platform.site.subtitle"),
|
||||
selected: platform == "matchlivetv",
|
||||
onClick: { platform = "matchlivetv" }
|
||||
)
|
||||
@@ -49,24 +49,24 @@ struct StepTransmissionScreen: View {
|
||||
subtitle: youtubeSubtitle,
|
||||
selected: platform == "youtube",
|
||||
enabled: youtubeReady,
|
||||
badge: team?.canUseYoutube == false ? "Premium" : nil,
|
||||
badge: team?.canUseYoutube == false ? L10n.t("wizard.transmission.youtube.badge") : nil,
|
||||
onClick: selectYoutube
|
||||
)
|
||||
.padding(.top, 8)
|
||||
|
||||
Text("Visibilità")
|
||||
Text(L10n.t("wizard.transmission.visibility.title"))
|
||||
.font(MatchTypography.headlineMedium)
|
||||
.padding(.top, 24)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
WizardChoiceButton(
|
||||
label: "PUBBLICO",
|
||||
label: L10n.t("wizard.transmission.public.label"),
|
||||
selected: privacy == "public",
|
||||
action: { privacy = "public" }
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
WizardChoiceButton(
|
||||
label: "NON IN ELENCO",
|
||||
label: L10n.t("wizard.transmission.unlisted.label"),
|
||||
selected: privacy == "unlisted",
|
||||
action: { privacy = "unlisted" }
|
||||
)
|
||||
@@ -80,7 +80,7 @@ struct StepTransmissionScreen: View {
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 8)
|
||||
|
||||
Text("La partita resta sempre visibile nel backend della squadra per tutta la durata dell'abbonamento.")
|
||||
Text(L10n.t("wizard.transmission.backend.note"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -88,7 +88,7 @@ struct StepTransmissionScreen: View {
|
||||
|
||||
WizardFooterButtons(
|
||||
onBack: onBack,
|
||||
forwardLabel: "AVANTI >",
|
||||
forwardLabel: L10n.t("wizard.action.next"),
|
||||
forwardLoading: creating,
|
||||
onForward: createSessionAndContinue
|
||||
)
|
||||
@@ -111,16 +111,16 @@ struct StepTransmissionScreen: View {
|
||||
|
||||
private var visibilityDescription: String {
|
||||
if privacy == "public" {
|
||||
return "Compare nell'elenco dirette su MatchLiveTV.it, nelle ricerche e sul canale YouTube (se selezionato)."
|
||||
return L10n.t("wizard.transmission.public.desc")
|
||||
}
|
||||
return "Non compare negli elenchi pubblici né nelle ricerche. Solo chi ha il link può guardare."
|
||||
return L10n.t("wizard.transmission.unlisted.desc")
|
||||
}
|
||||
|
||||
private func selectYoutube() {
|
||||
if youtubeReady {
|
||||
platform = "youtube"
|
||||
} else {
|
||||
onError("YouTube non disponibile per questa squadra")
|
||||
onError(L10n.t("wizard.error.youtube.unavailable"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,18 +34,18 @@ struct TeamBrandingRow: View {
|
||||
if isConfigured {
|
||||
TeamLogoImage(remoteLogoUrl: remoteLogoUrl, localLogoImage: localLogoImage, size: 44)
|
||||
ColorAccentBar(color: ColorHex.swiftUIColor(displayColorHex), height: 36)
|
||||
Text(teamName.isEmpty ? "Squadra" : teamName)
|
||||
Text(teamName.isEmpty ? L10n.t("wizard.branding.team.fallback.name") : teamName)
|
||||
.font(MatchTypography.titleMedium)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
ColorAccentBar(color: ColorHex.swiftUIColor(displayColorHex), height: 32)
|
||||
if nameEditable {
|
||||
TextField("Nome avversario", text: $teamName)
|
||||
TextField(L10n.t("wizard.branding.opponent.name.placeholder"), text: $teamName)
|
||||
.font(MatchTypography.titleMedium)
|
||||
.padding(8)
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(MatchColors.outline, lineWidth: 1))
|
||||
} else {
|
||||
Text(teamName.isEmpty ? "Squadra" : teamName)
|
||||
Text(teamName.isEmpty ? L10n.t("wizard.branding.team.fallback.name") : teamName)
|
||||
.font(MatchTypography.titleMedium)
|
||||
.lineLimit(1)
|
||||
}
|
||||
@@ -57,6 +57,7 @@ struct TeamBrandingRow: View {
|
||||
.frame(width: 44, height: 44)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(L10n.t("wizard.branding.edit.team.cd"))
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 10)
|
||||
@@ -100,29 +101,29 @@ private struct TeamBrandingCustomizeSheet: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("Personalizza").font(MatchTypography.headlineMedium)
|
||||
Text(L10n.t("wizard.branding.customize.title")).font(MatchTypography.headlineMedium)
|
||||
Text(title)
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.padding(.top, 4)
|
||||
|
||||
if nameEditable {
|
||||
Text("Nome squadra").font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
TextField("Nome avversario", text: $draftName)
|
||||
Text(L10n.t("wizard.branding.team.name.label")).font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
TextField(L10n.t("wizard.branding.opponent.name.placeholder"), text: $draftName)
|
||||
.padding(12)
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(MatchColors.outline, lineWidth: 1))
|
||||
.padding(.top, 8)
|
||||
} else {
|
||||
Text("Nome squadra").font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
Text(L10n.t("wizard.branding.team.name.label")).font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
Text(teamName).font(MatchTypography.titleMedium).padding(.top, 4)
|
||||
}
|
||||
|
||||
Text("Logo").font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
Text(L10n.t("wizard.branding.logo.label")).font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
HStack(spacing: 14) {
|
||||
TeamLogoImage(remoteLogoUrl: remoteLogoUrl, localLogoImage: localLogoImage, size: 72)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
PhotosPicker(selection: $logoPickerItem, matching: .images) {
|
||||
Text("CARICA LOGO")
|
||||
Text(L10n.t("wizard.branding.upload.logo"))
|
||||
.font(MatchTypography.labelLarge)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 52)
|
||||
@@ -130,7 +131,7 @@ private struct TeamBrandingCustomizeSheet: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
if hasLogo {
|
||||
MatchSecondaryButton(label: "RIMUOVI", action: {
|
||||
MatchSecondaryButton(label: L10n.t("wizard.branding.remove.logo"), action: {
|
||||
localLogoImage = nil
|
||||
logoPickerItem = nil
|
||||
})
|
||||
@@ -139,13 +140,13 @@ private struct TeamBrandingCustomizeSheet: View {
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("Colore squadra").font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
Text(L10n.t("wizard.branding.color.label")).font(MatchTypography.labelLarge).padding(.top, 20)
|
||||
TeamColorPickerPanel(initialColorHex: draftColor.isEmpty ? fallbackColorHex : draftColor) { draftColor = $0 }
|
||||
.padding(.top, 12)
|
||||
|
||||
MatchPrimaryButton(label: "SALVA", action: save)
|
||||
MatchPrimaryButton(label: L10n.t("wizard.branding.save"), action: save)
|
||||
.padding(.top, 24)
|
||||
MatchSecondaryButton(label: "ANNULLA", action: onDismiss)
|
||||
MatchSecondaryButton(label: L10n.t("wizard.branding.cancel"), action: onDismiss)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
@@ -205,7 +206,7 @@ private struct TeamLogoImage: View {
|
||||
private var placeholder: some View {
|
||||
ZStack {
|
||||
MatchColors.surface
|
||||
Text("Nessun logo")
|
||||
Text(L10n.t("wizard.branding.no.logo"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ struct TeamColorPickerPanel: View {
|
||||
.overlay(Circle().stroke(MatchColors.outline, lineWidth: 2))
|
||||
saturationBrightnessPicker
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Tonalità")
|
||||
Text(L10n.t("wizard.color.hue"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
hueGradient
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import SwiftUI
|
||||
|
||||
private let wizardStepTitles = ["01 · Partita", "02 · Trasmissione", "03 · Test rete"]
|
||||
|
||||
func wizardStepTitle(_ step: Int) -> String {
|
||||
wizardStepTitles[min(max(step, 1), 3) - 1]
|
||||
let titles = [
|
||||
L10n.t("wizard.step.title.match"),
|
||||
L10n.t("wizard.step.title.transmission"),
|
||||
L10n.t("wizard.step.title.network")
|
||||
]
|
||||
return titles[min(max(step, 1), 3) - 1]
|
||||
}
|
||||
|
||||
struct WizardStepIndicator: View {
|
||||
@@ -32,7 +35,7 @@ struct WizardFooterButtons: View {
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
if showBack {
|
||||
MatchSecondaryButton(label: "Indietro", action: onBack)
|
||||
MatchSecondaryButton(label: L10n.t("wizard.action.back"), action: onBack)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
MatchPrimaryButton(label: forwardLabel, action: onForward, loading: forwardLoading)
|
||||
|
||||
@@ -11,6 +11,7 @@ struct WizardShellScreen: View {
|
||||
@State private var team: Team?
|
||||
@State private var currentStep: Int
|
||||
@State private var error: String?
|
||||
@State private var languageTick = 0
|
||||
|
||||
init(container: AppContainer, matchId: String, step: Int, onClose: @escaping () -> Void, onStartLive: @escaping (String) -> Void) {
|
||||
self.container = container
|
||||
@@ -28,6 +29,7 @@ struct WizardShellScreen: View {
|
||||
Button(action: onClose) {
|
||||
Image(systemName: "xmark").foregroundStyle(.white)
|
||||
}
|
||||
.accessibilityLabel(L10n.t("common.close"))
|
||||
Text(wizardStepTitle(currentStep))
|
||||
.font(MatchTypography.titleMedium)
|
||||
Spacer(minLength: 0)
|
||||
@@ -70,7 +72,7 @@ struct WizardShellScreen: View {
|
||||
onError: { presentError($0) }
|
||||
)
|
||||
} else {
|
||||
Text("Completa lo step Trasmissione")
|
||||
Text(L10n.t("wizard.complete.transmission.step"))
|
||||
.font(MatchTypography.bodyMedium)
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
@@ -99,11 +101,15 @@ struct WizardShellScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Errore", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
|
||||
Button("OK", role: .cancel) {}
|
||||
.alert(L10n.t("common.error.generic").capitalized, isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
|
||||
Button(L10n.t("action.ok"), role: .cancel) {}
|
||||
} message: {
|
||||
Text(error ?? "")
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .appLanguageDidChange)) { _ in
|
||||
languageTick += 1
|
||||
}
|
||||
.id(languageTick)
|
||||
}
|
||||
|
||||
private func presentError(_ message: String) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import XCTest
|
||||
@testable import MatchLiveTv
|
||||
|
||||
@MainActor
|
||||
final class ThermalAdaptationTests: XCTestCase {
|
||||
func testSeriousReducesBitrateAndFps() {
|
||||
let baseline = BroadcastConfig(
|
||||
rtmpUrl: "rtmp://localhost/live/key",
|
||||
videoBitrate: 2_500_000,
|
||||
fps: 30
|
||||
)
|
||||
let adapted = ThermalStateManager.adaptedConfig(from: baseline, for: .serious)
|
||||
XCTAssertEqual(adapted.videoBitrate, 1_875_000)
|
||||
XCTAssertEqual(adapted.fps, 24)
|
||||
}
|
||||
|
||||
func testCriticalReducesFurther() {
|
||||
let baseline = BroadcastConfig(
|
||||
rtmpUrl: "rtmp://localhost/live/key",
|
||||
videoBitrate: 2_500_000,
|
||||
fps: 30
|
||||
)
|
||||
let adapted = ThermalStateManager.adaptedConfig(from: baseline, for: .critical)
|
||||
XCTAssertEqual(adapted.videoBitrate, 1_250_000)
|
||||
XCTAssertEqual(adapted.fps, 20)
|
||||
}
|
||||
|
||||
func testNominalKeepsBaseline() {
|
||||
let baseline = BroadcastConfig(
|
||||
rtmpUrl: "rtmp://localhost/live/key",
|
||||
videoBitrate: 2_500_000,
|
||||
fps: 30
|
||||
)
|
||||
let adapted = ThermalStateManager.adaptedConfig(from: baseline, for: .nominal)
|
||||
XCTAssertEqual(adapted.videoBitrate, baseline.videoBitrate)
|
||||
XCTAssertEqual(adapted.fps, baseline.fps)
|
||||
}
|
||||
|
||||
func testThermalStateLabels() {
|
||||
AppLanguage.current = .it
|
||||
XCTAssertEqual(ThermalState.nominal.displayLabel, L10n.t("thermal.state.nominal"))
|
||||
XCTAssertEqual(ThermalState.fair.displayLabel, L10n.t("thermal.state.fair"))
|
||||
XCTAssertEqual(ThermalState.serious.displayLabel, L10n.t("thermal.state.serious"))
|
||||
XCTAssertEqual(ThermalState.critical.displayLabel, L10n.t("thermal.state.critical"))
|
||||
AppLanguage.current = .en
|
||||
XCTAssertEqual(ThermalState.nominal.displayLabel, "Temperature OK")
|
||||
AppLanguage.current = .system
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ ids = {f: uid() for f in app_files + test_files}
|
||||
bf = {f: uid() for f in app_files + test_files}
|
||||
assets_ref, assets_bf = uid(), uid()
|
||||
info_ref = uid()
|
||||
lproj_langs = ["it", "en", "fr", "de", "es"]
|
||||
lproj_refs = {lang: uid() for lang in lproj_langs}
|
||||
lproj_bfs = {lang: uid() for lang in lproj_langs}
|
||||
app_product, test_product = uid(), uid()
|
||||
app_target, test_target = uid(), uid()
|
||||
app_sources, app_frameworks, app_resources = uid(), uid(), uid()
|
||||
@@ -51,6 +54,12 @@ for f, fid in ids.items():
|
||||
lines += [
|
||||
f'\t\t{assets_ref} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = "<group>"; }};',
|
||||
f'\t\t{info_ref} /* Info.plist */ = {{isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = "<group>"; }};',
|
||||
]
|
||||
for lang in lproj_langs:
|
||||
lines.append(
|
||||
f'\t\t{lproj_refs[lang]} /* {lang}.lproj */ = {{isa = PBXFileReference; lastKnownFileType = folder; path = MatchLiveTv/Resources/{lang}.lproj; sourceTree = "<group>"; }};'
|
||||
)
|
||||
lines += [
|
||||
f'\t\t{app_product} /* MatchLiveTv.app */ = {{isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; }};',
|
||||
f'\t\t{test_product} /* MatchLiveTvTests.xctest */ = {{isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }};',
|
||||
f'\t\t{hk} /* HaishinKit */ = {{isa = XCSwiftPackageProductDependency; package = {pkg}; productName = HaishinKit; }};',
|
||||
@@ -62,9 +71,15 @@ for f, bid in bf.items():
|
||||
lines += [
|
||||
f'\t\t{assets_bf} /* Assets in Resources */ = {{isa = PBXBuildFile; fileRef = {assets_ref}; }};',
|
||||
]
|
||||
for lang in lproj_langs:
|
||||
lines.append(
|
||||
f'\t\t{lproj_bfs[lang]} /* {lang}.lproj in Resources */ = {{isa = PBXBuildFile; fileRef = {lproj_refs[lang]}; }};'
|
||||
)
|
||||
|
||||
app_children = ", ".join(ids[f] for f in app_files) + f", {assets_ref}, {info_ref}"
|
||||
lproj_children = ", ".join(lproj_refs[lang] for lang in lproj_langs)
|
||||
app_children = ", ".join(ids[f] for f in app_files) + f", {assets_ref}, {info_ref}, {lproj_children}"
|
||||
test_children = ", ".join(ids[f] for f in test_files)
|
||||
resource_bfs = ", ".join([assets_bf] + [lproj_bfs[lang] for lang in lproj_langs])
|
||||
|
||||
lines += [
|
||||
f'\t\t{main_group} = {{isa = PBXGroup; children = ({app_group}, {test_group}, {products_group}); sourceTree = "<group>"; }};',
|
||||
@@ -73,7 +88,7 @@ lines += [
|
||||
f'\t\t{products_group} = {{isa = PBXGroup; children = ({app_product}, {test_product}); name = Products; sourceTree = "<group>"; }};',
|
||||
f'\t\t{app_sources} = {{isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ({", ".join(bf[f] for f in app_files)}); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
f'\t\t{test_sources} = {{isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ({", ".join(bf[f] for f in test_files) if test_files else ""}); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
f'\t\t{app_resources} = {{isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ({assets_bf}); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
f'\t\t{app_resources} = {{isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ({resource_bfs}); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
f'\t\t{app_frameworks} = {{isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
f'\t\t{test_frameworks} = {{isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }};',
|
||||
]
|
||||
@@ -81,7 +96,7 @@ lines += [
|
||||
app_settings = """
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
@@ -92,7 +107,7 @@ app_settings = """
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
API_BASE_URL = "https://www.matchlivetv.it";
|
||||
@@ -107,10 +122,10 @@ app_debug_settings = app_settings + """
|
||||
test_settings = """
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
MARKETING_VERSION = 2.0.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
Reference in New Issue
Block a user