Aggiunge app iOS nativa con parità multi-sport e test unitari.

Porting SwiftUI da Android (auth, hub, wizard, broadcast RTMP, overlay, scoring board-aware), reload hub al ritorno da diretta, decodifica score tollerante e documentazione allineata.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Emiliano Frascaro
2026-06-14 21:37:32 +02:00
parent b798e0ea06
commit 585332e32e
101 changed files with 7910 additions and 5 deletions

183
native/ios/generate_xcodeproj.py Executable file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
from pathlib import Path
import uuid
ROOT = Path(__file__).parent
APP_DIR = ROOT / "MatchLiveTv"
TEST_DIR = ROOT / "MatchLiveTvTests"
OUT = ROOT / "MatchLiveTv.xcodeproj" / "project.pbxproj"
def uid():
return uuid.uuid4().hex[:24].upper()
app_files = sorted(APP_DIR.rglob("*.swift"))
test_files = sorted(TEST_DIR.rglob("*.swift"))
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()
app_product, test_product = uid(), uid()
app_target, test_target = uid(), uid()
app_sources, app_frameworks, app_resources = uid(), uid(), uid()
test_sources, test_frameworks = uid(), uid()
main_group, app_group, test_group, products_group = uid(), uid(), uid(), uid()
proj = uid()
pkg = uid()
hk, rtmp = uid(), uid()
hk_bf, rtmp_bf = uid(), uid()
bcl_proj_dbg, bcl_proj_rel = uid(), uid()
bcl_app_dbg, bcl_app_rel = uid(), uid()
bcl_test_dbg, bcl_test_rel = uid(), uid()
cfg_app_dbg, cfg_app_rel = uid(), uid()
cfg_test_dbg, cfg_test_rel = uid(), uid()
cfg_proj_dbg, cfg_proj_rel = uid(), uid()
bcl_app, bcl_test, bcl_proj = uid(), uid(), uid()
test_dep, test_dep_proxy = uid(), uid()
scheme_id = uid()
lines = [
"// !$*UTF8*$!",
"{",
"\tarchiveVersion = 1;",
"\tclasses = {};",
"\tobjectVersion = 60;",
"\tobjects = {",
]
for f, fid in ids.items():
lines.append(f'\t\t{fid} /* {f.name} */ = {{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = {f.name}; path = {f.relative_to(ROOT)}; sourceTree = "<group>"; }};')
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>"; }};',
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; }};',
f'\t\t{rtmp} /* RTMPHaishinKit */ = {{isa = XCSwiftPackageProductDependency; package = {pkg}; productName = RTMPHaishinKit; }};',
]
for f, bid in bf.items():
lines.append(f'\t\t{bid} /* {f.name} in Sources */ = {{isa = PBXBuildFile; fileRef = {ids[f]} /* {f.name} */; }};')
lines += [
f'\t\t{assets_bf} /* Assets in Resources */ = {{isa = PBXBuildFile; fileRef = {assets_ref}; }};',
]
app_children = ", ".join(ids[f] for f in app_files) + f", {assets_ref}, {info_ref}"
test_children = ", ".join(ids[f] for f in test_files)
lines += [
f'\t\t{main_group} = {{isa = PBXGroup; children = ({app_group}, {test_group}, {products_group}); sourceTree = "<group>"; }};',
f'\t\t{app_group} = {{isa = PBXGroup; children = ({app_children}); name = MatchLiveTv; sourceTree = "<group>"; }};',
f'\t\t{test_group} = {{isa = PBXGroup; children = ({test_children}); name = MatchLiveTvTests; sourceTree = "<group>"; }};',
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_frameworks} = {{isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }};',
f'\t\t{test_frameworks} = {{isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; }};',
]
app_settings = """
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
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.0;
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
"""
app_debug_settings = app_settings + """
ENABLE_TESTABILITY = YES;
"""
test_settings = """
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";
"""
lines += [
f'\t\t{cfg_app_dbg} = {{isa = XCBuildConfiguration; buildSettings = {{{app_debug_settings}\t\t\t}}; name = Debug; }};',
f'\t\t{cfg_app_rel} = {{isa = XCBuildConfiguration; buildSettings = {{{app_settings}\t\t\t}}; name = Release; }};',
f'\t\t{cfg_test_dbg} = {{isa = XCBuildConfiguration; buildSettings = {{{test_settings}\t\t\t}}; name = Debug; }};',
f'\t\t{cfg_test_rel} = {{isa = XCBuildConfiguration; buildSettings = {{{test_settings}\t\t\t}}; name = Release; }};',
f'\t\t{cfg_proj_dbg} = {{isa = XCBuildConfiguration; buildSettings = {{IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }}; name = Debug; }};',
f'\t\t{cfg_proj_rel} = {{isa = XCBuildConfiguration; buildSettings = {{IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }}; name = Release; }};',
f'\t\t{bcl_app} = {{isa = XCConfigurationList; buildConfigurations = ({cfg_app_dbg}, {cfg_app_rel}); defaultConfigurationName = Release; }};',
f'\t\t{bcl_test} = {{isa = XCConfigurationList; buildConfigurations = ({cfg_test_dbg}, {cfg_test_rel}); defaultConfigurationName = Release; }};',
f'\t\t{bcl_proj} = {{isa = XCConfigurationList; buildConfigurations = ({cfg_proj_dbg}, {cfg_proj_rel}); defaultConfigurationName = Release; }};',
f'\t\t{app_target} = {{isa = PBXNativeTarget; buildConfigurationList = {bcl_app}; buildPhases = ({app_sources}, {app_frameworks}, {app_resources}); buildRules = (); dependencies = (); name = MatchLiveTv; packageProductDependencies = ({hk}, {rtmp}); productName = MatchLiveTv; productReference = {app_product}; productType = "com.apple.product-type.application"; }};',
f'\t\t{test_dep_proxy} = {{isa = PBXContainerItemProxy; containerPortal = {proj} /* Project object */; proxyType = 1; remoteGlobalIDString = {app_target}; remoteInfo = MatchLiveTv; }};',
f'\t\t{test_dep} = {{isa = PBXTargetDependency; target = {app_target} /* MatchLiveTv */; targetProxy = {test_dep_proxy} /* PBXContainerItemProxy */; }};',
f'\t\t{test_target} = {{isa = PBXNativeTarget; buildConfigurationList = {bcl_test}; buildPhases = ({test_sources}, {test_frameworks}); buildRules = (); dependencies = ({test_dep}); name = MatchLiveTvTests; productName = MatchLiveTvTests; productReference = {test_product}; productType = "com.apple.product-type.bundle.unit-test"; }};',
f'\t\t{pkg} = {{isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/shogo4405/HaishinKit.swift"; requirement = {{kind = upToNextMajorVersion; minimumVersion = 2.0.0;}}; }};',
f'\t\t{proj} = {{isa = PBXProject; attributes = {{BuildIndependentTargetsInParallel = 0; LastSwiftUpdateCheck = 1600;}}; buildConfigurationList = {bcl_proj}; compatibilityVersion = "Xcode 15.0"; developmentRegion = it; mainGroup = {main_group}; packageReferences = ({pkg}); productRefGroup = {products_group}; targets = ({app_target}, {test_target}); }};',
"\t};",
f"\trootObject = {proj} /* Project object */;",
"}",
]
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text("\n".join(lines) + "\n")
(OUT.parent / "project.xcworkspace").mkdir(exist_ok=True)
(OUT.parent / "project.xcworkspace" / "contents.xcworkspacedata").write_text(
'<?xml version="1.0" encoding="UTF-8"?>\n<Workspace version="1.0">\n <FileRef location="self:"></FileRef>\n</Workspace>\n'
)
scheme_dir = OUT.parent / "xcshareddata" / "xcschemes"
scheme_dir.mkdir(parents=True, exist_ok=True)
(scheme_dir / "MatchLiveTv.xcscheme").write_text(f"""<?xml version="1.0" encoding="UTF-8"?>
<Scheme LastUpgradeVersion="1600" version="1.7">
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES">
<BuildActionEntries>
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="{app_target}" 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="{test_target}" 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="{test_target}" 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="{app_target}" 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="{app_target}" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction buildConfiguration="Debug"/>
<ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
</Scheme>
""")
print(f"Wrote {OUT} ({len(app_files)} app sources, {len(test_files)} test sources)")