diff --git a/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.pbxproj b/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ef3c423 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.pbxproj @@ -0,0 +1,532 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 76D20B412DD31EFC0015BF98 /* cert.der in Resources */ = {isa = PBXBuildFile; fileRef = 76D20B402DD31EFC0015BF98 /* cert.der */; }; + 76D20B432DD31F010015BF98 /* cert.p12 in Resources */ = {isa = PBXBuildFile; fileRef = 76D20B422DD31F010015BF98 /* cert.p12 */; }; + 76DF1F732DD25A1F00FFDF1A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1F6F2DD25A1E00FFDF1A /* ContentView.swift */; }; + 76DF1F742DD25A1F00FFDF1A /* SwiftUIDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1F712DD25A1E00FFDF1A /* SwiftUIDemoApp.swift */; }; + 76DF1F752DD25A1F00FFDF1A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 76DF1F6E2DD25A1E00FFDF1A /* Assets.xcassets */; }; + 76DF1FD32DD25A4600FFDF1A /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB22DD25A4600FFDF1A /* Decoder.swift */; }; + 76DF1FD42DD25A4600FFDF1A /* VolumeLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC22DD25A4600FFDF1A /* VolumeLevel.swift */; }; + 76DF1FD52DD25A4600FFDF1A /* SecondConfigurationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC12DD25A4600FFDF1A /* SecondConfigurationResponse.swift */; }; + 76DF1FD62DD25A4600FFDF1A /* Pairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC62DD25A4600FFDF1A /* Pairing.swift */; }; + 76DF1FD72DD25A4600FFDF1A /* PairingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FCF2DD25A4600FFDF1A /* PairingManager.swift */; }; + 76DF1FD82DD25A4600FFDF1A /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FCE2DD25A4600FFDF1A /* Errors.swift */; }; + 76DF1FD92DD25A4600FFDF1A /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB32DD25A4600FFDF1A /* Encoder.swift */; }; + 76DF1FDA2DD25A4600FFDF1A /* Secret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC82DD25A4600FFDF1A /* Secret.swift */; }; + 76DF1FDB2DD25A4600FFDF1A /* TLSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FD12DD25A4600FFDF1A /* TLSManager.swift */; }; + 76DF1FDC2DD25A4600FFDF1A /* PairingNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC72DD25A4600FFDF1A /* PairingNetwork.swift */; }; + 76DF1FDD2DD25A4600FFDF1A /* CommandNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FBE2DD25A4600FFDF1A /* CommandNetwork.swift */; }; + 76DF1FDE2DD25A4600FFDF1A /* SecodConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC02DD25A4600FFDF1A /* SecodConfiguration.swift */; }; + 76DF1FDF2DD25A4600FFDF1A /* RequestDataProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FCA2DD25A4600FFDF1A /* RequestDataProtocol.swift */; }; + 76DF1FE02DD25A4600FFDF1A /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC52DD25A4600FFDF1A /* Option.swift */; }; + 76DF1FE12DD25A4600FFDF1A /* Ping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FBF2DD25A4600FFDF1A /* Ping.swift */; }; + 76DF1FE22DD25A4600FFDF1A /* Direction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB62DD25A4600FFDF1A /* Direction.swift */; }; + 76DF1FE32DD25A4600FFDF1A /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FC42DD25A4600FFDF1A /* Configuration.swift */; }; + 76DF1FE42DD25A4600FFDF1A /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB52DD25A4600FFDF1A /* DeepLink.swift */; }; + 76DF1FE52DD25A4600FFDF1A /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB72DD25A4600FFDF1A /* Key.swift */; }; + 76DF1FE62DD25A4600FFDF1A /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FBB2DD25A4600FFDF1A /* Result.swift */; }; + 76DF1FE72DD25A4600FFDF1A /* KeyPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FB82DD25A4600FFDF1A /* KeyPress.swift */; }; + 76DF1FE82DD25A4600FFDF1A /* AndroidTVConfigurationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FBD2DD25A4600FFDF1A /* AndroidTVConfigurationMessage.swift */; }; + 76DF1FE92DD25A4600FFDF1A /* RemoteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FD02DD25A4600FFDF1A /* RemoteManager.swift */; }; + 76DF1FEA2DD25A4600FFDF1A /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FBA2DD25A4600FFDF1A /* Logger.swift */; }; + 76DF1FEB2DD25A4600FFDF1A /* CertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FCC2DD25A4600FFDF1A /* CertManager.swift */; }; + 76DF1FEC2DD25A4600FFDF1A /* CryptoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF1FCD2DD25A4600FFDF1A /* CryptoManager.swift */; }; + 76DF20392DD25B6A00FFDF1A /* RemoteTVManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DF20382DD25B6A00FFDF1A /* RemoteTVManager.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 76D20B402DD31EFC0015BF98 /* cert.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = cert.der; sourceTree = ""; }; + 76D20B422DD31F010015BF98 /* cert.p12 */ = {isa = PBXFileReference; lastKnownFileType = file; path = cert.p12; sourceTree = ""; }; + 76DF1F1C2DD1E5DF00FFDF1A /* SwiftUIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 76DF1F6E2DD25A1E00FFDF1A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 76DF1F6F2DD25A1E00FFDF1A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 76DF1F702DD25A1E00FFDF1A /* SwiftUIDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUIDemo.entitlements; sourceTree = ""; }; + 76DF1F712DD25A1E00FFDF1A /* SwiftUIDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIDemoApp.swift; sourceTree = ""; }; + 76DF1FB22DD25A4600FFDF1A /* Decoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = ""; }; + 76DF1FB32DD25A4600FFDF1A /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = ""; }; + 76DF1FB52DD25A4600FFDF1A /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; + 76DF1FB62DD25A4600FFDF1A /* Direction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Direction.swift; sourceTree = ""; }; + 76DF1FB72DD25A4600FFDF1A /* Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; + 76DF1FB82DD25A4600FFDF1A /* KeyPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPress.swift; sourceTree = ""; }; + 76DF1FBA2DD25A4600FFDF1A /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 76DF1FBB2DD25A4600FFDF1A /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 76DF1FBD2DD25A4600FFDF1A /* AndroidTVConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidTVConfigurationMessage.swift; sourceTree = ""; }; + 76DF1FBE2DD25A4600FFDF1A /* CommandNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandNetwork.swift; sourceTree = ""; }; + 76DF1FBF2DD25A4600FFDF1A /* Ping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ping.swift; sourceTree = ""; }; + 76DF1FC02DD25A4600FFDF1A /* SecodConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecodConfiguration.swift; sourceTree = ""; }; + 76DF1FC12DD25A4600FFDF1A /* SecondConfigurationResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondConfigurationResponse.swift; sourceTree = ""; }; + 76DF1FC22DD25A4600FFDF1A /* VolumeLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeLevel.swift; sourceTree = ""; }; + 76DF1FC42DD25A4600FFDF1A /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + 76DF1FC52DD25A4600FFDF1A /* Option.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = ""; }; + 76DF1FC62DD25A4600FFDF1A /* Pairing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pairing.swift; sourceTree = ""; }; + 76DF1FC72DD25A4600FFDF1A /* PairingNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingNetwork.swift; sourceTree = ""; }; + 76DF1FC82DD25A4600FFDF1A /* Secret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secret.swift; sourceTree = ""; }; + 76DF1FCA2DD25A4600FFDF1A /* RequestDataProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestDataProtocol.swift; sourceTree = ""; }; + 76DF1FCC2DD25A4600FFDF1A /* CertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertManager.swift; sourceTree = ""; }; + 76DF1FCD2DD25A4600FFDF1A /* CryptoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoManager.swift; sourceTree = ""; }; + 76DF1FCE2DD25A4600FFDF1A /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 76DF1FCF2DD25A4600FFDF1A /* PairingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingManager.swift; sourceTree = ""; }; + 76DF1FD02DD25A4600FFDF1A /* RemoteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteManager.swift; sourceTree = ""; }; + 76DF1FD12DD25A4600FFDF1A /* TLSManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLSManager.swift; sourceTree = ""; }; + 76DF20382DD25B6A00FFDF1A /* RemoteTVManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteTVManager.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 76DF1F192DD1E5DF00FFDF1A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 76DF1F132DD1E5DF00FFDF1A = { + isa = PBXGroup; + children = ( + 76DF1F722DD25A1E00FFDF1A /* SwiftUIDemo */, + 76DF1F1D2DD1E5DF00FFDF1A /* Products */, + ); + sourceTree = ""; + }; + 76DF1F1D2DD1E5DF00FFDF1A /* Products */ = { + isa = PBXGroup; + children = ( + 76DF1F1C2DD1E5DF00FFDF1A /* SwiftUIDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + 76DF1F722DD25A1E00FFDF1A /* SwiftUIDemo */ = { + isa = PBXGroup; + children = ( + 76DF20382DD25B6A00FFDF1A /* RemoteTVManager.swift */, + 76DF1FD22DD25A4600FFDF1A /* AndroidTVRemoteControl */, + 76DF1F6E2DD25A1E00FFDF1A /* Assets.xcassets */, + 76DF1F6F2DD25A1E00FFDF1A /* ContentView.swift */, + 76DF1F702DD25A1E00FFDF1A /* SwiftUIDemo.entitlements */, + 76DF1F712DD25A1E00FFDF1A /* SwiftUIDemoApp.swift */, + 76D20B402DD31EFC0015BF98 /* cert.der */, + 76D20B422DD31F010015BF98 /* cert.p12 */, + ); + path = SwiftUIDemo; + sourceTree = ""; + }; + 76DF1FB42DD25A4600FFDF1A /* coding */ = { + isa = PBXGroup; + children = ( + 76DF1FB22DD25A4600FFDF1A /* Decoder.swift */, + 76DF1FB32DD25A4600FFDF1A /* Encoder.swift */, + ); + path = coding; + sourceTree = ""; + }; + 76DF1FB92DD25A4600FFDF1A /* Commands */ = { + isa = PBXGroup; + children = ( + 76DF1FB52DD25A4600FFDF1A /* DeepLink.swift */, + 76DF1FB62DD25A4600FFDF1A /* Direction.swift */, + 76DF1FB72DD25A4600FFDF1A /* Key.swift */, + 76DF1FB82DD25A4600FFDF1A /* KeyPress.swift */, + ); + path = Commands; + sourceTree = ""; + }; + 76DF1FBC2DD25A4600FFDF1A /* misc */ = { + isa = PBXGroup; + children = ( + 76DF1FBA2DD25A4600FFDF1A /* Logger.swift */, + 76DF1FBB2DD25A4600FFDF1A /* Result.swift */, + ); + path = misc; + sourceTree = ""; + }; + 76DF1FC32DD25A4600FFDF1A /* CommandNetwork */ = { + isa = PBXGroup; + children = ( + 76DF1FBD2DD25A4600FFDF1A /* AndroidTVConfigurationMessage.swift */, + 76DF1FBE2DD25A4600FFDF1A /* CommandNetwork.swift */, + 76DF1FBF2DD25A4600FFDF1A /* Ping.swift */, + 76DF1FC02DD25A4600FFDF1A /* SecodConfiguration.swift */, + 76DF1FC12DD25A4600FFDF1A /* SecondConfigurationResponse.swift */, + 76DF1FC22DD25A4600FFDF1A /* VolumeLevel.swift */, + ); + path = CommandNetwork; + sourceTree = ""; + }; + 76DF1FC92DD25A4600FFDF1A /* PairingNetwork */ = { + isa = PBXGroup; + children = ( + 76DF1FC42DD25A4600FFDF1A /* Configuration.swift */, + 76DF1FC52DD25A4600FFDF1A /* Option.swift */, + 76DF1FC62DD25A4600FFDF1A /* Pairing.swift */, + 76DF1FC72DD25A4600FFDF1A /* PairingNetwork.swift */, + 76DF1FC82DD25A4600FFDF1A /* Secret.swift */, + ); + path = PairingNetwork; + sourceTree = ""; + }; + 76DF1FCB2DD25A4600FFDF1A /* Network */ = { + isa = PBXGroup; + children = ( + 76DF1FC32DD25A4600FFDF1A /* CommandNetwork */, + 76DF1FC92DD25A4600FFDF1A /* PairingNetwork */, + 76DF1FCA2DD25A4600FFDF1A /* RequestDataProtocol.swift */, + ); + path = Network; + sourceTree = ""; + }; + 76DF1FD22DD25A4600FFDF1A /* AndroidTVRemoteControl */ = { + isa = PBXGroup; + children = ( + 76DF1FB42DD25A4600FFDF1A /* coding */, + 76DF1FB92DD25A4600FFDF1A /* Commands */, + 76DF1FBC2DD25A4600FFDF1A /* misc */, + 76DF1FCB2DD25A4600FFDF1A /* Network */, + 76DF1FCC2DD25A4600FFDF1A /* CertManager.swift */, + 76DF1FCD2DD25A4600FFDF1A /* CryptoManager.swift */, + 76DF1FCE2DD25A4600FFDF1A /* Errors.swift */, + 76DF1FCF2DD25A4600FFDF1A /* PairingManager.swift */, + 76DF1FD02DD25A4600FFDF1A /* RemoteManager.swift */, + 76DF1FD12DD25A4600FFDF1A /* TLSManager.swift */, + ); + name = AndroidTVRemoteControl; + path = /Users/floriangabach/Documents/Apps/AndroidTVRemoteControl/Sources/AndroidTVRemoteControl; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 76DF1F1B2DD1E5DF00FFDF1A /* SwiftUIDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 76DF1F282DD1E5E100FFDF1A /* Build configuration list for PBXNativeTarget "SwiftUIDemo" */; + buildPhases = ( + 76DF1F182DD1E5DF00FFDF1A /* Sources */, + 76DF1F192DD1E5DF00FFDF1A /* Frameworks */, + 76DF1F1A2DD1E5DF00FFDF1A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftUIDemo; + packageProductDependencies = ( + ); + productName = SwiftUIDemo; + productReference = 76DF1F1C2DD1E5DF00FFDF1A /* SwiftUIDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 76DF1F142DD1E5DF00FFDF1A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 1630; + TargetAttributes = { + 76DF1F1B2DD1E5DF00FFDF1A = { + CreatedOnToolsVersion = 16.3; + }; + }; + }; + buildConfigurationList = 76DF1F172DD1E5DF00FFDF1A /* Build configuration list for PBXProject "SwiftUIDemo" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 76DF1F132DD1E5DF00FFDF1A; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 76DF1F1D2DD1E5DF00FFDF1A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 76DF1F1B2DD1E5DF00FFDF1A /* SwiftUIDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 76DF1F1A2DD1E5DF00FFDF1A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76D20B412DD31EFC0015BF98 /* cert.der in Resources */, + 76D20B432DD31F010015BF98 /* cert.p12 in Resources */, + 76DF1F752DD25A1F00FFDF1A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 76DF1F182DD1E5DF00FFDF1A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 76DF1F732DD25A1F00FFDF1A /* ContentView.swift in Sources */, + 76DF1F742DD25A1F00FFDF1A /* SwiftUIDemoApp.swift in Sources */, + 76DF1FD32DD25A4600FFDF1A /* Decoder.swift in Sources */, + 76DF1FD42DD25A4600FFDF1A /* VolumeLevel.swift in Sources */, + 76DF1FD52DD25A4600FFDF1A /* SecondConfigurationResponse.swift in Sources */, + 76DF1FD62DD25A4600FFDF1A /* Pairing.swift in Sources */, + 76DF1FD72DD25A4600FFDF1A /* PairingManager.swift in Sources */, + 76DF1FD82DD25A4600FFDF1A /* Errors.swift in Sources */, + 76DF1FD92DD25A4600FFDF1A /* Encoder.swift in Sources */, + 76DF1FDA2DD25A4600FFDF1A /* Secret.swift in Sources */, + 76DF1FDB2DD25A4600FFDF1A /* TLSManager.swift in Sources */, + 76DF1FDC2DD25A4600FFDF1A /* PairingNetwork.swift in Sources */, + 76DF1FDD2DD25A4600FFDF1A /* CommandNetwork.swift in Sources */, + 76DF1FDE2DD25A4600FFDF1A /* SecodConfiguration.swift in Sources */, + 76DF1FDF2DD25A4600FFDF1A /* RequestDataProtocol.swift in Sources */, + 76DF1FE02DD25A4600FFDF1A /* Option.swift in Sources */, + 76DF1FE12DD25A4600FFDF1A /* Ping.swift in Sources */, + 76DF1FE22DD25A4600FFDF1A /* Direction.swift in Sources */, + 76DF1FE32DD25A4600FFDF1A /* Configuration.swift in Sources */, + 76DF1FE42DD25A4600FFDF1A /* DeepLink.swift in Sources */, + 76DF1FE52DD25A4600FFDF1A /* Key.swift in Sources */, + 76DF1FE62DD25A4600FFDF1A /* Result.swift in Sources */, + 76DF1FE72DD25A4600FFDF1A /* KeyPress.swift in Sources */, + 76DF1FE82DD25A4600FFDF1A /* AndroidTVConfigurationMessage.swift in Sources */, + 76DF1FE92DD25A4600FFDF1A /* RemoteManager.swift in Sources */, + 76DF20392DD25B6A00FFDF1A /* RemoteTVManager.swift in Sources */, + 76DF1FEA2DD25A4600FFDF1A /* Logger.swift in Sources */, + 76DF1FEB2DD25A4600FFDF1A /* CertManager.swift in Sources */, + 76DF1FEC2DD25A4600FFDF1A /* CryptoManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 76DF1F262DD1E5E100FFDF1A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = M3TP5VX25W; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 76DF1F272DD1E5E100FFDF1A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = M3TP5VX25W; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 76DF1F292DD1E5E100FFDF1A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIDemo/SwiftUIDemo.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 62PP362QNQ; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.androidremotetv.demo.SwiftUIDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Debug; + }; + 76DF1F2A2DD1E5E100FFDF1A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIDemo/SwiftUIDemo.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 62PP362QNQ; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.androidremotetv.demo.SwiftUIDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 76DF1F172DD1E5DF00FFDF1A /* Build configuration list for PBXProject "SwiftUIDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 76DF1F262DD1E5E100FFDF1A /* Debug */, + 76DF1F272DD1E5E100FFDF1A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 76DF1F282DD1E5E100FFDF1A /* Build configuration list for PBXNativeTarget "SwiftUIDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 76DF1F292DD1E5E100FFDF1A /* Debug */, + 76DF1F2A2DD1E5E100FFDF1A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 76DF1F142DD1E5DF00FFDF1A /* Project object */; +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..ffdfe15 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,85 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/Contents.json b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/ContentView.swift b/Demo/SwiftUIDemo/SwiftUIDemo/ContentView.swift new file mode 100644 index 0000000..dfcb127 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/ContentView.swift @@ -0,0 +1,344 @@ +import SwiftUI + +class RemoteTVManagerViewModel: ObservableObject { + private let remoteManager = RemoteTVManager() + + @Published var pairingState: String = "pairingStateLabel" + @Published var remoteState: String = "remoteStateLabel" + @Published var isCodeEntryEnabled: Bool = false + @Published var codeText: String = "" + @Published var ipAddress: String = "192.168.0.159" // Default IP + + init() { + remoteManager.pairingStateChanged = { [weak self] state in + DispatchQueue.main.async { + self?.pairingState = "Pairing state: " + state + self?.isCodeEntryEnabled = state == "Waiting Code" + } + } + + remoteManager.remoteStateChanged = { [weak self] state in + DispatchQueue.main.async { + self?.remoteState = "Remote state: " + state + } + } + } + + func connect() { + remoteManager.connect(host: ipAddress) + } + + func startPairing() { + // Make sure to disconnect before starting pairing + disconnect() + // Then connect to start pairing + remoteManager.pairing(host: ipAddress) + } + + func sendCode() { + guard !codeText.isEmpty else { return } + remoteManager.sendCode(code: codeText) + } + + func runNetflix() { + remoteManager.runNetflix() + } + + func sendKey(_ key: RemoteKey) { + remoteManager.sendKey(key) + } + + func disconnect() { + remoteManager.disconnect() + } +} + +struct ContentView: View { + @StateObject private var viewModel = RemoteTVManagerViewModel() + + var body: some View { + NavigationView { + ScrollView { + VStack(spacing: 24) { + // Remote Control Section + if viewModel.remoteState.contains("runned") || viewModel.remoteState.contains("connected") { + Divider() + remoteControlSection + } + + // Connection Section + connectionSection + } + .padding() + } + .navigationTitle("Android TV Remote") + .background(Color.gray.opacity(0.1)) + } + } + + private var connectionSection: some View { + VStack(spacing: 16) { + // Status Information + statusInfoView + + // IP Address Field + ipAddressEntryView + + // Connection Buttons + HStack { + // Connect Button + Button(action: { + viewModel.connect() + }) { + Text("Connect") + .fontWeight(.medium) + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(10) + } + + // Pairing Button + Button(action: { + viewModel.startPairing() + }) { + Text("Pairing") + .fontWeight(.medium) + .frame(maxWidth: .infinity) + .padding() + .background(Color.orange) + .foregroundColor(.white) + .cornerRadius(10) + } + } + + // Code Entry Section + if viewModel.isCodeEntryEnabled { + codeEntryView + } + } + .padding() + .background(Color.white) + .cornerRadius(12) + } + + private var ipAddressEntryView: some View { + VStack(alignment: .leading, spacing: 8) { + Text("TV IP Address") + .font(.subheadline) + .foregroundColor(.secondary) + + TextField("192.168.0.xxx", text: $viewModel.ipAddress) + .padding(10) + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + } + } + + private var statusInfoView: some View { + VStack(spacing: 8) { + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .foregroundColor(.blue) + Text(viewModel.pairingState) + .font(.subheadline) + } + .frame(maxWidth: .infinity, alignment: .leading) + + HStack { + Image(systemName: "tv") + .foregroundColor(.blue) + Text(viewModel.remoteState) + .font(.subheadline) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } + + private var codeEntryView: some View { + VStack(spacing: 16) { + Text("Enter the code displayed on your Android TV") + .font(.footnote) + .multilineTextAlignment(.center) + .foregroundColor(.secondary) + + HStack { + TextField("Code", text: $viewModel.codeText) + .keyboardType(.default) + .multilineTextAlignment(.center) + .padding(10) + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + .frame(width: 120) + + Button(action: { + viewModel.sendCode() + }) { + Text("Send") + .padding(.horizontal) + .padding(.vertical, 10) + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + } + } + } + } + + private var remoteControlSection: some View { + VStack(spacing: 24) { + // Disconnect Button + Button(action: viewModel.disconnect) { + HStack { + Image(systemName: "link.badge.minus") + Text("Disconnect") + } + .padding() + .frame(maxWidth: .infinity) + .background(Color.red.opacity(0.8)) + .foregroundColor(.white) + .cornerRadius(10) + } + + // D-Pad Controls + dpadControlsView + + // Volume Controls + volumeControlsView + + // Media Controls + mediaControlsView + } + .padding() + .background(Color.white) + .cornerRadius(12) + } + + private var dpadControlsView: some View { + VStack(spacing: 0) { + Text("Navigation") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 16) + + // Up Button + HStack { + Spacer() + dpadButton(icon: "arrow.up", action: { viewModel.sendKey(.dpadUp) }) + Spacer() + } + + // Left, Center, Right Buttons Row + HStack(spacing: 0) { + dpadButton(icon: "arrow.left", action: { viewModel.sendKey(.dpadLeft) }) + dpadButton(icon: "circle.fill", action: { viewModel.sendKey(.dpadCenter) }) + dpadButton(icon: "arrow.right", action: { viewModel.sendKey(.dpadRight) }) + } + + // Down Button + HStack { + Spacer() + dpadButton(icon: "arrow.down", action: { viewModel.sendKey(.dpadDown) }) + Spacer() + } + + // Home Button + HStack { + Spacer() + Button(action: { viewModel.sendKey(.home) }) { + Image(systemName: "house.fill") + .font(.system(size: 22)) + .foregroundColor(.white) + .frame(width: 60, height: 60) + .background(Color.orange) + .cornerRadius(30) + } + .buttonStyle(PressEffectButtonStyle()) + .padding(.top, 16) + Spacer() + } + } + } + + private func dpadButton(icon: String, action: @escaping () -> Void) -> some View { + Button(action: action) { + Image(systemName: icon) + .font(.system(size: 22)) + .foregroundColor(.white) + .frame(width: 60, height: 60) + .background(Color.blue) + .cornerRadius(30) + } + .buttonStyle(PressEffectButtonStyle()) + .padding(8) + } + + private var volumeControlsView: some View { + VStack(spacing: 16) { + Text("Volume") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + HStack { + Button(action: { viewModel.sendKey(.volumeDown) }) { + HStack { + Image(systemName: "speaker.wave.1.fill") + Text("Vol -") + } + .padding() + .frame(maxWidth: .infinity) + .background(Color.blue.opacity(0.8)) + .foregroundColor(.white) + .cornerRadius(10) + } + + Button(action: { viewModel.sendKey(.volumeUp) }) { + HStack { + Image(systemName: "speaker.wave.3.fill") + Text("Vol +") + } + .padding() + .frame(maxWidth: .infinity) + .background(Color.blue.opacity(0.8)) + .foregroundColor(.white) + .cornerRadius(10) + } + } + } + } + + private var mediaControlsView: some View { + VStack(spacing: 16) { + Text("Applications") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + Button(action: viewModel.runNetflix) { + HStack { + Image(systemName: "play.tv.fill") + Text("Netflix") + } + .padding() + .frame(maxWidth: .infinity) + .background(Color.red) + .foregroundColor(.white) + .cornerRadius(10) + } + } + } +} + +// Button press effect +struct PressEffectButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .scaleEffect(configuration.isPressed ? 0.9 : 1) + .opacity(configuration.isPressed ? 0.8 : 1) + .animation(.easeOut(duration: 0.15), value: configuration.isPressed) + } +} + +#Preview { + ContentView() +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/RemoteTVManager.swift b/Demo/SwiftUIDemo/SwiftUIDemo/RemoteTVManager.swift new file mode 100644 index 0000000..985ee6b --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/RemoteTVManager.swift @@ -0,0 +1,287 @@ +// +// RemoteTVManager.swift +// Demo +// +// + +import Foundation + +enum RemoteKey { + case volumeUp + case volumeDown + case dpadUp + case dpadDown + case dpadLeft + case dpadRight + case dpadCenter + case home + case back + case menu + + var keycode: Key { + switch self { + case .volumeUp: + return .KEYCODE_VOLUME_UP + case .volumeDown: + return .KEYCODE_VOLUME_DOWN + case .dpadUp: + return .KEYCODE_DPAD_UP + case .dpadDown: + return .KEYCODE_DPAD_DOWN + case .dpadLeft: + return .KEYCODE_DPAD_LEFT + case .dpadRight: + return .KEYCODE_DPAD_RIGHT + case .dpadCenter: + return .KEYCODE_DPAD_CENTER + case .home: + return .KEYCODE_HOME + case .back: + return .KEYCODE_BACK + case .menu: + return .KEYCODE_MENU + } + } +} + +class RemoteTVManager { + private let queue = DispatchQueue(label: "queue") + + private let pairingManager: PairingManager + private let remoteManager: RemoteManager + + public var pairingStateChanged: ((String)->())? + public var remoteStateChanged: ((String)->())? + + init() { + let cryptoManager = CryptoManager() + + cryptoManager.clientPublicCertificate = { + guard let url = Bundle.main.url(forResource: "cert", withExtension: "der") else { + return .Error(.loadCertFromURLError(MyError.certNotFound)) + } + + return CertManager().getSecKey(url) + } + + let tlsManager = TLSManager { + guard let url = Bundle.main.url(forResource: "cert", withExtension: "p12") else { + return .Error(.loadCertFromURLError(MyError.certNotFound)) + } + + return CertManager().cert(url, "azerty") + } + + tlsManager.secTrustClosure = { secTrust in + cryptoManager.serverPublicCertificate = { + if #available(iOS 14.0, *) { + guard let key = SecTrustCopyKey(secTrust) else { + return .Error(.secTrustCopyKeyError) + } + return .Result(key) + } else { + guard let key = SecTrustCopyPublicKey(secTrust) else { + return .Error(.secTrustCopyKeyError) + } + return .Result(key) + } + } + } + + pairingManager = PairingManager(tlsManager, cryptoManager, DefaultLogger()) + remoteManager = RemoteManager(tlsManager, CommandNetwork.DeviceInfo("client", "iPhone", "1.0.0", "example_app", "235"), DefaultLogger()) + } + + func pairing(host: String) { + queue.async { + self.pairingManager.stateChanged = { [weak self] state in + self?.pairingStateChanged?(state.toString()) + } + + self.pairingManager.connect(host, "TV Remote", "iPhone") + } + } + + func connect(host: String) { + queue.async { + self.remoteManager.disconnect() + self.pairingManager.disconnect() + + self.remoteManager.stateChanged = { [weak self] remoteState in + self?.remoteStateChanged?(remoteState.toString()) + + if case .error(.connectionWaitingError) = remoteState { + self?.pairingManager.stateChanged = { pairingState in + self?.pairingStateChanged?(pairingState.toString()) + + if case .successPaired = pairingState { + self?.remoteManager.connect(host) + } + } + + self?.pairingManager.connect(host, "client", "iPhone") + } + } + + self.remoteManager.connect(host) + } + } + + func sendCode(code: String) { + queue.async { + self.pairingManager.sendSecret(code) + } + } + + func runNetflix() { + queue.async { + self.remoteManager.send(DeepLink("https://www.netflix.com/title")) + } + } + + func sendKey(_ key: RemoteKey) { + queue.async { + self.remoteManager.send(KeyPress(key.keycode)) + } + } + + func disconnect() { + queue.async { + self.pairingManager.disconnect() + self.remoteManager.disconnect() + self.pairingStateChanged?("idle") + self.remoteStateChanged?("idle") + } + } +} + +public enum MyError: Error { + case certNotFound +} + +extension RemoteManager.RemoteState { + func toString() -> String { + switch self { + case .idle: + return "idle" + case .connectionSetUp: + return "connection Set Up" + case .connectionPrepairing: + return "connection Prepairing" + case .connected: + return "connected" + case .fisrtConfigMessageReceived(let info): + return "fisrt Config Message Received: vendor: \(info.vendor) model: \(info.model)" + case .firstConfigSent: + return "first Config Sent" + case .secondConfigSent: + return "second Config Sent" + case .paired(let runningApp): + return "Paired! Current runned app " + (runningApp ?? "") + case .error(let error): + return "Error: " + error.toString() + } + } +} + +extension PairingManager.PairingState { + func toString() -> String { + switch self { + case .idle: + return "idle" + case .extractTLSparams: + return "Extract TLS params" + case .connectionSetUp: + return "Connection Set Up" + case .connectionPrepairing: + return "Connection Prepairing" + case .connected: + return "Connected" + case .pairingRequestSent: + return "Pairing Request Sent" + case .pairingResponseSuccess: + return "Pairing Response Success" + case .optionRequestSent: + return "Option Request Sent" + case .optionResponseSuccess: + return "Option Response Success" + case .confirmationRequestSent: + return "Confirmation Request Sent" + case .confirmationResponseSuccess: + return "Confirmation Response Success" + case .waitingCode: + return "Waiting Code" + case .secretSent: + return "Secret Sent" + case .successPaired: + return "Success Paired" + case .error(let error): + return "Error: " + error.toString() + } + } +} + +extension AndroidTVRemoteControlError { + func toString() -> String { + switch self { + case .unexpectedCertData: + return "unexpected Cert Data" + case .extractCFTypeRefError: + return "extract CFTypeRef Error" + case .secIdentityCreateError: + return "sec Identity Create Error" + case .toLongNames(let description): + return "to Long Names" + description + case .connectionCanceled: + return "connection Canceled" + case .pairingNotSuccess: + return "pairing Not Success" + case .optionNotSuccess: + return "option Not Success" + case .configurationNotSuccess: + return "configuration Not Success" + case .secretNotSuccess: + return "secret Not Success" + case .connectionWaitingError(let error): + return "connection Waiting Error: " + error.localizedDescription + case .connectionFailed: + return "connection Failed" + case .receiveDataError: + return "receive Data Error" + case .sendDataError: + return "send Data Error" + case .invalidCode(let description): + return "invalid Code " + description + case .wrongCode: + return "wrong Code" + case .noSecAttributes: + return "no SecAttributes" + case .notRSAKey: + return "not RSA Key" + case .notPublicKey: + return "not Public Key" + case .noKeySizeAttribute: + return "no Key Size Attribute" + case .noValueData: + return "no Value Data" + case .invalidCertData: + return "invalid Cert Data" + case .createCertFromDataError: + return "create Cert From Data Error" + case .noClientPublicCertificate: + return "no Client Public Certificate" + case .noServerPublicCertificate: + return "no Server Public Certificate" + case .secTrustCopyKeyError: + return "sec Trust Copy Key Error" + case .loadCertFromURLError: + return "load Cert From URL Error" + case .secPKCS12ImportNotSuccess: + return "secPKCS12Import Not Success" + case .createTrustObjectError: + return "create Trust Object Error" + case .secTrustCreateWithCertificatesNotSuccess: + return "secTrust Create With Certificates Not Success" + } + } +} diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemo.entitlements b/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemo.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemo.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemoApp.swift b/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemoApp.swift new file mode 100644 index 0000000..ca05cc3 --- /dev/null +++ b/Demo/SwiftUIDemo/SwiftUIDemo/SwiftUIDemoApp.swift @@ -0,0 +1,17 @@ +// +// SwiftUIDemoApp.swift +// SwiftUIDemo +// +// Created by Florian Gabach on 12/05/2025. +// + +import SwiftUI + +@main +struct SwiftUIDemoApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}