diff --git a/wled-native.xcodeproj/project.pbxproj b/wled-native.xcodeproj/project.pbxproj index 4b291b2..3b3487c 100644 --- a/wled-native.xcodeproj/project.pbxproj +++ b/wled-native.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -21,11 +21,8 @@ C946FB982B6C953F00D5B00D /* WLEDWebsocketHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB972B6C953F00D5B00D /* WLEDWebsocketHandler.swift */; }; C946FB9A2B6CA10600D5B00D /* DeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB992B6CA10600D5B00D /* DeviceExtensions.swift */; }; C946FB9C2B6E032800D5B00D /* WLEDChangeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB9B2B6E032800D5B00D /* WLEDChangeStateRequest.swift */; }; - C946FB9E2B6E0FE000D5B00D /* DeviceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB9D2B6E0FE000D5B00D /* DeviceState.swift */; }; - C946FBA02B6E103600D5B00D /* DeviceStateFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB9F2B6E103600D5B00D /* DeviceStateFactory.swift */; }; C946FBA22B702F8B00D5B00D /* WLEDSoftwareUpdateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FBA12B702F8B00D5B00D /* WLEDSoftwareUpdateRequest.swift */; }; C948F4952B3D1C480092C4B1 /* DeviceUpdateDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C948F4942B3D1C480092C4B1 /* DeviceUpdateDetails.swift */; }; - C948F4982B3D2B330092C4B1 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = C948F4972B3D2B330092C4B1 /* MarkdownUI */; }; C948F49A2B3E2B590092C4B1 /* DeviceUpdateInstalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = C948F4992B3E2B590092C4B1 /* DeviceUpdateInstalling.swift */; }; C95B4A832B3E3F540081F4C5 /* BackgroundBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95B4A822B3E3F540081F4C5 /* BackgroundBlurView.swift */; }; C96440E22AEF6B6000C51204 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96440E12AEF6B6000C51204 /* Persistence.swift */; }; @@ -67,6 +64,66 @@ C9FCC3A3293DBD2200DEDDEA /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC3A0293DBD2200DEDDEA /* Info.swift */; }; C9FCC3A4293DBD2200DEDDEA /* Leds.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC39C293DBD2200DEDDEA /* Leds.swift */; }; C9FCC3A5293DBD2200DEDDEA /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC398293DBD2200DEDDEA /* Segment.swift */; }; + E149382E2CB3C930000F0224 /* WLEDRequestManagerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E149382D2CB3C930000F0224 /* WLEDRequestManagerProvider.swift */; }; + E149382F2CB3C930000F0224 /* WLEDRequestManagerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E149382D2CB3C930000F0224 /* WLEDRequestManagerProvider.swift */; }; + E15C19DD2CA1CB4000431441 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9A9E5C92918D1C4005B5C22 /* Assets.xcassets */; }; + E15C19DE2CA1CB7F00431441 /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99BEF562AF8B926001AD80A /* DeviceView.swift */; }; + E15C19DF2CA1CB7F00431441 /* FetchedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9767DE22AFAE65C002F3EA9 /* FetchedObjects.swift */; }; + E15C19E02CA1CB7F00431441 /* DeviceListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96440E72AF095B500C51204 /* DeviceListItemView.swift */; }; + E15C19E12CA1CB7F00431441 /* DeviceEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9767DDF2AFA04E0002F3EA9 /* DeviceEditView.swift */; }; + E15C19E22CA1CB7F00431441 /* DeviceUpdateDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C948F4942B3D1C480092C4B1 /* DeviceUpdateDetails.swift */; }; + E15C19E32CA1CB7F00431441 /* DeviceUpdateInstalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = C948F4992B3E2B590092C4B1 /* DeviceUpdateInstalling.swift */; }; + E15C19E42CA1CB7F00431441 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99BEF582AF8BA79001AD80A /* WebView.swift */; }; + E15C19E52CA1CB7F00431441 /* DeviceAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9767DDD2AF9F435002F3EA9 /* DeviceAddView.swift */; }; + E15C19E62CA1CB7F00431441 /* BackgroundBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95B4A822B3E3F540081F4C5 /* BackgroundBlurView.swift */; }; + E15C19E72CA1CB7F00431441 /* ToolbarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99BAC2D2B735875009BC476 /* ToolbarBadge.swift */; }; + E15C19E82CA1CB7F00431441 /* DeviceListFilterAndSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9AF21582AFB47BB00E03749 /* DeviceListFilterAndSort.swift */; }; + E15C19E92CA1CB7F00431441 /* DeviceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9865AF52AEF1902000B2714 /* DeviceListView.swift */; }; + E15C19EA2CA1CB8600431441 /* WLEDWebsocketHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB972B6C953F00D5B00D /* WLEDWebsocketHandler.swift */; }; + E15C19EB2CA1CB8600431441 /* ReleaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4AA2B30175400420E93 /* ReleaseService.swift */; }; + E15C19EC2CA1CB8600431441 /* DeviceUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B9FB3D2B3FB7280003E6F9 /* DeviceUpdateService.swift */; }; + E15C19ED2CA1CB8600431441 /* WLEDRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB932B6C93F000D5B00D /* WLEDRequestHandler.swift */; }; + E15C19EE2CA1CB8600431441 /* DeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB992B6CA10600D5B00D /* DeviceExtensions.swift */; }; + E15C19EF2CA1CB8600431441 /* DiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99BEF542AF49513001AD80A /* DiscoveryService.swift */; }; + E15C19F12CA1CB8600431441 /* WLEDRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB822B68C3B700D5B00D /* WLEDRequestManager.swift */; }; + E15C19F22CA1CB8600431441 /* GithubApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4A62B30107800420E93 /* GithubApi.swift */; }; + E15C19F32CA1CB8600431441 /* WLEDRepoApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4A82B30165900420E93 /* WLEDRepoApi.swift */; }; + E15C19F42CA1CB8600431441 /* WLEDJsonApiHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB952B6C950200D5B00D /* WLEDJsonApiHandler.swift */; }; + E15C19F62CA1CB8C00431441 /* Reactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4A02B269F8B00420E93 /* Reactions.swift */; }; + E15C19F72CA1CB8C00431441 /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD49E2B269F6800420E93 /* Author.swift */; }; + E15C19F82CA1CB8C00431441 /* Release.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4A22B269F9400420E93 /* Release.swift */; }; + E15C19F92CA1CB8C00431441 /* Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4A42B269FA400420E93 /* Uploader.swift */; }; + E15C19FA2CA1CB8C00431441 /* GithubAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD49C2B269F3000420E93 /* GithubAsset.swift */; }; + E15C19FB2CA1CB9300431441 /* WLEDRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB8D2B6C8E8800D5B00D /* WLEDRequest.swift */; }; + E15C19FC2CA1CB9300431441 /* WLEDRequestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC38A293DB8A000DEDDEA /* WLEDRequestState.swift */; }; + E15C19FD2CA1CB9300431441 /* WLEDRefreshRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB8F2B6C92C400D5B00D /* WLEDRefreshRequest.swift */; }; + E15C19FE2CA1CB9300431441 /* WLEDChangeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FB9B2B6E032800D5B00D /* WLEDChangeStateRequest.swift */; }; + E15C19FF2CA1CB9300431441 /* WLEDSoftwareUpdateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FBA12B702F8B00D5B00D /* WLEDSoftwareUpdateRequest.swift */; }; + E15C1A002CA1CB9D00431441 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96440E12AEF6B6000C51204 /* Persistence.swift */; }; + E15C1A012CA1CB9D00431441 /* DeviceStateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC396293DBD1800DEDDEA /* DeviceStateInfo.swift */; }; + E15C1A022CA1CB9D00431441 /* Wifi.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC390293DB95500DEDDEA /* Wifi.swift */; }; + E15C1A032CA1CB9D00431441 /* Branch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4992B2519BC00420E93 /* Branch.swift */; }; + E15C1A042CA1CB9D00431441 /* Nightlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC392293DB9A500DEDDEA /* Nightlight.swift */; }; + E15C1A052CA1CB9D00431441 /* FileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC388293DB86900DEDDEA /* FileSystem.swift */; }; + E15C1A062CA1CB9D00431441 /* WledState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC384293DB69F00DEDDEA /* WledState.swift */; }; + E15C1A072CA1CB9D00431441 /* Leds.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC39C293DBD2200DEDDEA /* Leds.swift */; }; + E15C1A082CA1CB9D00431441 /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC398293DBD2200DEDDEA /* Segment.swift */; }; + E15C1A092CA1CB9D00431441 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FCC3A0293DBD2200DEDDEA /* Info.swift */; }; + E15C1A0A2CA1CBC900431441 /* WLEDNativeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A9E5C02918D1C3005B5C22 /* WLEDNativeApp.swift */; }; + E15C1A272CA1CD3800431441 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = E15C1A262CA1CD3800431441 /* Collections */; }; + E15C1A292CA1CD3C00431441 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = E15C1A282CA1CD3C00431441 /* DequeModule */; }; + E15C1A2D2CA1CD4400431441 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E15C1A2C2CA1CD4400431441 /* OrderedCollections */; }; + E15C1A2E2CA1CDB900431441 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9D43A7D29BAE3BA005A71C2 /* Localizable.strings */; }; + E15C1A2F2CA1CDC100431441 /* wled_native_data.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C97CD4962B25000300420E93 /* wled_native_data.xcdatamodeld */; }; + E15C1A302CA1CDC700431441 /* 012_rip.png in Resources */ = {isa = PBXBuildFile; fileRef = C93470AD29D905BD0085D9D5 /* 012_rip.png */; }; + E15C1A312CA1CDCE00431441 /* errorPage.en.html in Resources */ = {isa = PBXBuildFile; fileRef = C93470AB29D904C20085D9D5 /* errorPage.en.html */; }; + E15C1A322CA1CDCE00431441 /* errorPage.fr.html in Resources */ = {isa = PBXBuildFile; fileRef = C96827222B01AFB900A5DDEC /* errorPage.fr.html */; }; + E15C1A332CA1CDCE00431441 /* errorPage.css in Resources */ = {isa = PBXBuildFile; fileRef = C96827202B01AF5900A5DDEC /* errorPage.css */; }; + E15C1A342CA1CDD400431441 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C906561F2BBA5B9500D4ADAF /* PrivacyInfo.xcprivacy */; }; + E15C1A352CA1CDDD00431441 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C9C4B47E2AC0050500791D20 /* README.md */; }; + E15C1A362CA1CE0600431441 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9F82F2429BBD0E100B8A5EE /* InfoPlist.strings */; }; + E15C1A3A2CA1D4FD00431441 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = E15C1A392CA1D4FD00431441 /* MarkdownUI */; }; + E15C1A3C2CA1D50500431441 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = E15C1A3B2CA1D50500431441 /* MarkdownUI */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -81,8 +138,6 @@ C946FB972B6C953F00D5B00D /* WLEDWebsocketHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WLEDWebsocketHandler.swift; sourceTree = ""; }; C946FB992B6CA10600D5B00D /* DeviceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceExtensions.swift; sourceTree = ""; }; C946FB9B2B6E032800D5B00D /* WLEDChangeStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WLEDChangeStateRequest.swift; sourceTree = ""; }; - C946FB9D2B6E0FE000D5B00D /* DeviceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceState.swift; sourceTree = ""; }; - C946FB9F2B6E103600D5B00D /* DeviceStateFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStateFactory.swift; sourceTree = ""; }; C946FBA12B702F8B00D5B00D /* WLEDSoftwareUpdateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WLEDSoftwareUpdateRequest.swift; sourceTree = ""; }; C948F4942B3D1C480092C4B1 /* DeviceUpdateDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUpdateDetails.swift; sourceTree = ""; }; C948F4992B3E2B590092C4B1 /* DeviceUpdateInstalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUpdateInstalling.swift; sourceTree = ""; }; @@ -130,17 +185,35 @@ C9FCC398293DBD2200DEDDEA /* Segment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Segment.swift; sourceTree = ""; }; C9FCC39C293DBD2200DEDDEA /* Leds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Leds.swift; sourceTree = ""; }; C9FCC3A0293DBD2200DEDDEA /* Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = ""; }; + E149382D2CB3C930000F0224 /* WLEDRequestManagerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WLEDRequestManagerProvider.swift; sourceTree = ""; }; + E15C19CE2CA1CABD00431441 /* wled-native-osx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "wled-native-osx.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + E15C1A452CA1DC4B00431441 /* wled_native_osx.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = wled_native_osx.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + E15C1A482CA1DFF000431441 /* Preview Content */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Preview Content"; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ C9A9E5BA2918D1C3005B5C22 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C946FB862B68C53600D5B00D /* Collections in Frameworks */, - C948F4982B3D2B330092C4B1 /* MarkdownUI in Frameworks */, C946FB8A2B68C53600D5B00D /* OrderedCollections in Frameworks */, C946FB882B68C53600D5B00D /* DequeModule in Frameworks */, + E15C1A3C2CA1D50500431441 /* MarkdownUI in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E15C19CB2CA1CABD00431441 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E15C1A272CA1CD3800431441 /* Collections in Frameworks */, + E15C1A2D2CA1CD4400431441 /* OrderedCollections in Frameworks */, + E15C1A292CA1CD3C00431441 /* DequeModule in Frameworks */, + E15C1A3A2CA1D4FD00431441 /* MarkdownUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -177,6 +250,7 @@ C946FB922B6C93DA00D5B00D /* DeviceApi */ = { isa = PBXGroup; children = ( + E149382D2CB3C930000F0224 /* WLEDRequestManagerProvider.swift */, C946FB822B68C3B700D5B00D /* WLEDRequestManager.swift */, C946FB932B6C93F000D5B00D /* WLEDRequestHandler.swift */, C946FB952B6C950200D5B00D /* WLEDJsonApiHandler.swift */, @@ -239,6 +313,7 @@ isa = PBXGroup; children = ( C9A9E5BF2918D1C3005B5C22 /* wled-native */, + E15C1A252CA1CD3800431441 /* Frameworks */, C9A9E5BE2918D1C3005B5C22 /* Products */, ); sourceTree = ""; @@ -247,6 +322,7 @@ isa = PBXGroup; children = ( C9A9E5BD2918D1C3005B5C22 /* wled-native.app */, + E15C19CE2CA1CABD00431441 /* wled-native-osx.app */, ); name = Products; sourceTree = ""; @@ -254,6 +330,7 @@ C9A9E5BF2918D1C3005B5C22 /* wled-native */ = { isa = PBXGroup; children = ( + E15C1A482CA1DFF000431441 /* Preview Content */, C9865AF42AEF18E7000B2714 /* View */, C9FCC37E293DAF8300DEDDEA /* Service */, C96BF3F7291FFDC90011BC3B /* Model */, @@ -261,6 +338,7 @@ C9A9E5C02918D1C3005B5C22 /* WLEDNativeApp.swift */, C9A9E5C92918D1C4005B5C22 /* Assets.xcassets */, C97CD4962B25000300420E93 /* wled_native_data.xcdatamodeld */, + E15C1A452CA1DC4B00431441 /* wled_native_osx.entitlements */, C9D43A7D29BAE3BA005A71C2 /* Localizable.strings */, C9F82F2429BBD0E100B8A5EE /* InfoPlist.strings */, C93470AD29D905BD0085D9D5 /* 012_rip.png */, @@ -284,8 +362,6 @@ C97CD4A82B30165900420E93 /* WLEDRepoApi.swift */, C97CD4AA2B30175400420E93 /* ReleaseService.swift */, C9B9FB3D2B3FB7280003E6F9 /* DeviceUpdateService.swift */, - C946FB9D2B6E0FE000D5B00D /* DeviceState.swift */, - C946FB9F2B6E103600D5B00D /* DeviceStateFactory.swift */, ); path = Service; sourceTree = ""; @@ -299,6 +375,13 @@ path = DeviceApi; sourceTree = ""; }; + E15C1A252CA1CD3800431441 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -316,15 +399,41 @@ ); name = "wled-native"; packageProductDependencies = ( - C948F4972B3D2B330092C4B1 /* MarkdownUI */, C946FB852B68C53600D5B00D /* Collections */, C946FB872B68C53600D5B00D /* DequeModule */, C946FB892B68C53600D5B00D /* OrderedCollections */, + E15C1A3B2CA1D50500431441 /* MarkdownUI */, ); productName = "wled-native"; productReference = C9A9E5BD2918D1C3005B5C22 /* wled-native.app */; productType = "com.apple.product-type.application"; }; + E15C19CD2CA1CABD00431441 /* wled-native-osx */ = { + isa = PBXNativeTarget; + buildConfigurationList = E15C19DA2CA1CABE00431441 /* Build configuration list for PBXNativeTarget "wled-native-osx" */; + buildPhases = ( + E15C19CA2CA1CABD00431441 /* Sources */, + E15C19CB2CA1CABD00431441 /* Frameworks */, + E15C19CC2CA1CABD00431441 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + E15C1A482CA1DFF000431441 /* Preview Content */, + ); + name = "wled-native-osx"; + packageProductDependencies = ( + E15C1A262CA1CD3800431441 /* Collections */, + E15C1A282CA1CD3C00431441 /* DequeModule */, + E15C1A2C2CA1CD4400431441 /* OrderedCollections */, + E15C1A392CA1D4FD00431441 /* MarkdownUI */, + ); + productName = "wled-native-osx"; + productReference = E15C19CE2CA1CABD00431441 /* wled-native-osx.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -332,12 +441,15 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1500; TargetAttributes = { C9A9E5BC2918D1C3005B5C22 = { CreatedOnToolsVersion = 14.1; }; + E15C19CD2CA1CABD00431441 = { + CreatedOnToolsVersion = 16.0; + }; }; }; buildConfigurationList = C9A9E5B82918D1C3005B5C22 /* Build configuration list for PBXProject "wled-native" */; @@ -359,6 +471,7 @@ projectRoot = ""; targets = ( C9A9E5BC2918D1C3005B5C22 /* wled-native */, + E15C19CD2CA1CABD00431441 /* wled-native-osx */, ); }; /* End PBXProject section */ @@ -381,6 +494,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E15C19CC2CA1CABD00431441 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E15C1A2E2CA1CDB900431441 /* Localizable.strings in Resources */, + E15C19DD2CA1CB4000431441 /* Assets.xcassets in Resources */, + E15C1A302CA1CDC700431441 /* 012_rip.png in Resources */, + E15C1A312CA1CDCE00431441 /* errorPage.en.html in Resources */, + E15C1A322CA1CDCE00431441 /* errorPage.fr.html in Resources */, + E15C1A352CA1CDDD00431441 /* README.md in Resources */, + E15C1A342CA1CDD400431441 /* PrivacyInfo.xcprivacy in Resources */, + E15C1A332CA1CDCE00431441 /* errorPage.css in Resources */, + E15C1A362CA1CE0600431441 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -392,6 +521,7 @@ C946FB962B6C950200D5B00D /* WLEDJsonApiHandler.swift in Sources */, C97CD4A52B269FA400420E93 /* Uploader.swift in Sources */, C946FB9A2B6CA10600D5B00D /* DeviceExtensions.swift in Sources */, + E149382E2CB3C930000F0224 /* WLEDRequestManagerProvider.swift in Sources */, C946FB982B6C953F00D5B00D /* WLEDWebsocketHandler.swift in Sources */, C9FCC389293DB86900DEDDEA /* FileSystem.swift in Sources */, C97CD4A12B269F8B00420E93 /* Reactions.swift in Sources */, @@ -424,9 +554,7 @@ C9FCC3A5293DBD2200DEDDEA /* Segment.swift in Sources */, C9767DDE2AF9F435002F3EA9 /* DeviceAddView.swift in Sources */, C9A9E5C12918D1C3005B5C22 /* WLEDNativeApp.swift in Sources */, - C946FBA02B6E103600D5B00D /* DeviceStateFactory.swift in Sources */, C946FB8E2B6C8E8800D5B00D /* WLEDRequest.swift in Sources */, - C946FB9E2B6E0FE000D5B00D /* DeviceState.swift in Sources */, C97CD49A2B2519BC00420E93 /* Branch.swift in Sources */, C99BEF592AF8BA79001AD80A /* WebView.swift in Sources */, C946FBA22B702F8B00D5B00D /* WLEDSoftwareUpdateRequest.swift in Sources */, @@ -437,6 +565,58 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E15C19CA2CA1CABD00431441 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E15C19EA2CA1CB8600431441 /* WLEDWebsocketHandler.swift in Sources */, + E15C19EB2CA1CB8600431441 /* ReleaseService.swift in Sources */, + E15C19EC2CA1CB8600431441 /* DeviceUpdateService.swift in Sources */, + E15C19ED2CA1CB8600431441 /* WLEDRequestHandler.swift in Sources */, + E149382F2CB3C930000F0224 /* WLEDRequestManagerProvider.swift in Sources */, + E15C19EE2CA1CB8600431441 /* DeviceExtensions.swift in Sources */, + E15C19EF2CA1CB8600431441 /* DiscoveryService.swift in Sources */, + E15C1A0A2CA1CBC900431441 /* WLEDNativeApp.swift in Sources */, + E15C19F12CA1CB8600431441 /* WLEDRequestManager.swift in Sources */, + E15C19F22CA1CB8600431441 /* GithubApi.swift in Sources */, + E15C19F32CA1CB8600431441 /* WLEDRepoApi.swift in Sources */, + E15C19F42CA1CB8600431441 /* WLEDJsonApiHandler.swift in Sources */, + E15C19DE2CA1CB7F00431441 /* DeviceView.swift in Sources */, + E15C19DF2CA1CB7F00431441 /* FetchedObjects.swift in Sources */, + E15C19E02CA1CB7F00431441 /* DeviceListItemView.swift in Sources */, + E15C19FB2CA1CB9300431441 /* WLEDRequest.swift in Sources */, + E15C19FC2CA1CB9300431441 /* WLEDRequestState.swift in Sources */, + E15C19FD2CA1CB9300431441 /* WLEDRefreshRequest.swift in Sources */, + E15C19FE2CA1CB9300431441 /* WLEDChangeStateRequest.swift in Sources */, + E15C19FF2CA1CB9300431441 /* WLEDSoftwareUpdateRequest.swift in Sources */, + E15C19E12CA1CB7F00431441 /* DeviceEditView.swift in Sources */, + E15C19E22CA1CB7F00431441 /* DeviceUpdateDetails.swift in Sources */, + E15C19E32CA1CB7F00431441 /* DeviceUpdateInstalling.swift in Sources */, + E15C19E42CA1CB7F00431441 /* WebView.swift in Sources */, + E15C19E52CA1CB7F00431441 /* DeviceAddView.swift in Sources */, + E15C19E62CA1CB7F00431441 /* BackgroundBlurView.swift in Sources */, + E15C19E72CA1CB7F00431441 /* ToolbarBadge.swift in Sources */, + E15C19E82CA1CB7F00431441 /* DeviceListFilterAndSort.swift in Sources */, + E15C19E92CA1CB7F00431441 /* DeviceListView.swift in Sources */, + E15C19F62CA1CB8C00431441 /* Reactions.swift in Sources */, + E15C19F72CA1CB8C00431441 /* Author.swift in Sources */, + E15C19F82CA1CB8C00431441 /* Release.swift in Sources */, + E15C19F92CA1CB8C00431441 /* Uploader.swift in Sources */, + E15C1A002CA1CB9D00431441 /* Persistence.swift in Sources */, + E15C1A012CA1CB9D00431441 /* DeviceStateInfo.swift in Sources */, + E15C1A022CA1CB9D00431441 /* Wifi.swift in Sources */, + E15C1A032CA1CB9D00431441 /* Branch.swift in Sources */, + E15C1A042CA1CB9D00431441 /* Nightlight.swift in Sources */, + E15C1A052CA1CB9D00431441 /* FileSystem.swift in Sources */, + E15C1A2F2CA1CDC100431441 /* wled_native_data.xcdatamodeld in Sources */, + E15C1A062CA1CB9D00431441 /* WledState.swift in Sources */, + E15C1A072CA1CB9D00431441 /* Leds.swift in Sources */, + E15C1A082CA1CB9D00431441 /* Segment.swift in Sources */, + E15C1A092CA1CB9D00431441 /* Info.swift in Sources */, + E15C19FA2CA1CB8C00431441 /* GithubAsset.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ @@ -520,6 +700,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -575,6 +756,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 6.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -586,9 +768,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = RWLNCUSDCJ; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "wled-native/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = WLED; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "WLED uses the local network to connect to WLED devices around you."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -604,8 +787,11 @@ PRODUCT_BUNDLE_IDENTIFIER = "ca.cgagnier.wled-native"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -617,9 +803,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = RWLNCUSDCJ; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "wled-native/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = WLED; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "WLED uses the local network to connect to WLED devices around you."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -635,12 +822,88 @@ PRODUCT_BUNDLE_IDENTIFIER = "ca.cgagnier.wled-native"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; + E15C19DB2CA1CABE00431441 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "ASSETCATALOG_COMPILER_APPICON_NAME[sdk=macosx*]" = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "wled-native/wled_native_osx.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"wled-native/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = WLED; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 6.1; + PRODUCT_BUNDLE_IDENTIFIER = "ca.cgagnier.wled-native"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + }; + name = Debug; + }; + E15C19DC2CA1CABE00431441 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "ASSETCATALOG_COMPILER_APPICON_NAME[sdk=macosx*]" = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "wled-native/wled_native_osx.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"wled-native/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = WLED; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 6.1; + PRODUCT_BUNDLE_IDENTIFIER = "ca.cgagnier.wled-native"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -662,6 +925,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E15C19DA2CA1CABE00431441 /* Build configuration list for PBXNativeTarget "wled-native-osx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E15C19DB2CA1CABE00431441 /* Debug */, + E15C19DC2CA1CABE00431441 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -677,8 +949,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui"; requirement = { - kind = exactVersion; - version = 2.2.0; + kind = upToNextMajorVersion; + minimumVersion = 2.2.0; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -699,7 +971,27 @@ package = C946FB842B68C53600D5B00D /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; - C948F4972B3D2B330092C4B1 /* MarkdownUI */ = { + E15C1A262CA1CD3800431441 /* Collections */ = { + isa = XCSwiftPackageProductDependency; + package = C946FB842B68C53600D5B00D /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = Collections; + }; + E15C1A282CA1CD3C00431441 /* DequeModule */ = { + isa = XCSwiftPackageProductDependency; + package = C946FB842B68C53600D5B00D /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = DequeModule; + }; + E15C1A2C2CA1CD4400431441 /* OrderedCollections */ = { + isa = XCSwiftPackageProductDependency; + package = C946FB842B68C53600D5B00D /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = OrderedCollections; + }; + E15C1A392CA1D4FD00431441 /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = C948F4962B3D2B320092C4B1 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + E15C1A3B2CA1D50500431441 /* MarkdownUI */ = { isa = XCSwiftPackageProductDependency; package = C948F4962B3D2B320092C4B1 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; productName = MarkdownUI; diff --git a/wled-native.xcodeproj/xcshareddata/xcschemes/wled-native-osx.xcscheme b/wled-native.xcodeproj/xcshareddata/xcschemes/wled-native-osx.xcscheme new file mode 100644 index 0000000..32c0cd2 --- /dev/null +++ b/wled-native.xcodeproj/xcshareddata/xcschemes/wled-native-osx.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wled-native/Model/DeviceApi/Request/WLEDChangeStateRequest.swift b/wled-native/Model/DeviceApi/Request/WLEDChangeStateRequest.swift index e790ba4..13525b5 100644 --- a/wled-native/Model/DeviceApi/Request/WLEDChangeStateRequest.swift +++ b/wled-native/Model/DeviceApi/Request/WLEDChangeStateRequest.swift @@ -1,8 +1,6 @@ import Foundation -import CoreData struct WLEDChangeStateRequest: WLEDRequest { let state: WLEDStateChange - let context: NSManagedObjectContext } diff --git a/wled-native/Model/DeviceApi/Request/WLEDRefreshRequest.swift b/wled-native/Model/DeviceApi/Request/WLEDRefreshRequest.swift index cda0ffa..732e5a3 100644 --- a/wled-native/Model/DeviceApi/Request/WLEDRefreshRequest.swift +++ b/wled-native/Model/DeviceApi/Request/WLEDRefreshRequest.swift @@ -1,7 +1,4 @@ import Foundation -import CoreData -struct WLEDRefreshRequest: WLEDRequest { - let context: NSManagedObjectContext -} +struct WLEDRefreshRequest: WLEDRequest { } diff --git a/wled-native/Model/DeviceApi/Request/WLEDRequest.swift b/wled-native/Model/DeviceApi/Request/WLEDRequest.swift index 7d9d6cd..166b670 100644 --- a/wled-native/Model/DeviceApi/Request/WLEDRequest.swift +++ b/wled-native/Model/DeviceApi/Request/WLEDRequest.swift @@ -2,6 +2,4 @@ import Foundation import CoreData -protocol WLEDRequest { - var context: NSManagedObjectContext { get } -} +protocol WLEDRequest: Sendable { } diff --git a/wled-native/Model/DeviceApi/Request/WLEDSoftwareUpdateRequest.swift b/wled-native/Model/DeviceApi/Request/WLEDSoftwareUpdateRequest.swift index e8ca9c1..8e17725 100644 --- a/wled-native/Model/DeviceApi/Request/WLEDSoftwareUpdateRequest.swift +++ b/wled-native/Model/DeviceApi/Request/WLEDSoftwareUpdateRequest.swift @@ -1,10 +1,8 @@ import Foundation -import CoreData struct WLEDSoftwareUpdateRequest: WLEDRequest { - let context: NSManagedObjectContext let binaryFile: URL - let onCompletion: () -> () - let onFailure: () -> () + let onCompletion: @MainActor () -> () + let onFailure: @MainActor () -> () } diff --git a/wled-native/Persistence.swift b/wled-native/Persistence.swift index b7fbb48..f8694e0 100644 --- a/wled-native/Persistence.swift +++ b/wled-native/Persistence.swift @@ -4,7 +4,7 @@ import CoreData struct PersistenceController { static let shared = PersistenceController() - static var preview: PersistenceController = { + static let preview: PersistenceController = { let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext for i in 0..<10 { diff --git a/wled-native/Preview Content/Preview Assets.xcassets/Contents.json b/wled-native/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/wled-native/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/wled-native/Service/DeviceApi/DeviceExtensions.swift b/wled-native/Service/DeviceApi/DeviceExtensions.swift index 66d350d..9eddb46 100644 --- a/wled-native/Service/DeviceApi/DeviceExtensions.swift +++ b/wled-native/Service/DeviceApi/DeviceExtensions.swift @@ -1,11 +1,14 @@ import Foundation -extension Device { - var requestManager: WLEDRequestManager { - get { - return DeviceStateFactory.shared.getStateForDevice(self).getRequestManager(device: self) - } +extension Device: @unchecked Sendable { + + func refresh() async { + await self.getRequestManager().addRequest(WLEDRefreshRequest()) + } + + func getRequestManager() async -> WLEDRequestManager { + await WLEDRequestManagerProvider.shared.getRequestManager(device: self) } func getColor(state: WledState) -> Int64 { diff --git a/wled-native/Service/DeviceApi/WLEDJsonApiHandler.swift b/wled-native/Service/DeviceApi/WLEDJsonApiHandler.swift index a1c75d8..efe3ed5 100644 --- a/wled-native/Service/DeviceApi/WLEDJsonApiHandler.swift +++ b/wled-native/Service/DeviceApi/WLEDJsonApiHandler.swift @@ -1,26 +1,21 @@ import Foundation +import Combine import CoreData -class WLEDJsonApiHandler : WLEDRequestHandler { - let device: Device - var urlSession: URLSession? - - init(device: Device) { - self.device = device - } +final class WLEDJsonApiHandler: WLEDRequestHandler { - func getUrlSession() -> URLSession { - if (urlSession != nil) { - return urlSession! - } - + let device: Device + let urlSession: URLSession = { let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = 8 sessionConfig.timeoutIntervalForResource = 18 sessionConfig.waitsForConnectivity = true sessionConfig.httpMaximumConnectionsPerHost = 1 - urlSession = URLSession(configuration: sessionConfig) - return urlSession! + return URLSession(configuration: sessionConfig) + }() + + init(device: Device) { + self.device = device } func processRequest(_ request: WLEDRequest) async { @@ -45,23 +40,23 @@ class WLEDJsonApiHandler : WLEDRequestHandler { print("Reading api at: \(url)") do { - let (data, response) = try await getUrlSession().data(from: url) + let (data, response) = try await urlSession.data(from: url) guard let httpResponse = response as? HTTPURLResponse else { print("Invalid httpResponse in update") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } guard (200...299).contains(httpResponse.statusCode) else { print("Error with the response in update, unexpected status code: \(httpResponse)") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } - self.onResultFetchDataSuccess(context: request.context, data: data) + await self.onResultFetchDataSuccess(data: data) } catch { print("Error with fetching device: \(error)") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } } @@ -70,7 +65,7 @@ class WLEDJsonApiHandler : WLEDRequestHandler { let url = getJsonApiUrl(path: "json/state") guard let url else { print("Can't post to device, url nil") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } print("Posting api at: \(url)") @@ -83,28 +78,28 @@ class WLEDJsonApiHandler : WLEDRequestHandler { urlRequest.httpBody = jsonData do { - let (data, response) = try await getUrlSession().data(for: urlRequest) + let (data, response) = try await urlSession.data(for: urlRequest) guard let httpResponse = response as? HTTPURLResponse else { print("Invalid httpResponse in post") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } guard (200...299).contains(httpResponse.statusCode) else { print("Error with the response in post, unexpected status code: \(httpResponse)") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } - self.onSuccessPostJson(context: request.context, data: data) + self.onSuccessPostJson(data: data) } catch { print("Error with fetching device after post: \(error)") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } } catch { print(error) - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() } } @@ -130,7 +125,7 @@ class WLEDJsonApiHandler : WLEDRequestHandler { try body.append(Data(contentsOf: request.binaryFile)) } catch { print("Error with reading binary file: \(error)") - self.updateDeviceOnError(context: request.context) + self.updateDeviceOnError() return } body.append("\r\n".data(using: .utf8)!) @@ -145,26 +140,30 @@ class WLEDJsonApiHandler : WLEDRequestHandler { guard let httpResponse = response as? HTTPURLResponse else { print("Invalid httpResponse in post for update install") - request.onFailure() + await request.onFailure() return } guard (200...299).contains(httpResponse.statusCode) else { print("Error with the response in update install, unexpected status code: \(httpResponse)") - request.onFailure() + await request.onFailure() return } - request.onCompletion() + await request.onCompletion() } catch { print("Error with installing device update: \(error)") - request.onFailure() + await request.onFailure() return } } - private func updateDeviceOnError(context: NSManagedObjectContext) { + private func updateDeviceOnError() { + print("Device \(device.address ?? "unknown") could not be updated. Marking as offline.") + guard let context = device.managedObjectContext else { + return + } context.performAndWait { device.isOnline = false @@ -190,8 +189,15 @@ class WLEDJsonApiHandler : WLEDRequestHandler { return URL(string: urlString) } - private func onResultFetchDataSuccess(context: NSManagedObjectContext, data: Data?) { - guard let data else { return } + @MainActor + private func onResultFetchDataSuccess(data: Data?) { + guard let context = device.managedObjectContext else { + return + } + guard let data else { + return + } + context.performAndWait { do { let deviceStateInfo = try JSONDecoder().decode(DeviceStateInfo.self, from: data) @@ -232,12 +238,16 @@ class WLEDJsonApiHandler : WLEDRequestHandler { } } catch { print(error) - updateDeviceOnError(context: context) + updateDeviceOnError() } } } - private func onSuccessPostJson(context: NSManagedObjectContext, data: Data?) { + private func onSuccessPostJson(data: Data?) { + guard let context = device.managedObjectContext else { + return + } + guard let data else { return } context.performAndWait { do { @@ -256,7 +266,7 @@ class WLEDJsonApiHandler : WLEDRequestHandler { } } catch { print(error) - updateDeviceOnError(context: context) + updateDeviceOnError() } } } diff --git a/wled-native/Service/DeviceApi/WLEDRequestHandler.swift b/wled-native/Service/DeviceApi/WLEDRequestHandler.swift index a96f67b..e661048 100644 --- a/wled-native/Service/DeviceApi/WLEDRequestHandler.swift +++ b/wled-native/Service/DeviceApi/WLEDRequestHandler.swift @@ -1,7 +1,6 @@ import Foundation -protocol WLEDRequestHandler { - var device: Device { get } +protocol WLEDRequestHandler: Sendable { func processRequest(_ request: WLEDRequest) async } diff --git a/wled-native/Service/DeviceApi/WLEDRequestManager.swift b/wled-native/Service/DeviceApi/WLEDRequestManager.swift index 28735bf..e6071f6 100644 --- a/wled-native/Service/DeviceApi/WLEDRequestManager.swift +++ b/wled-native/Service/DeviceApi/WLEDRequestManager.swift @@ -2,14 +2,17 @@ import Foundation import Collections +// A request manager manages serial access to a device to perform all updates sequentially actor WLEDRequestManager { - let device: Device + + var device: Device + let requestHandler: WLEDRequestHandler var requestQueue: Deque = [] - private var locked = false init(device: Device) { self.device = device + // TODO: Add websocket support requestHandler = WLEDJsonApiHandler(device: device) } @@ -23,7 +26,7 @@ actor WLEDRequestManager { func processAllRequests() { Task { var canProcessMore = true - while (!requestQueue.isEmpty && canProcessMore) { + while (!self.requestQueue.isEmpty && canProcessMore) { print("Processing request") canProcessMore = await processRequests() print("Request done, processing next? \(canProcessMore)") @@ -32,17 +35,10 @@ actor WLEDRequestManager { } private func processRequests() async -> Bool { - guard locked == false else { - return false - } - locked = true - defer { - locked = false - } guard let request = requestQueue.popFirst() else { return false } - await requestHandler.processRequest(request) + await self.requestHandler.processRequest(request) return true } } diff --git a/wled-native/Service/DeviceApi/WLEDRequestManagerProvider.swift b/wled-native/Service/DeviceApi/WLEDRequestManagerProvider.swift new file mode 100644 index 0000000..eb67b8b --- /dev/null +++ b/wled-native/Service/DeviceApi/WLEDRequestManagerProvider.swift @@ -0,0 +1,19 @@ +// +// WLEDRequestManagerProvider.swift +// wled-native +// +// Created by Robert Brune on 07.10.24. +// + +import Foundation + +actor WLEDRequestManagerProvider { + + public static let shared = WLEDRequestManagerProvider() + + private var requestManagers: [Device: WLEDRequestManager] = [:] + + func getRequestManager(device: Device) -> WLEDRequestManager { + requestManagers[device] ?? .init(device: device) + } +} diff --git a/wled-native/Service/DeviceApi/WLEDWebsocketHandler.swift b/wled-native/Service/DeviceApi/WLEDWebsocketHandler.swift index 5ee8cee..f0ad6f6 100644 --- a/wled-native/Service/DeviceApi/WLEDWebsocketHandler.swift +++ b/wled-native/Service/DeviceApi/WLEDWebsocketHandler.swift @@ -1,17 +1,20 @@ import Foundation -class WLEDWebsocketHandler : WLEDRequestHandler { - var device: Device - - init(device: Device) { - self.device = device - } - - func processRequest(_ request: WLEDRequest) async { - // TODO: Implement this - fatalError("Not Implemented") - } - - -} +/* Uncommented due to Sendable issues + + class WLEDWebsocketHandler : WLEDRequestHandler { + var device: Device + + init(device: Device) { + self.device = device + } + + func processRequest(_ request: WLEDRequest) async { + // TODO: Implement this + fatalError("Not Implemented") + } + + + } + */ diff --git a/wled-native/Service/DeviceState.swift b/wled-native/Service/DeviceState.swift deleted file mode 100644 index 39b2232..0000000 --- a/wled-native/Service/DeviceState.swift +++ /dev/null @@ -1,14 +0,0 @@ - -import Foundation - -// Stores transient information about a device -class DeviceState { - private var requestManager: WLEDRequestManager? - - func getRequestManager(device: Device) -> WLEDRequestManager { - if (requestManager == nil) { - requestManager = WLEDRequestManager(device: device) - } - return requestManager! - } -} diff --git a/wled-native/Service/DeviceStateFactory.swift b/wled-native/Service/DeviceStateFactory.swift deleted file mode 100644 index 658274a..0000000 --- a/wled-native/Service/DeviceStateFactory.swift +++ /dev/null @@ -1,22 +0,0 @@ - -import Foundation - -class DeviceStateFactory { - static var shared = DeviceStateFactory() - - private let dispatchQueue = DispatchQueue(label: "deviceStateFactory", attributes: .concurrent) - private var allDeviceStates: Dictionary = [:] - - func getStateForDevice(_ device: Device) -> DeviceState { - let address = device.address ?? "unknown" - var deviceState: DeviceState? - dispatchQueue.sync(flags: .barrier) { - if !self.allDeviceStates.keys.contains(address) { - self.allDeviceStates[address] = DeviceState() - } - - deviceState = self.allDeviceStates[address]! - } - return deviceState! - } -} diff --git a/wled-native/Service/DeviceUpdateService.swift b/wled-native/Service/DeviceUpdateService.swift index 42953b3..2c616c5 100644 --- a/wled-native/Service/DeviceUpdateService.swift +++ b/wled-native/Service/DeviceUpdateService.swift @@ -1,7 +1,7 @@ import Foundation import CoreData -class DeviceUpdateService { +class DeviceUpdateService: @unchecked Sendable { let supportedPlatforms = [ "esp01", @@ -62,7 +62,8 @@ class DeviceUpdateService { return FileManager.default.fileExists(atPath: binaryPath.path) } - func downloadBinary(onCompletion: @escaping (DeviceUpdateService) -> ()) { + + func downloadBinary(onCompletion: @MainActor @escaping (DeviceUpdateService) -> ()) { guard let assetUrl = URL(string: asset?.downloadUrl ?? "") else { // TODO: Handle errors return @@ -84,7 +85,9 @@ class DeviceUpdateService { do { try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl) - onCompletion(self) + Task { + await onCompletion(self) + } } catch (let writeError) { print("error writing file \(localUrl) : \(writeError)") } @@ -97,19 +100,17 @@ class DeviceUpdateService { task.resume() } - func installUpdate(onCompletion: @escaping () -> (), onFailure: @escaping () -> ()) { + func installUpdate(onCompletion: @MainActor @escaping () -> (), onFailure: @MainActor @escaping () -> ()) async { guard let binaryPath = getPathForAsset() else { // TODO: Handle errors return } - Task { - await device.requestManager.addRequest(WLEDSoftwareUpdateRequest( - context: context, - binaryFile: binaryPath, - onCompletion: onCompletion, - onFailure: onFailure - )) - } + await device.getRequestManager().addRequest(WLEDSoftwareUpdateRequest( + binaryFile: binaryPath, + onCompletion: onCompletion, + onFailure: onFailure + )) + } func getPathForAsset() -> URL? { diff --git a/wled-native/Service/DiscoveryService.swift b/wled-native/Service/DiscoveryService.swift index 904d783..b1fc130 100644 --- a/wled-native/Service/DiscoveryService.swift +++ b/wled-native/Service/DiscoveryService.swift @@ -5,11 +5,9 @@ import CoreData import Network import SwiftUI -class DiscoveryService: NSObject, Identifiable { +final class DiscoveryService: NSObject, Identifiable, Sendable { - var browser: NWBrowser! - - func scan() { + let browser = { let bonjourTCP = NWBrowser.Descriptor.bonjour(type: "_wled._tcp" , domain: "local.") let bonjourParms = NWParameters.init() @@ -17,12 +15,12 @@ class DiscoveryService: NSObject, Identifiable { bonjourParms.acceptLocalOnly = true bonjourParms.allowFastOpen = true - browser = NWBrowser(for: bonjourTCP, using: bonjourParms) - browser.stateUpdateHandler = {newState in + let browser = NWBrowser(for: bonjourTCP, using: bonjourParms) + browser.stateUpdateHandler = { @Sendable newState in switch newState { case .failed(let error): print("NW Browser: now in Error state: \(error)") - self.browser.cancel() + browser.cancel() case .ready: print("NW Browser: new bonjour discovery - ready") case .setup: @@ -31,7 +29,7 @@ class DiscoveryService: NSObject, Identifiable { break } } - browser.browseResultsChangedHandler = { ( results, changes ) in + browser.browseResultsChangedHandler = { @Sendable ( results, changes ) in print("NW Browser: Scan results found:") for result in results { print(result.endpoint.debugDescription) @@ -49,7 +47,7 @@ class DiscoveryService: NSObject, Identifiable { case .hostPort(let host, let port) = innerEndpoint { let remoteHost = "\(host)".split(separator: "%")[0] print("Connected to", "\(remoteHost):\(port)") - self.addDevice(name: name, host: "\(remoteHost)") + addDevice(name: name, host: "\(remoteHost)") } default: break @@ -60,10 +58,14 @@ class DiscoveryService: NSObject, Identifiable { } } } + return browser + }() + + func scan() { self.browser.start(queue: DispatchQueue.main) } - func addDevice(name: String, host: String) { + static func addDevice(name: String, host: String) { let viewContext = PersistenceController.shared.container.viewContext viewContext.performAndWait { if (doesDeviceAlreadyExists(host: host, viewContext: viewContext)) { @@ -76,12 +78,12 @@ class DiscoveryService: NSObject, Identifiable { newDevice.address = host newDevice.isHidden = false Task { - await newDevice.requestManager.addRequest(WLEDRefreshRequest(context: viewContext)) + await newDevice.refresh() } } } - func doesDeviceAlreadyExists(host: String, viewContext: NSManagedObjectContext) -> Bool { + static func doesDeviceAlreadyExists(host: String, viewContext: NSManagedObjectContext) -> Bool { let fetchRequest: NSFetchRequest fetchRequest = Device.fetchRequest() diff --git a/wled-native/Service/GithubApi.swift b/wled-native/Service/GithubApi.swift index 4792248..619ebb3 100644 --- a/wled-native/Service/GithubApi.swift +++ b/wled-native/Service/GithubApi.swift @@ -2,9 +2,9 @@ import Foundation import CoreData class GithubApi { - static var urlSession: URLSession? + @MainActor static var urlSession: URLSession? - static func getUrlSession() -> URLSession { + @MainActor static func getUrlSession() -> URLSession { if (urlSession != nil) { return urlSession! } diff --git a/wled-native/Service/ReleaseService.swift b/wled-native/Service/ReleaseService.swift index 54465e6..1dd255f 100644 --- a/wled-native/Service/ReleaseService.swift +++ b/wled-native/Service/ReleaseService.swift @@ -1,6 +1,7 @@ import Foundation import CoreData +@MainActor class ReleaseService { let context: NSManagedObjectContext @@ -38,7 +39,6 @@ class ReleaseService { return versionCompare == .orderedDescending ? latestTagName : "" } - func getLatestVersion(branch: Branch) -> Version? { let fetchRequest = Version.fetchRequest() fetchRequest.fetchLimit = 1 @@ -58,7 +58,6 @@ class ReleaseService { fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } - func refreshVersions() async { let allReleases = await WLEDRepoApi().getAllReleases() @@ -92,8 +91,6 @@ class ReleaseService { } } - - private func createVersion(release: Release) -> Version { let version = Version(context: context) version.tagName = release.tagName diff --git a/wled-native/View/DeviceAddView.swift b/wled-native/View/DeviceAddView.swift index 3d05542..60777cb 100644 --- a/wled-native/View/DeviceAddView.swift +++ b/wled-native/View/DeviceAddView.swift @@ -22,8 +22,10 @@ struct DeviceAddView: View { Text("IP Address or URL") TextField("IP Address or URL", text: $address) .focused($focusedField, equals: .address) + #if os(iOS) .keyboardType(.URL) .submitLabel(.next) + #endif .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay(RoundedRectangle(cornerRadius: 4).stroke(address.isEmpty || isAddressValid() ? Color.clear : Color.red)) .onChange(of: address) { _ in @@ -55,6 +57,7 @@ struct DeviceAddView: View { } .padding() .toolbar { + #if os(iOS) ToolbarItem(placement: .topBarLeading) { Button(role: .cancel) { dismiss() @@ -62,6 +65,16 @@ struct DeviceAddView: View { Text("Cancel") } } + #elseif os(macOS) + ToolbarItem(placement: .cancellationAction) { + Button(role: .cancel) { + dismiss() + } label: { + Text("Cancel") + } + } + #endif + ToolbarItem { Button(action: addItem) { Text("Save") @@ -70,7 +83,9 @@ struct DeviceAddView: View { } } .navigationTitle("New Device") + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif } } @@ -86,6 +101,7 @@ struct DeviceAddView: View { return false } + #if os(iOS) if let url = NSURL(string: address) { if (UIApplication.shared.canOpenURL(url as URL)) { return true @@ -94,6 +110,16 @@ struct DeviceAddView: View { if let url = NSURL(string: "http://\(address)") { return UIApplication.shared.canOpenURL(url as URL) } + #elseif os(macOS) + if let url = NSURL(string: address) { + if NSWorkspace.shared.urlForApplication(toOpen: url as URL) != nil { + return true + } + } + if let url = NSURL(string: "http://\(address)") { + return NSWorkspace.shared.urlForApplication(toOpen: url as URL) != nil + } + #endif return false } diff --git a/wled-native/View/DeviceEditView.swift b/wled-native/View/DeviceEditView.swift index fd6b505..d87c653 100644 --- a/wled-native/View/DeviceEditView.swift +++ b/wled-native/View/DeviceEditView.swift @@ -79,9 +79,7 @@ struct DeviceEditView: View { device.branchValue = newBranch device.latestUpdateVersionTagAvailable = "" saveDevice() - Task { - await checkForUpdate() - } + checkForUpdate() } } .padding(.bottom) @@ -92,9 +90,7 @@ struct DeviceEditView: View { Text("Version \(device.version ?? unknownVersion)") HStack { Button(action: { - Task { - await checkForUpdate() - } + checkForUpdate() }) { Text(isCheckingForUpdates ? "Checking for Updates" : "Check for Update") } @@ -127,14 +123,18 @@ struct DeviceEditView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding() - .background(Color(UIColor.secondarySystemBackground)) + .background(Color.black.opacity(0.5)) .cornerRadius(8) Spacer() } .padding() .navigationTitle("Edit Device") + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #elseif os(macOS) + //TODO: for mac os + #endif .onAppear() { address = device.address ?? "" customName = device.isCustomName ? (self.device.name ?? "") : "" @@ -155,18 +155,21 @@ struct DeviceEditView: View { } } - private func checkForUpdate() async { + private func checkForUpdate() { withAnimation { isCheckingForUpdates = true } print("Refreshing available Releases") - await ReleaseService(context: viewContext).refreshVersions() + Task { + await ReleaseService(context: viewContext).refreshVersions() + } + UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: WLEDNativeApp.dateLastUpdateKey) device.skipUpdateTag = "" withAnimation { - Task { - await device.requestManager.addRequest(WLEDRefreshRequest(context: viewContext)) + Task { @MainActor in + await device.getRequestManager().addRequest(WLEDRefreshRequest()) } isCheckingForUpdates = false } diff --git a/wled-native/View/DeviceListItemView.swift b/wled-native/View/DeviceListItemView.swift index 8a31fbc..0a2176a 100644 --- a/wled-native/View/DeviceListItemView.swift +++ b/wled-native/View/DeviceListItemView.swift @@ -27,7 +27,7 @@ struct DeviceListItemView: View { .fixedSize() .font(.subheadline.leading(.tight)) .lineSpacing(0) - Image(uiImage: getSignalImage(isOnline: device.isOnline, signalStrength: Int(device.networkRssi))) + getSignalImage(isOnline: device.isOnline, signalStrength: Int(device.networkRssi)) .resizable() .renderingMode(.template) .foregroundColor(.primary) @@ -74,7 +74,7 @@ struct DeviceListItemView: View { if (!editing) { let postParam = WLEDStateChange(brightness: Int64(brightness)) Task { - await device.requestManager.addRequest(WLEDChangeStateRequest(state: postParam, context: viewContext)) + await device.getRequestManager().addRequest(WLEDChangeStateRequest(state: postParam)) } } } @@ -87,7 +87,7 @@ struct DeviceListItemView: View { let postParam = WLEDStateChange(isOn: $0) print("device \(device.address ?? "?") toggled \(postParam)") Task { - await device.requestManager.addRequest(WLEDChangeStateRequest(state: postParam, context: viewContext)) + await device.getRequestManager().addRequest(WLEDChangeStateRequest(state: postParam)) } })) .labelsHidden() @@ -102,21 +102,17 @@ struct DeviceListItemView: View { } } - func getSignalImage(isOnline: Bool, signalStrength: Int) -> UIImage { + func getSignalImage(isOnline: Bool, signalStrength: Int) -> Image { let icon = !isOnline || signalStrength == 0 ? "wifi.slash" : "wifi" - var image: UIImage; if #available(iOS 16.0, *) { - image = UIImage( - systemName: icon, + return Image(systemName: icon, variableValue: getSignalValue(signalStrength: Int(device.networkRssi)) - )! + ) } else { - image = UIImage( + return Image( systemName: icon - )! + ) } - image.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .systemBlue)) - return image } func getDeviceDisplayName() -> String { @@ -155,10 +151,14 @@ struct DeviceListItemView: View { let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 0xFF let blue = CGFloat(rgbValue & 0x0000FF) / 0xFF let alpha = CGFloat(alpha ?? 1.0) - + // TODO: Fix Colors also for XOS + #if os(iOS) return fixColor(color: UIColor(red: red, green: green, blue: blue, alpha: alpha)) + #else + return Color(red: red, green: green, blue: blue, opacity: alpha) + #endif } - + #if os(iOS) // Fixes the color if it is too dark or too bright depending of the dark/light theme func fixColor(color: UIColor) -> Color { var h = CGFloat(0), s = CGFloat(0), b = CGFloat(0), a = CGFloat(0) @@ -166,6 +166,7 @@ struct DeviceListItemView: View { b = colorScheme == .dark ? fmax(b, 0.2) : fmin(b, 0.75) return Color(UIColor(hue: h, saturation: s, brightness: b, alpha: a)) } + #endif func getUpdateIconName() -> String { if #available(iOS 17.0, *) { diff --git a/wled-native/View/DeviceListView.swift b/wled-native/View/DeviceListView.swift index f66e5a1..9ba2457 100644 --- a/wled-native/View/DeviceListView.swift +++ b/wled-native/View/DeviceListView.swift @@ -11,114 +11,124 @@ struct DeviceListView: View { @State private var firstLoad = true @StateObject private var filter = DeviceListFilterAndSort(showHiddenDevices: false) + private let discoveryService = DiscoveryService() var body: some View { NavigationView { FetchedObjects(predicate: filter.getOnlineFilter(), sortDescriptors: filter.getSortDescriptors()) { (devices: [Device]) in FetchedObjects(predicate: filter.getOfflineFilter(), sortDescriptors: filter.getSortDescriptors()) { (devicesOffline: [Device]) in - List { - ForEach(devices, id: \.tag) { device in - NavigationLink { - DeviceView() - .environmentObject(device) - } label: { - DeviceListItemView() - .environmentObject(device) - } - .environmentObject(device) - .swipeActions(allowsFullSwipe: true) { - Button(role: .destructive) { - deleteItems(device: device) - } label: { - Label("Delete", systemImage: "trash.fill") - } - } + list(devices: devices, devicesOffline: devicesOffline) + } + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + Image(.wledLogoAkemi) + .resizable() + .scaledToFit() + .padding(2) } - Section(header: Text("Offline Devices")) { - ForEach(devicesOffline, id: \.tag) { device in - NavigationLink { - DeviceView() - .environmentObject(device) + .frame(maxWidth: 200) + } + ToolbarItem { + Menu { + Section { + Button { + addDeviceButtonActive.toggle() } label: { - DeviceListItemView() - .environmentObject(device) + Label("Add New Device", systemImage: "plus") } - .swipeActions(allowsFullSwipe: true) { - Button(role: .destructive) { - deleteItems(device: device) - } label: { - Label("Delete", systemImage: "trash.fill") + Button { + withAnimation { + filter.showHiddenDevices = !filter.showHiddenDevices + } + } label: { + if (filter.showHiddenDevices) { + Label("Hide Hidden Devices", systemImage: "eye.slash") + } else { + Label("Show Hidden Devices", systemImage: "eye") } } } + Section { + Link(destination: URL(string: "https://kno.wled.ge/")!) { + Label("WLED Documentation", systemImage: "questionmark.circle") + } + } + } label: { + Label("Menu", systemImage: "ellipsis.circle") } - .opacity(devicesOffline.count > 0 ? 1 : 0) - } - .listStyle(PlainListStyle()) - .refreshable { - await refreshDevices(devices: devices + devicesOffline) - discoveryService.scan() } - .onAppear(perform: { - Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { _ in - refreshDevicesSync(devices: devices + devicesOffline) - } - Task { - print("Initial refresh and scan") - await refreshDevices(devices: devices + devicesOffline) - discoveryService.scan() - } - }) + } + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .sheet(isPresented: $addDeviceButtonActive, content: DeviceAddView.init) + VStack { + Text("Select A Device") + .font(.title2) } } - .toolbar { - ToolbarItem(placement: .principal) { - VStack { - Image(.wledLogoAkemi) - .resizable() - .scaledToFit() - .padding(2) - } - .frame(maxWidth: 200) + } + } + + private func list(devices: [Device], devicesOffline: [Device]) -> some View { + List { + ForEach(devices, id: \.tag) { device in + NavigationLink { + DeviceView() + .environmentObject(device) + } label: { + DeviceListItemView() + .environmentObject(device) } - ToolbarItem { - Menu { - Section { - Button { - addDeviceButtonActive.toggle() - } label: { - Label("Add New Device", systemImage: "plus") - } - Button { - withAnimation { - filter.showHiddenDevices = !filter.showHiddenDevices - } - } label: { - if (filter.showHiddenDevices) { - Label("Hide Hidden Devices", systemImage: "eye.slash") - } else { - Label("Show Hidden Devices", systemImage: "eye") - } - } - } - Section { - Link(destination: URL(string: "https://kno.wled.ge/")!) { - Label("WLED Documentation", systemImage: "questionmark.circle") - } - } + .environmentObject(device) + .swipeActions(allowsFullSwipe: true) { + Button(role: .destructive) { + deleteItems(device: device) } label: { - Label("Menu", systemImage: "ellipsis.circle") + Label("Delete", systemImage: "trash.fill") } } } - .navigationBarTitleDisplayMode(.inline) - .sheet(isPresented: $addDeviceButtonActive, content: DeviceAddView.init) - VStack { - Text("Select A Device") - .font(.title2) + Section(header: Text("Offline Devices")) { + ForEach(devicesOffline, id: \.tag) { device in + NavigationLink { + DeviceView() + .environmentObject(device) + } label: { + DeviceListItemView() + .environmentObject(device) + } + .swipeActions(allowsFullSwipe: true) { + Button(role: .destructive) { + deleteItems(device: device) + } label: { + Label("Delete", systemImage: "trash.fill") + } + } + } } + .opacity(devicesOffline.count > 0 ? 1 : 0) + } + .listStyle(PlainListStyle()) + .refreshable { + await refreshDevices(devices: devices + devicesOffline) + discoveryService.scan() } + .onAppear(perform: { + Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { _ in + Task { + print("auto-refreshing") + await refreshDevices(devices: devices + devicesOffline) + } + } + Task { + print("Initial refresh and scan") + await refreshDevices(devices: devices + devicesOffline) + discoveryService.scan() + } + }) } private func deleteItems(device: Device) { @@ -128,6 +138,7 @@ struct DeviceListView: View { do { try viewContext.save() } catch { + // TODO: check for changes and fix error message // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nsError = error as NSError @@ -136,7 +147,6 @@ struct DeviceListView: View { } } - @Sendable private func refreshDevices(devices: [Device]) async { await withTaskGroup(of: Void.self) { [self] group in for device in devices { @@ -145,22 +155,14 @@ struct DeviceListView: View { continue } group.addTask { - await viewContext.performAndWait { - device.isRefreshing = true - } - await device.requestManager.addRequest(WLEDRefreshRequest(context: viewContext)) + device.isRefreshing = true + await device.refresh() + } } self.firstLoad = false } } - - private func refreshDevicesSync(devices: [Device]) { - Task { - print("auto-refreshing") - await refreshDevices(devices: devices) - } - } } struct DeviceListView_Previews: PreviewProvider { diff --git a/wled-native/View/DeviceUpdateDetails.swift b/wled-native/View/DeviceUpdateDetails.swift index 6aa35ef..894ede5 100644 --- a/wled-native/View/DeviceUpdateDetails.swift +++ b/wled-native/View/DeviceUpdateDetails.swift @@ -59,12 +59,16 @@ struct DeviceUpdateDetails: View { .background(.bar) } .navigationTitle("Version \(version?.tagName ?? "")") + #if os(iOS) .fullScreenCover(isPresented: $showInstallingDialog) { if let version = version { DeviceUpdateInstalling(version: version) .background(BackgroundBlurView()) } } + #else + //TODO: Missing feature MAC os + #endif .onReceive(NotificationCenter.default.publisher(for: .didCompleteUpdateInstall)) {_ in dismiss() } diff --git a/wled-native/View/DeviceUpdateInstalling.swift b/wled-native/View/DeviceUpdateInstalling.swift index b0d80e5..a1b25d1 100644 --- a/wled-native/View/DeviceUpdateInstalling.swift +++ b/wled-native/View/DeviceUpdateInstalling.swift @@ -75,7 +75,11 @@ struct DeviceUpdateInstalling: View { } .fixedSize(horizontal: false, vertical: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) .padding() + #if os(iOS) .background(Color(UIColor.secondarySystemBackground)) + #else + .background(Color(NSColor.secondarySystemFill)) + #endif .clipShape(RoundedRectangle(cornerRadius: 20)) .shadow(radius: 20) .offset(x: 0, y: offset) @@ -128,7 +132,9 @@ struct DeviceUpdateInstalling: View { private func onDownloadCompleted(_ updateService: DeviceUpdateService) { print("Download is done.") statusString = String(localized: "Installing Update") - updateService.installUpdate(onCompletion: onInstallCompleted, onFailure: onInstallFailed) + Task { + await updateService.installUpdate(onCompletion: onInstallCompleted, onFailure: onInstallFailed) + } } private func onInstallCompleted() { @@ -140,7 +146,7 @@ struct DeviceUpdateInstalling: View { Task { // Wait 3 seconds before sending a refresh request try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC))) - await device.requestManager.addRequest(WLEDRefreshRequest(context: viewContext)) + await device.getRequestManager().addRequest(WLEDRefreshRequest()) } } diff --git a/wled-native/View/DeviceView.swift b/wled-native/View/DeviceView.swift index 4d75654..dc6c87d 100644 --- a/wled-native/View/DeviceView.swift +++ b/wled-native/View/DeviceView.swift @@ -10,6 +10,7 @@ struct DeviceView: View { var body: some View { ZStack { + #if os(iOS) WebView(url: getDeviceAddress(), reload: $shouldWebViewRefresh) { filePathDestination in withAnimation { showDownloadFinished = true @@ -19,6 +20,10 @@ struct DeviceView: View { } } } + #else + //Add Web View for xos + Text("Not Supported") + #endif if (showDownloadFinished) { VStack { Spacer() @@ -32,7 +37,9 @@ struct DeviceView: View { } } .navigationTitle(getDeviceName()) + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .toolbar { ToolbarItemGroup(placement: .primaryAction) { Button { diff --git a/wled-native/View/Tool/BackgroundBlurView.swift b/wled-native/View/Tool/BackgroundBlurView.swift index aed3b33..edb2cc3 100644 --- a/wled-native/View/Tool/BackgroundBlurView.swift +++ b/wled-native/View/Tool/BackgroundBlurView.swift @@ -1,6 +1,7 @@ import Foundation import SwiftUI +#if os(iOS) struct BackgroundBlurView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) @@ -12,3 +13,18 @@ struct BackgroundBlurView: UIViewRepresentable { func updateUIView(_ uiView: UIView, context: Context) {} } +#elseif os(macOS) +struct BackgroundBlurView: NSViewRepresentable { + func makeNSView(context: Context) -> NSVisualEffectView { + let visualEffectView = NSVisualEffectView() + visualEffectView.blendingMode = .behindWindow + visualEffectView.material = .underWindowBackground + return visualEffectView + } + + func updateNSView(_ nsView: NSVisualEffectView, context: Context) { + // Update the view if needed + } +} + +#endif diff --git a/wled-native/View/WebView.swift b/wled-native/View/WebView.swift index 39f9b7c..97a0518 100644 --- a/wled-native/View/WebView.swift +++ b/wled-native/View/WebView.swift @@ -2,6 +2,7 @@ import SwiftUI import WebKit +#if os(iOS) struct WebView: UIViewRepresentable { var webView: WKWebView = WKWebView() @@ -44,6 +45,7 @@ struct WebView: UIViewRepresentable { } class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate, WKDownloadDelegate { + var parent: WebView private var filePathDestination: URL? @@ -82,11 +84,6 @@ struct WebView: UIViewRepresentable { } } - func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) { - filePathDestination = getDownloadPath(suggestedFilename as NSString) - completionHandler(filePathDestination) - } - func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) { download.delegate = self } @@ -95,6 +92,15 @@ struct WebView: UIViewRepresentable { download.delegate = self } + func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String) async -> URL? { + getDownloadPath(suggestedFilename as NSString) + } + + func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) { + filePathDestination = getDownloadPath(suggestedFilename as NSString) + completionHandler(filePathDestination) + } + func download(didFailWithError: Error, resumeData: Data?) { print("Failed to download: \(didFailWithError)") } @@ -244,3 +250,4 @@ struct WebView: UIViewRepresentable { Coordinator(self) } } +#endif diff --git a/wled-native/WLEDNativeApp.swift b/wled-native/WLEDNativeApp.swift index 0e56165..6e2bb39 100644 --- a/wled-native/WLEDNativeApp.swift +++ b/wled-native/WLEDNativeApp.swift @@ -8,6 +8,7 @@ struct WLEDNativeApp: App { let persistenceController = PersistenceController.shared var body: some Scene { + #if os(iOS) WindowGroup { DeviceListView() .environment(\.managedObjectContext, persistenceController.container.viewContext) @@ -15,6 +16,17 @@ struct WLEDNativeApp: App { refreshVersionsSync() } } + #elseif os(macOS) + // The Menu Bar for macOS + MenuBarExtra("WLED", systemImage: "lamp.table.fill") { + DeviceListView() + .environment(\.managedObjectContext, persistenceController.container.viewContext) + .onAppear() { + refreshVersionsSync() + } + } + .menuBarExtraStyle(.window) + #endif } diff --git a/wled-native/wled_native_osx.entitlements b/wled-native/wled_native_osx.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/wled-native/wled_native_osx.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + +