Compare commits
No commits in common. "master" and "Custom-project-fix" have entirely different histories.
master
...
Custom-pro
62 changed files with 3197 additions and 5259 deletions
|
|
@ -27,8 +27,6 @@ public class FileTransferConnectionManager: ObservableObject {
|
||||||
@Published public var isConnectedOrReconnecting = false // Is any peripheral connected or trying to connect
|
@Published public var isConnectedOrReconnecting = false // Is any peripheral connected or trying to connect
|
||||||
@Published public var isAnyPeripheralConnecting = false
|
@Published public var isAnyPeripheralConnecting = false
|
||||||
|
|
||||||
@Published public var isDisconnectingFromCurrent = false
|
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
public var userDefaults = UserDefaults.standard // Can be replaced if data saved needs to be shared
|
public var userDefaults = UserDefaults.standard // Can be replaced if data saved needs to be shared
|
||||||
|
|
||||||
|
|
|
||||||
25
LICENSE.txt
25
LICENSE.txt
|
|
@ -1,25 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
|
|
||||||
Copyright (c) 2023 Adafruit Industries
|
|
||||||
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
7
PyLeap 2.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
PyLeap 2.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -11,14 +11,6 @@
|
||||||
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D505B99D2756894300386E9F /* ViewModifier.swift */; };
|
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D505B99D2756894300386E9F /* ViewModifier.swift */; };
|
||||||
D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.swift */; };
|
D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.swift */; };
|
||||||
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */; };
|
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */; };
|
||||||
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */; };
|
|
||||||
D5267411292E902700D4C79E /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5267410292E902700D4C79E /* Networking.swift */; };
|
|
||||||
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269BFF291960A300C0CE4B /* WifiSelection.swift */; };
|
|
||||||
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */; };
|
|
||||||
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */; };
|
|
||||||
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C07291AB75800C0CE4B /* WifiPairingView.swift */; };
|
|
||||||
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A926C29071DF400973B6B /* SelectionView.swift */; };
|
|
||||||
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */; };
|
|
||||||
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */; };
|
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */; };
|
||||||
D52BE7F3269DF62100630900 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = D52BE7F2269DF62100630900 /* Zip */; };
|
D52BE7F3269DF62100630900 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = D52BE7F2269DF62100630900 /* Zip */; };
|
||||||
D52BE82A26A0660200630900 /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54D38B82691E0D000FBFE47 /* KeyboardUtils.swift */; };
|
D52BE82A26A0660200630900 /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54D38B82691E0D000FBFE47 /* KeyboardUtils.swift */; };
|
||||||
|
|
@ -30,8 +22,6 @@
|
||||||
D52F7E7B2672F4C500911D43 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */; };
|
D52F7E7B2672F4C500911D43 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */; };
|
||||||
D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.swift */; };
|
D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.swift */; };
|
||||||
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */; };
|
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */; };
|
||||||
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */; };
|
|
||||||
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361099296FB2BB00228E15 /* DataStore.swift */; };
|
|
||||||
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A1D1281B9BB70038D483 /* Buttons.swift */; };
|
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A1D1281B9BB70038D483 /* Buttons.swift */; };
|
||||||
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.swift */; };
|
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.swift */; };
|
||||||
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24E282046840038D483 /* OnAnimationComplete.swift */; };
|
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24E282046840038D483 /* OnAnimationComplete.swift */; };
|
||||||
|
|
@ -53,8 +43,6 @@
|
||||||
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */; };
|
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */; };
|
||||||
D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */; };
|
D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */; };
|
||||||
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2DE28C8D40C0009F768 /* SettingsView.swift */; };
|
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2DE28C8D40C0009F768 /* SettingsView.swift */; };
|
||||||
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */; };
|
|
||||||
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */; };
|
|
||||||
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */; };
|
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */; };
|
||||||
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */; };
|
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */; };
|
||||||
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */; };
|
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */; };
|
||||||
|
|
@ -118,14 +106,6 @@
|
||||||
D505B99D2756894300386E9F /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; };
|
D505B99D2756894300386E9F /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; };
|
||||||
D517F68026C5771D002996E8 /* FillerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillerView.swift; sourceTree = "<group>"; };
|
D517F68026C5771D002996E8 /* FillerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillerView.swift; sourceTree = "<group>"; };
|
||||||
D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleContentTransfer.swift; sourceTree = "<group>"; };
|
D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleContentTransfer.swift; sourceTree = "<group>"; };
|
||||||
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiCellViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D5267410292E902700D4C79E /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
|
|
||||||
D5269BFF291960A300C0CE4B /* WifiSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiSelection.swift; path = "PyLeap/Views/Unpaired View/WifiSelection.swift"; sourceTree = SOURCE_ROOT; };
|
|
||||||
D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellView.swift; path = ../../../../../../Desktop/WifiServiceCellView.swift; sourceTree = "<group>"; };
|
|
||||||
D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellSubView.swift; path = ../../../../../../Desktop/WifiServiceCellSubView.swift; sourceTree = "<group>"; };
|
|
||||||
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiPairingView.swift; sourceTree = "<group>"; };
|
|
||||||
D52A926C29071DF400973B6B /* SelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionView.swift; sourceTree = "<group>"; };
|
|
||||||
D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiServiceSelectionView.swift; sourceTree = "<group>"; };
|
|
||||||
D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadViewModel.swift; sourceTree = "<group>"; };
|
D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadViewModel.swift; sourceTree = "<group>"; };
|
||||||
D52BE85326A0E39100630900 /* BTConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTConnectionViewModel.swift; sourceTree = "<group>"; };
|
D52BE85326A0E39100630900 /* BTConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTConnectionViewModel.swift; sourceTree = "<group>"; };
|
||||||
D52F7E702672F4C400911D43 /* PyLeap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PyLeap.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D52F7E702672F4C400911D43 /* PyLeap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PyLeap.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
|
@ -135,8 +115,6 @@
|
||||||
D52F7E7C2672F4C500911D43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D52F7E7C2672F4C500911D43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D534F3FB280B59090053699C /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = "<group>"; };
|
D534F3FB280B59090053699C /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = "<group>"; };
|
||||||
D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollRefreshableView.swift; sourceTree = "<group>"; };
|
D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollRefreshableView.swift; sourceTree = "<group>"; };
|
||||||
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONDecoderHelper.swift; sourceTree = "<group>"; };
|
|
||||||
D5361099296FB2BB00228E15 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
|
|
||||||
D544A1D1281B9BB70038D483 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
|
D544A1D1281B9BB70038D483 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
|
||||||
D544A24A281F92840038D483 /* ImageCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCaching.swift; sourceTree = "<group>"; };
|
D544A24A281F92840038D483 /* ImageCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCaching.swift; sourceTree = "<group>"; };
|
||||||
D544A24E282046840038D483 /* OnAnimationComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAnimationComplete.swift; sourceTree = "<group>"; };
|
D544A24E282046840038D483 /* OnAnimationComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAnimationComplete.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -162,8 +140,6 @@
|
||||||
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCell.swift; sourceTree = "<group>"; };
|
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCell.swift; sourceTree = "<group>"; };
|
||||||
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCellModel.swift; sourceTree = "<group>"; };
|
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCellModel.swift; sourceTree = "<group>"; };
|
||||||
D567E2DE28C8D40C0009F768 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
D567E2DE28C8D40C0009F768 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
|
D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
|
||||||
D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DeletingPrefix.swift"; sourceTree = "<group>"; };
|
D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DeletingPrefix.swift"; sourceTree = "<group>"; };
|
||||||
D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferPathUtils.swift; sourceTree = "<group>"; };
|
D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferPathUtils.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -187,7 +163,6 @@
|
||||||
D59DFDC1268CFA36001737F6 /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; };
|
D59DFDC1268CFA36001737F6 /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; };
|
||||||
D59DFDC3268CFAB4001737F6 /* OnboardingDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDataModel.swift; sourceTree = "<group>"; };
|
D59DFDC3268CFAB4001737F6 /* OnboardingDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDataModel.swift; sourceTree = "<group>"; };
|
||||||
D59E31A9281B8DD300D24211 /* DownloadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadState.swift; sourceTree = "<group>"; };
|
D59E31A9281B8DD300D24211 /* DownloadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadState.swift; sourceTree = "<group>"; };
|
||||||
D5A3D4ED292575F000ECCEC9 /* ReadexPro-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ReadexPro-SemiBold.ttf"; sourceTree = "<group>"; };
|
|
||||||
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPythonType.swift; sourceTree = "<group>"; };
|
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPythonType.swift; sourceTree = "<group>"; };
|
||||||
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiStatusHeaderBarView.swift; sourceTree = "<group>"; };
|
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiStatusHeaderBarView.swift; sourceTree = "<group>"; };
|
||||||
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiListDetailView.swift; sourceTree = "<group>"; };
|
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiListDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -360,42 +335,25 @@
|
||||||
D567E2DD28C8D3E20009F768 /* SettingsView */ = {
|
D567E2DD28C8D3E20009F768 /* SettingsView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D56B75D2294BAA8900D008E7 /* BLESetttings */,
|
|
||||||
D567E2DE28C8D40C0009F768 /* SettingsView.swift */,
|
D567E2DE28C8D40C0009F768 /* SettingsView.swift */,
|
||||||
D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */,
|
D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = SettingsView;
|
path = SettingsView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D56B75D2294BAA8900D008E7 /* BLESetttings */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */,
|
|
||||||
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */,
|
|
||||||
);
|
|
||||||
path = BLESetttings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D58E1C8628A2B0DE00AB683E /* Wifi View */ = {
|
D58E1C8628A2B0DE00AB683E /* Wifi View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D567E2DD28C8D3E20009F768 /* SettingsView */,
|
|
||||||
D58E1C8728A2B10B00AB683E /* WifiView.swift */,
|
D58E1C8728A2B10B00AB683E /* WifiView.swift */,
|
||||||
D567E2B728C137880009F768 /* WifiCell.swift */,
|
|
||||||
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */,
|
|
||||||
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
|
|
||||||
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
|
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
|
||||||
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */,
|
|
||||||
D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */,
|
|
||||||
D5269BFF291960A300C0CE4B /* WifiSelection.swift */,
|
|
||||||
D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */,
|
|
||||||
D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */,
|
|
||||||
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
|
|
||||||
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
|
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
|
||||||
|
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
|
||||||
D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */,
|
D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */,
|
||||||
|
D567E2B728C137880009F768 /* WifiCell.swift */,
|
||||||
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */,
|
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */,
|
||||||
D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */,
|
D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */,
|
||||||
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */,
|
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */,
|
||||||
|
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
|
||||||
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */,
|
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */,
|
||||||
D567E2B528B81B730009F768 /* Queue.swift */,
|
D567E2B528B81B730009F768 /* Queue.swift */,
|
||||||
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */,
|
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */,
|
||||||
|
|
@ -412,6 +370,7 @@
|
||||||
D59DFDB2268CCEAC001737F6 /* Views */ = {
|
D59DFDB2268CCEAC001737F6 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D567E2DD28C8D3E20009F768 /* SettingsView */,
|
||||||
D58E1C8628A2B0DE00AB683E /* Wifi View */,
|
D58E1C8628A2B0DE00AB683E /* Wifi View */,
|
||||||
D5507ACE26C668BC00512BAA /* UI Components */,
|
D5507ACE26C668BC00512BAA /* UI Components */,
|
||||||
D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
|
D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
|
||||||
|
|
@ -469,7 +428,6 @@
|
||||||
D5C474C627E39FC8002DD160 /* ReadexPro-Medium.ttf */,
|
D5C474C627E39FC8002DD160 /* ReadexPro-Medium.ttf */,
|
||||||
D5C474C727E39FC8002DD160 /* ReadexPro-Regular.ttf */,
|
D5C474C727E39FC8002DD160 /* ReadexPro-Regular.ttf */,
|
||||||
D5C474C527E39FC8002DD160 /* ReadexPro-Light.ttf */,
|
D5C474C527E39FC8002DD160 /* ReadexPro-Light.ttf */,
|
||||||
D5A3D4ED292575F000ECCEC9 /* ReadexPro-SemiBold.ttf */,
|
|
||||||
D5C474C227E39FAD002DD160 /* ReadexPro-Bold.ttf */,
|
D5C474C227E39FAD002DD160 /* ReadexPro-Bold.ttf */,
|
||||||
);
|
);
|
||||||
path = ReadexPro;
|
path = ReadexPro;
|
||||||
|
|
@ -490,11 +448,9 @@
|
||||||
children = (
|
children = (
|
||||||
D505B99B2755323C00386E9F /* NetworkMonitor.swift */,
|
D505B99B2755323C00386E9F /* NetworkMonitor.swift */,
|
||||||
D544A24A281F92840038D483 /* ImageCaching.swift */,
|
D544A24A281F92840038D483 /* ImageCaching.swift */,
|
||||||
D5267410292E902700D4C79E /* Networking.swift */,
|
|
||||||
D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */,
|
D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */,
|
||||||
D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */,
|
D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */,
|
||||||
D595FC2D2812C23D00569D8C /* Image Extension.swift */,
|
D595FC2D2812C23D00569D8C /* Image Extension.swift */,
|
||||||
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */,
|
|
||||||
);
|
);
|
||||||
path = Networking;
|
path = Networking;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -503,7 +459,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
|
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
|
||||||
D5361099296FB2BB00228E15 /* DataStore.swift */,
|
|
||||||
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
|
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
|
|
@ -524,7 +479,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D5640215271B54BF00AE1519 /* MainSelectionView.swift */,
|
D5640215271B54BF00AE1519 /* MainSelectionView.swift */,
|
||||||
D52A926C29071DF400973B6B /* SelectionView.swift */,
|
|
||||||
D5482F4828E63DB7000B0C8E /* MainSelectionViewModel.swift */,
|
D5482F4828E63DB7000B0C8E /* MainSelectionViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = "Unpaired View";
|
path = "Unpaired View";
|
||||||
|
|
@ -663,7 +617,6 @@
|
||||||
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */,
|
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */,
|
||||||
D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */,
|
D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */,
|
||||||
D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */,
|
D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */,
|
||||||
D5267411292E902700D4C79E /* Networking.swift in Sources */,
|
|
||||||
D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */,
|
D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */,
|
||||||
D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */,
|
D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */,
|
||||||
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */,
|
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */,
|
||||||
|
|
@ -678,7 +631,6 @@
|
||||||
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */,
|
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */,
|
||||||
D5F53CED2694B7A9007634C2 /* OnboardingBackgroundView.swift in Sources */,
|
D5F53CED2694B7A9007634C2 /* OnboardingBackgroundView.swift in Sources */,
|
||||||
D5D1F4B227ECFF760040E2BF /* ProjectsModel.swift in Sources */,
|
D5D1F4B227ECFF760040E2BF /* ProjectsModel.swift in Sources */,
|
||||||
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */,
|
|
||||||
D5BA1F7A28B52A490012FC62 /* WifiListDetailView.swift in Sources */,
|
D5BA1F7A28B52A490012FC62 /* WifiListDetailView.swift in Sources */,
|
||||||
D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */,
|
D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */,
|
||||||
D567E2B628B81B730009F768 /* Queue.swift in Sources */,
|
D567E2B628B81B730009F768 /* Queue.swift in Sources */,
|
||||||
|
|
@ -692,17 +644,13 @@
|
||||||
D59DFDB6268CD052001737F6 /* AppEnvironment.swift in Sources */,
|
D59DFDB6268CD052001737F6 /* AppEnvironment.swift in Sources */,
|
||||||
D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */,
|
D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */,
|
||||||
D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */,
|
D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */,
|
||||||
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */,
|
|
||||||
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */,
|
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */,
|
||||||
D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */,
|
D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */,
|
||||||
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */,
|
|
||||||
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */,
|
|
||||||
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */,
|
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */,
|
||||||
D517F68126C5771D002996E8 /* FillerView.swift in Sources */,
|
D517F68126C5771D002996E8 /* FillerView.swift in Sources */,
|
||||||
D5DD39A728D11817000FAEB8 /* WifiFileTransfer.swift in Sources */,
|
D5DD39A728D11817000FAEB8 /* WifiFileTransfer.swift in Sources */,
|
||||||
D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */,
|
D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */,
|
||||||
D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */,
|
D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */,
|
||||||
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */,
|
|
||||||
D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */,
|
D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */,
|
||||||
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */,
|
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */,
|
||||||
D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */,
|
D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */,
|
||||||
|
|
@ -711,16 +659,13 @@
|
||||||
D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */,
|
D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */,
|
||||||
D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */,
|
D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */,
|
||||||
D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */,
|
D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */,
|
||||||
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */,
|
|
||||||
D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */,
|
D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */,
|
||||||
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */,
|
|
||||||
D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */,
|
D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */,
|
||||||
D59DFDBC268CE0EB001737F6 /* RootView.swift in Sources */,
|
D59DFDBC268CE0EB001737F6 /* RootView.swift in Sources */,
|
||||||
D595FC2E2812C23D00569D8C /* Image Extension.swift in Sources */,
|
D595FC2E2812C23D00569D8C /* Image Extension.swift in Sources */,
|
||||||
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */,
|
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */,
|
||||||
D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */,
|
D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */,
|
||||||
D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */,
|
D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */,
|
||||||
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */,
|
|
||||||
D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */,
|
D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */,
|
||||||
D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */,
|
D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */,
|
||||||
D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */,
|
D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */,
|
||||||
|
|
@ -728,7 +673,6 @@
|
||||||
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */,
|
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */,
|
||||||
D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */,
|
D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */,
|
||||||
D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */,
|
D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */,
|
||||||
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */,
|
|
||||||
D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */,
|
D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */,
|
||||||
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */,
|
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */,
|
||||||
D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */,
|
D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */,
|
||||||
|
|
@ -740,7 +684,6 @@
|
||||||
D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */,
|
D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */,
|
||||||
D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */,
|
D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */,
|
||||||
D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */,
|
D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */,
|
||||||
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */,
|
|
||||||
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */,
|
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */,
|
||||||
D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */,
|
D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */,
|
||||||
D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */,
|
D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */,
|
||||||
|
|
@ -749,7 +692,6 @@
|
||||||
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */,
|
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */,
|
||||||
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */,
|
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */,
|
||||||
D5640216271B54BF00AE1519 /* MainSelectionView.swift in Sources */,
|
D5640216271B54BF00AE1519 /* MainSelectionView.swift in Sources */,
|
||||||
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */,
|
|
||||||
D505B99C2755323C00386E9F /* NetworkMonitor.swift in Sources */,
|
D505B99C2755323C00386E9F /* NetworkMonitor.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
@ -881,17 +823,17 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 0;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = PyLeap/Info.plist;
|
INFOPLIST_FILE = PyLeap/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.1.1;
|
MARKETING_VERSION = 1.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
|
@ -911,17 +853,17 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 0;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = PyLeap/Info.plist;
|
INFOPLIST_FILE = PyLeap/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.1.1;
|
MARKETING_VERSION = 1.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -9,6 +9,8 @@ import UIKit
|
||||||
|
|
||||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
|
@ -18,7 +20,21 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupAppearances() {
|
private func setupAppearances() {
|
||||||
// Alerts
|
print("Set Appearance")
|
||||||
|
// Navigation bar title
|
||||||
|
// UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
|
||||||
|
// UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white]
|
||||||
|
|
||||||
|
// Navigation bar background
|
||||||
|
// UINavigationBar.appearance().barTintColor = .clear
|
||||||
|
// UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
|
||||||
|
|
||||||
|
// List background
|
||||||
|
// UITableView.appearance().backgroundColor = UIColor.clear
|
||||||
|
// UITableView.appearance().separatorStyle = .none
|
||||||
|
// UITableViewCell.appearance().backgroundColor = .clear
|
||||||
|
//
|
||||||
|
// Alerts
|
||||||
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .blue
|
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .blue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
PyLeap/Assets.xcassets/.DS_Store
vendored
BIN
PyLeap/Assets.xcassets/.DS_Store
vendored
Binary file not shown.
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "150",
|
"blue" : "0.769",
|
||||||
"green" : "100",
|
"green" : "0.561",
|
||||||
"red" : "74"
|
"red" : "0.380"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "150",
|
"blue" : "0.773",
|
||||||
"green" : "100",
|
"green" : "0.561",
|
||||||
"red" : "74"
|
"red" : "0.380"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "97",
|
|
||||||
"green" : "97",
|
|
||||||
"red" : "97"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "97",
|
|
||||||
"green" : "97",
|
|
||||||
"red" : "97"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "bluetoothLogo.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.9 KiB |
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "196",
|
|
||||||
"green" : "143",
|
|
||||||
"red" : "97"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "255",
|
|
||||||
"green" : "255",
|
|
||||||
"red" : "255"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,17 +11,17 @@ import Zip
|
||||||
class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate {
|
class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate {
|
||||||
|
|
||||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
let cachesPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||||
|
|
||||||
static let shared = DownloadViewModel()
|
static let shared = DownloadViewModel()
|
||||||
|
@StateObject var globalString = GlobalString()
|
||||||
var bundleURL = String()
|
|
||||||
var bundleTitle = String()
|
|
||||||
|
|
||||||
// Alert
|
// Alert
|
||||||
@Published var alertMsg = ""
|
@Published var alertMsg = ""
|
||||||
@Published var showAlert = false
|
@Published var showAlert = false
|
||||||
|
|
||||||
var manager = FileManager.default
|
// Saving Download task reference for cancelling...
|
||||||
|
@Published var downloadTaskSession: URLSessionDownloadTask!
|
||||||
|
|
||||||
// Show Progress View
|
// Show Progress View
|
||||||
@Published var downloadProgress: CGFloat = 0
|
@Published var downloadProgress: CGFloat = 0
|
||||||
|
|
@ -31,232 +31,12 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
|
||||||
@Published var isDownloading = false
|
@Published var isDownloading = false
|
||||||
|
|
||||||
// Saving Download task refernce for cancelling...
|
// Saving Download task refernce for cancelling...
|
||||||
// @Published var downloadtaskSession : URLSessionDownloadTask!
|
@Published var downloadtaskSession : URLSessionDownloadTask!
|
||||||
|
|
||||||
@Published var attemptToSendBunle = false
|
@Published var attemptToSendBunle = false
|
||||||
|
|
||||||
@Published var state: DownloadState = .idle
|
@Published var state: DownloadState = .idle
|
||||||
|
|
||||||
private lazy var session: URLSession = {
|
|
||||||
let configuration = URLSessionConfiguration.default
|
|
||||||
configuration.timeoutIntervalForResource = 5
|
|
||||||
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
func trueDownload(useProject link: String, projectName: String) {
|
|
||||||
|
|
||||||
let CPZipName = directoryPath.appendingPathComponent("\(projectName).zip")
|
|
||||||
let request = URLRequest(url: URL(string: link)!)
|
|
||||||
|
|
||||||
session.downloadTask(with: request).resume()
|
|
||||||
bundleURL = link
|
|
||||||
bundleTitle = projectName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saving Download task reference for cancelling...
|
|
||||||
@Published var downloadTaskSession: URLSessionDownloadTask!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func newZip(projectTitle: String, location: URL) {
|
|
||||||
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
|
|
||||||
print("Location 1: \(location)")
|
|
||||||
|
|
||||||
do {
|
|
||||||
|
|
||||||
let zipData = try Data(contentsOf: location)
|
|
||||||
|
|
||||||
try zipData.write(to: CPZipName)
|
|
||||||
|
|
||||||
// let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
|
|
||||||
|
|
||||||
try FileManager.default.removeItem(at: CPZipName)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
print("newZip - Zip ERROR")
|
|
||||||
print("Error: \(error)")
|
|
||||||
print("Location 2: \(location)")
|
|
||||||
self.state = .failed
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
||||||
self.state = .idle
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func unzipProjectFile(urlString: String, projectTitle: String) {
|
|
||||||
print("Times unzipProjectFile was called")
|
|
||||||
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
|
|
||||||
|
|
||||||
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
|
|
||||||
|
|
||||||
if let zipFileUrl = URL(string: urlString) {
|
|
||||||
// Download from this site
|
|
||||||
URLSession.shared.downloadTask(with: zipFileUrl) { (tempFileUrl, response, error) in
|
|
||||||
|
|
||||||
if let zipTempFileUrl = tempFileUrl {
|
|
||||||
|
|
||||||
do {
|
|
||||||
print("Times do looped in unzipProjectFile")
|
|
||||||
let zipData = try Data(contentsOf: zipTempFileUrl)
|
|
||||||
|
|
||||||
try zipData.write(to: CPZipName)
|
|
||||||
|
|
||||||
let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
|
|
||||||
|
|
||||||
try FileManager.default.removeItem(at: CPZipName)
|
|
||||||
|
|
||||||
|
|
||||||
var projectResponse = [String: String]()
|
|
||||||
projectResponse["projectTitle"] = self.bundleTitle
|
|
||||||
projectResponse["projectLink"] = self.bundleURL
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
|
|
||||||
|
|
||||||
print("times wifiDownloadComplete was triggered")
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .wifiDownloadComplete, object: nil, userInfo: projectResponse)
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
print("unzipProjectFile - Zip ERROR")
|
|
||||||
print("Error: \(error)")
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .downloadErrorDidOccur, object: nil, userInfo: nil)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.state = .failed
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
||||||
self.state = .idle
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
self.state = .failed
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
||||||
self.state = .idle
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
|
||||||
// print("Download succeeded")
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Periodically informs the delegate about the download’s progress - Used for progress UI
|
|
||||||
|
|
||||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
|
||||||
// Getting Progress
|
|
||||||
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
|
|
||||||
print("Progress: \(numeralProgress)")
|
|
||||||
|
|
||||||
// Since URL Session will be running in the background thread
|
|
||||||
// UI will be done on the main thread
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
|
|
||||||
self.downloadProgress = numeralProgress
|
|
||||||
print("Recorded downloadProgress Progress: \(self.downloadProgress)")
|
|
||||||
print("Recorded numeralProgress Progress: \(numeralProgress)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
||||||
|
|
||||||
|
|
||||||
guard let error = error else { return }
|
|
||||||
print(error)
|
|
||||||
|
|
||||||
guard let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data else {
|
|
||||||
print("Download failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session.downloadTask(withResumeData: resumeData).resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tells the delegate that a download task has finished downloading.
|
|
||||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
|
||||||
|
|
||||||
print("urlSession completed")
|
|
||||||
|
|
||||||
guard let httpResponse = downloadTask.response as? HTTPURLResponse,
|
|
||||||
(200...299).contains(httpResponse.statusCode) else {
|
|
||||||
print ("server error")
|
|
||||||
NotificationCenter.default.post(name: .downloadErrorDidOccur, object: nil, userInfo: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Location: \(location)")
|
|
||||||
|
|
||||||
guard let url = downloadTask.originalRequest?.url else {
|
|
||||||
self.reportError(error: "An error has occurred...")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating a destination for storing files with a destination URL
|
|
||||||
let destinationURL = directoryPath.appendingPathComponent(url.lastPathComponent)
|
|
||||||
|
|
||||||
//if that file already exists, replace it.
|
|
||||||
|
|
||||||
try? FileManager.default.removeItem(at: destinationURL)
|
|
||||||
|
|
||||||
print(#function)
|
|
||||||
do {
|
|
||||||
|
|
||||||
// Copy temp file to directory.
|
|
||||||
try FileManager.default.copyItem(at: location, to: destinationURL)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
print("unzipProjectFile loop")
|
|
||||||
self.unzipProjectFile(urlString: self.bundleURL, projectTitle: self.bundleTitle)
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
||||||
self.attemptToSendBunle.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .didCompleteTransfer, object: nil, userInfo: nil)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
self.reportError(error: "Try again later")
|
|
||||||
isDownloading = false
|
|
||||||
self.didDownloadBundle = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report Error Function...
|
|
||||||
func reportError(error: String){
|
|
||||||
alertMsg = error
|
|
||||||
showAlert.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK:- Download
|
// MARK:- Download
|
||||||
func startDownload(urlString: String, projectTitle: String) {
|
func startDownload(urlString: String, projectTitle: String) {
|
||||||
print("Starting Download...")
|
print("Starting Download...")
|
||||||
|
|
@ -274,19 +54,86 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
|
||||||
downloadTaskSession = session.downloadTask(with: validURL)
|
downloadTaskSession = session.downloadTask(with: validURL)
|
||||||
downloadTaskSession.resume()
|
downloadTaskSession.resume()
|
||||||
|
|
||||||
|
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startDownload(urlString: String, projectTitle: String, compeletion: () -> ()) {
|
||||||
|
print("Starting Download...")
|
||||||
|
isDownloading = true
|
||||||
|
// Check for valid URL
|
||||||
|
guard let validURL = URL(string: urlString) else {
|
||||||
|
self.reportError(error: "Invalid URL!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
downloadProgress = 0
|
||||||
|
|
||||||
func testCallback(completion: ()->()) {
|
// Download Task...
|
||||||
print("Do something")
|
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
|
||||||
|
|
||||||
|
downloadTaskSession = session.downloadTask(with: validURL)
|
||||||
|
downloadTaskSession.resume()
|
||||||
|
|
||||||
|
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeFileDirectory() {
|
||||||
|
// Creating a File Manager Object
|
||||||
|
|
||||||
|
// Creating a folder
|
||||||
|
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
}
|
try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
|
||||||
extension DownloadViewModel {
|
withIntermediateDirectories: true,
|
||||||
|
attributes: [:])
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unzipProjectFile(urlString: String, projectTitle: String) {
|
||||||
|
|
||||||
|
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
|
||||||
|
|
||||||
|
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
|
||||||
|
|
||||||
|
if let zipFileUrl = URL(string: urlString) {
|
||||||
|
// Download from this site
|
||||||
|
URLSession.shared.downloadTask(with: zipFileUrl) { (tempFileUrl, response, error) in
|
||||||
|
|
||||||
|
/*
|
||||||
|
if let...
|
||||||
|
if you can let the new variable name equal the non-optional version of optionalName, do the following with it"
|
||||||
|
*/
|
||||||
|
|
||||||
|
if let zipTempFileUrl = tempFileUrl {
|
||||||
|
do {
|
||||||
|
|
||||||
|
let zipData = try Data(contentsOf: zipTempFileUrl)
|
||||||
|
|
||||||
|
try zipData.write(to: CPZipName)
|
||||||
|
|
||||||
|
let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: CPZipName)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print("Zip ERROR")
|
||||||
|
print("Error: \(error)")
|
||||||
|
|
||||||
|
self.state = .failed
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
|
self.state = .idle
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createNewTextFile() {
|
func createNewTextFile() {
|
||||||
|
|
||||||
|
|
@ -335,20 +182,89 @@ extension DownloadViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFileDirectory() {
|
/// Periodically informs the delegate about the download’s progress - Used for progress UI
|
||||||
// Creating a File Manager Object
|
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||||
|
// Getting Progress
|
||||||
|
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
|
||||||
|
print("Progress: \(numeralProgress)")
|
||||||
|
|
||||||
// Creating a folder
|
// Since URL Session will be running in the background thread
|
||||||
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
|
// UI will be done on the main thread
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
do {
|
self.downloadProgress = numeralProgress
|
||||||
|
print("Recorded downloadProgress Progress: \(self.downloadProgress)")
|
||||||
try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
|
print("Recorded numeralProgress Progress: \(numeralProgress)")
|
||||||
withIntermediateDirectories: true,
|
|
||||||
attributes: [:])
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tells the delegate that a download task has finished downloading.
|
||||||
|
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||||
|
|
||||||
|
guard let url = downloadTask.originalRequest?.url else {
|
||||||
|
self.reportError(error: "An error has occurred...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating a destination for storing files with a destination URL
|
||||||
|
let destinationURL = directoryPath.appendingPathComponent(url.lastPathComponent)
|
||||||
|
|
||||||
|
//if that file already exists, replace it.
|
||||||
|
|
||||||
|
try? FileManager.default.removeItem(at: destinationURL)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print(#function)
|
||||||
|
do {
|
||||||
|
|
||||||
|
// Copy temp file to directory.
|
||||||
|
try FileManager.default.copyItem(at: location, to: destinationURL)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// If Successful...
|
||||||
|
print("Successful Download")
|
||||||
|
self.isDownloading = false
|
||||||
|
print("Download Location: \(location)")
|
||||||
|
self.downloadProgress = 1.0
|
||||||
|
print("\(self.didDownloadBundle) CURRENT STATE")
|
||||||
|
self.didDownloadBundle = true
|
||||||
|
print("\(self.didDownloadBundle) CURRENT STATE")
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
|
self.attemptToSendBunle.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
self.reportError(error: "Try again later")
|
||||||
|
isDownloading = false
|
||||||
|
self.didDownloadBundle = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report Error Function...
|
||||||
|
func reportError(error: String){
|
||||||
|
alertMsg = error
|
||||||
|
showAlert.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel Task...
|
||||||
|
func cancelTask(){
|
||||||
|
if let task = downloadtaskSession,task.state == .running{
|
||||||
|
// cancelling...
|
||||||
|
downloadtaskSession.cancel()
|
||||||
|
// closing view...
|
||||||
|
// withAnimation{self.showDownlodProgress = false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ struct ExampleView: View {
|
||||||
//
|
//
|
||||||
// PageView(title: "Choose your Adventure!", subtitle: "Choose a project you would like to send over to your PyLeap compatible device.", imageName: "slide3", showDismissButton: false, shouldShowOnboarding: $shouldShowOnboarding)
|
// PageView(title: "Choose your Adventure!", subtitle: "Choose a project you would like to send over to your PyLeap compatible device.", imageName: "slide3", showDismissButton: false, shouldShowOnboarding: $shouldShowOnboarding)
|
||||||
//
|
//
|
||||||
PageView(title: "Send projects directly from the Adafruit Learning System to your Adafruit Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
|
PageView(title: "Send projects directly from the Adafruit Learning System to your Bluefruit Compatible Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
|
||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(PageTabViewStyle())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>NSBonjourServices</key>
|
||||||
|
<array>
|
||||||
|
<string>_circuitpython._tcp</string>
|
||||||
|
<string>_bonjour._tcp</string>
|
||||||
|
</array>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
@ -28,11 +33,6 @@
|
||||||
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
|
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
|
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
|
||||||
<key>NSBonjourServices</key>
|
|
||||||
<array>
|
|
||||||
<string>_circuitpython._tcp</string>
|
|
||||||
<string>_bonjour._tcp</string>
|
|
||||||
</array>
|
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<string>PyLeap uses the local network to communicate with your Adafruit device</string>
|
<string>PyLeap uses the local network to communicate with your Adafruit device</string>
|
||||||
<key>UIAppFonts</key>
|
<key>UIAppFonts</key>
|
||||||
|
|
@ -64,6 +64,10 @@
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
//
|
|
||||||
// DataStore.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 1/11/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/**
|
|
||||||
/// This is a DataStore class that is used for saving and loading data to/from the file system using the FileManager class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DataStore {
|
|
||||||
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
|
|
||||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
- parameter content: This method takes an array of ResultItem objects.
|
|
||||||
|
|
||||||
- returns: completion
|
|
||||||
|
|
||||||
/// This method writes it to a file named "StandardPyLeapProjects.json" in the documents directory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func save(content: [ResultItem], completion: @escaping () -> Void) {
|
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
if let encodedProjectData = try? encoder.encode(content) {
|
|
||||||
|
|
||||||
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let fileURL = documentsURL.appendingPathComponent("StandardPyLeapProjects.json")
|
|
||||||
try? encodedProjectData.write(to: fileURL)
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
- parameter content: This method takes an array of ResultItem objects.
|
|
||||||
|
|
||||||
- returns: completion
|
|
||||||
|
|
||||||
/// This method reads the "StandardPyLeapProjects.json" file in the documents directory and decodes it as an array of ResultItem objects, and then appends the customProjects array to it and saves it back to the file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func save(customProjects: [ResultItem], completion: @escaping () -> Void) {
|
|
||||||
|
|
||||||
var temp = customProjects
|
|
||||||
|
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
|
||||||
let savedData = try? Data(contentsOf: fileURL)
|
|
||||||
|
|
||||||
if let savedData = savedData,
|
|
||||||
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
|
|
||||||
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
|
|
||||||
temp.append(contentsOf: savedProjects)
|
|
||||||
|
|
||||||
save(content: temp) {
|
|
||||||
self.removeDuplicates(projectList: temp)
|
|
||||||
}
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
/// This method reads the "StandardPyLeapProjects.json" file in the documents directory and decodes it as an array of ResultItem objects, and then calls the loadCustomProjectList(contents:) method with the decoded array as an argument
|
|
||||||
*/
|
|
||||||
|
|
||||||
func loadDefaultProjectList() {
|
|
||||||
|
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
|
||||||
let savedData = try? Data(contentsOf: fileURL)
|
|
||||||
|
|
||||||
if let savedData = savedData,
|
|
||||||
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
|
|
||||||
loadCustomProjectList(contents: savedProjects)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
/// : This method reads the "StandardPyLeapProjects.json" file in the documents directory, decodes it as an array of ResultItem objects, and returns it.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func loadDefaultList() -> [ResultItem] {
|
|
||||||
|
|
||||||
var result = [ResultItem]()
|
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
|
||||||
|
|
||||||
let savedData = try? Data(contentsOf: fileURL)
|
|
||||||
|
|
||||||
if let savedData = savedData,
|
|
||||||
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
|
|
||||||
result = savedProjects
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
- parameter: This method takes an array of ResultItem objects
|
|
||||||
|
|
||||||
- returns: completion
|
|
||||||
|
|
||||||
/// This method reads the "CustomProjects.json" file in the documents directory, decodes it as an array of ResultItem objects, appends it to the input array, and then calls
|
|
||||||
*/
|
|
||||||
|
|
||||||
func loadCustomProjectList(contents: [ResultItem]) {
|
|
||||||
var temp = contents
|
|
||||||
|
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("CustomProjects.json")
|
|
||||||
let savedData = try? Data(contentsOf: fileURL)
|
|
||||||
|
|
||||||
if let savedData = savedData,
|
|
||||||
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
|
|
||||||
temp.append(contentsOf: savedProjects)
|
|
||||||
removeDuplicates(projectList: temp)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
- parameter: This method takes an array of ResultItem objects
|
|
||||||
|
|
||||||
- returns: completion
|
|
||||||
|
|
||||||
/// This method uses the reduce(into:_:) method to iterate over the array, and it builds a new array that only contains unique ResultItem objects based on their bundleLink property. It then calls the save(content:completion:) method to save the new array to "StandardPyLeapProjects.json" file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func removeDuplicates(projectList: [ResultItem]) {
|
|
||||||
|
|
||||||
let combinedLists = projectList.reduce(into: [ResultItem]()) { (result, projectList) in
|
|
||||||
|
|
||||||
if !result.contains(where: { $0.bundleLink == projectList.bundleLink }) {
|
|
||||||
result.append(projectList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
save(content: combinedLists) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadThirdPartyProjectsFromFileManager() {
|
|
||||||
|
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("CustomProjects.json")
|
|
||||||
let savedData = try? Data(contentsOf: fileURL)
|
|
||||||
|
|
||||||
if let savedData = savedData,
|
|
||||||
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
|
|
||||||
|
|
||||||
for project in savedProjects {
|
|
||||||
print("CustomProjects name: \(project.projectName)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -30,5 +30,3 @@ struct ResultItem: Codable, Identifiable, Equatable {
|
||||||
let learnGuideLink: String
|
let learnGuideLink: String
|
||||||
let compatibility: [String]
|
let compatibility: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// JSONDecoderHelper.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 1/11/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class JSONDecoderHelper {
|
|
||||||
static func decode<T: Decodable>(data: Data) -> T? {
|
|
||||||
let decoder = JSONDecoder()
|
|
||||||
return try? decoder.decode(T.self, from: data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,9 +14,221 @@ import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class NetworkService: ObservableObject {
|
class NetworkService: ObservableObject {
|
||||||
let dataStore = DataStore()
|
|
||||||
|
|
||||||
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent)
|
static let shared = NetworkService()
|
||||||
|
|
||||||
|
@Published var pdemos : [ResultItem] = []
|
||||||
|
@State var storedURL = ""
|
||||||
|
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
|
||||||
|
init(){
|
||||||
|
load()
|
||||||
|
loadCustProjects()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCustProjects() {
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
print("++++Custom Projects++++")
|
||||||
|
for i in loadedProjects {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func save(content: [ResultItem]) {
|
||||||
|
print("Saving JSON response...")
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
if let encoded = try? encoder.encode(content) {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
defaults.set(encoded, forKey: "SavedProjects")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(customProjects: [ResultItem]) {
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
|
||||||
|
if let encoded = try? encoder.encode(customProjects) {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
defaults.set(encoded, forKey: "CustomProjects")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveCustomProjects(content: [ResultItem]) {
|
||||||
|
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
|
||||||
|
// if let newIncoming = content.contains()
|
||||||
|
|
||||||
|
var customList: [ResultItem] = []
|
||||||
|
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
print("Loading previous list")
|
||||||
|
|
||||||
|
for i in loadedProjects {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
customList = loadedProjects
|
||||||
|
print("Appending new content")
|
||||||
|
|
||||||
|
customList.append(contentsOf: content)
|
||||||
|
|
||||||
|
for i in customList {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
if let encoded = try? encoder.encode(customList) {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
print("Saved new custom list.")
|
||||||
|
defaults.set(encoded, forKey: "CustomProjects")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIncomingProject(json response: [ResultItem]){
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
/// Does incoming project exist already?
|
||||||
|
/// Check with object's property URL
|
||||||
|
|
||||||
|
|
||||||
|
if loadedProjects.contains(where: { $0.bundleLink == response[0].bundleLink
|
||||||
|
}) {
|
||||||
|
print("does exist")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
print("does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
print(loadedProjects)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCustomProjects() -> [ResultItem]{
|
||||||
|
print(#function)
|
||||||
|
var customList: [ResultItem] = []
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
print("Load custom projectsxo")
|
||||||
|
// print(loadedProjects)
|
||||||
|
|
||||||
|
for i in loadedProjects {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
customList = loadedProjects
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return customList
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeProjects() {
|
||||||
|
print(#function)
|
||||||
|
|
||||||
|
var standardList: [ResultItem] = []
|
||||||
|
var customList: [ResultItem] = []
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
print("Load saved projects")
|
||||||
|
|
||||||
|
// let check = loadedProjects.map { $0.bundleLink == "" }
|
||||||
|
|
||||||
|
pdemos = loadedProjects
|
||||||
|
|
||||||
|
print(loadedProjects)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
print("Load saved projects")
|
||||||
|
|
||||||
|
print(loadedProjects)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
|
||||||
|
let mergedList = loadedProjects + loadCustomProjects()
|
||||||
|
|
||||||
|
// save(content: <#T##[ResultItem]#>)
|
||||||
|
|
||||||
|
pdemos = mergedList
|
||||||
|
|
||||||
|
|
||||||
|
print("----Standard Projects----")
|
||||||
|
for i in loadedProjects {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
print("++++Custom Projects++++")
|
||||||
|
for i in loadCustomProjects() {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private var dataTask: URLSessionDataTask?
|
private var dataTask: URLSessionDataTask?
|
||||||
|
|
||||||
|
|
@ -26,16 +238,24 @@ class NetworkService: ObservableObject {
|
||||||
|
|
||||||
// Session Configuration & Caching Policy
|
// Session Configuration & Caching Policy
|
||||||
let configuration = URLSessionConfiguration.default
|
let configuration = URLSessionConfiguration.default
|
||||||
configuration.requestCachePolicy = .returnCacheDataElseLoad
|
// configuration.requestCachePolicy = .useProtocolCachePolicy
|
||||||
|
|
||||||
return URLSession(configuration: configuration)
|
return URLSession(configuration: configuration)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@Published var projectInfo = Data()
|
||||||
|
|
||||||
func fetch(completion: @escaping() -> Void) {
|
func fetch() {
|
||||||
print("Attempting Network Request")
|
|
||||||
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 60.0)
|
let cache = URLCache.shared
|
||||||
let task = session.dataTask(with: request) { data, response, error in
|
|
||||||
|
let requestForCache = URLRequest(url: URL(string: AdafruitInfo.baseURL)!)
|
||||||
|
|
||||||
|
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
|
||||||
|
|
||||||
|
print("Making Network Request.")
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("error: \(error)")
|
print("error: \(error)")
|
||||||
|
|
@ -47,82 +267,78 @@ class NetworkService: ObservableObject {
|
||||||
if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) {
|
if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) {
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
self.save(content: projectData.projects)
|
||||||
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList)
|
self.load()
|
||||||
|
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
print("No data found")
|
print("No data found")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
print("Updating UIList with Cached data...")
|
print("Updating UIList with Cached data...")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.dataStore.loadDefaultProjectList()
|
self.load()
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchThirdParyProject(urlString: String?) {
|
||||||
|
let cache = URLCache.shared
|
||||||
|
|
||||||
func fetchThirdPartyProject(urlString: String?) {
|
guard let urlString = urlString else {
|
||||||
|
print("Error")
|
||||||
thirdPartyBackgroundQueue.async {
|
return
|
||||||
|
|
||||||
guard let urlString = urlString else {
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
|
||||||
print("Error urlString")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if urlString.contains(" ") {
|
|
||||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let requestURL = URL(string: urlString) else {
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
print("Error requestURL")
|
|
||||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = URLRequest(url: requestURL, cachePolicy: URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 0.0)
|
|
||||||
|
|
||||||
print("Making Network Request for Custom Project.")
|
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
|
|
||||||
if let error = error {
|
|
||||||
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let data = data {
|
|
||||||
|
|
||||||
let projectData = JSONDecoderHelper.decode(data: data) as RootResults?
|
|
||||||
|
|
||||||
if let projects = projectData?.projects {
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.dataStore.save(customProjects: projects, completion: self.dataStore.loadThirdPartyProjectsFromFileManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if urlString.contains(" ") {
|
||||||
|
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: URL(string: urlString)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
|
||||||
|
|
||||||
|
|
||||||
|
print("Making Network Request for Custom Project.")
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = data {
|
||||||
|
|
||||||
|
print("Updating UIList with new data...")
|
||||||
|
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
|
||||||
|
|
||||||
|
if let projects = projectData?.projects {
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
print(#function)
|
||||||
|
for i in projects {
|
||||||
|
print("\(i.projectName)")
|
||||||
|
}
|
||||||
|
// self.pdemos.append(contentsOf: projects)
|
||||||
|
self.saveCustomProjects(content: projects)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
//
|
|
||||||
// Networking.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 11/23/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
|
||||||
enum HTTPMethod: String {
|
|
||||||
case delete = "DELETE"
|
|
||||||
case get = "GET"
|
|
||||||
case patch = "PATCH"
|
|
||||||
case post = "POST"
|
|
||||||
case put = "PUT"
|
|
||||||
case options = "OPTIONS"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HTTPScheme: String {
|
|
||||||
case http
|
|
||||||
case https
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The API protocol allows us to separate the task of constructing a URL,
|
|
||||||
/// its parameters, and HTTP method from the act of executing the URL request
|
|
||||||
/// and parsing the response.
|
|
||||||
///
|
|
||||||
|
|
||||||
protocol API {
|
|
||||||
|
|
||||||
var scheme: HTTPScheme { get }
|
|
||||||
|
|
||||||
var baseURL: String { get }
|
|
||||||
|
|
||||||
var path: String { get }
|
|
||||||
// [URLQueryItem(name: "api_key", value: API_KEY)]
|
|
||||||
var parameters: [URLQueryItem] { get }
|
|
||||||
|
|
||||||
var method: HTTPMethod { get }
|
|
||||||
}
|
|
||||||
Binary file not shown.
BIN
PyLeap/Views/.DS_Store
vendored
BIN
PyLeap/Views/.DS_Store
vendored
Binary file not shown.
|
|
@ -60,7 +60,7 @@ struct BLEPairingView: View {
|
||||||
//.padding(.top, 100)
|
//.padding(.top, 100)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
BlinkaAnimationView(height: 250, width: 250)
|
BlinkaAnimationView()
|
||||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||||
|
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
|
|
|
||||||
|
|
@ -29,34 +29,41 @@ struct BTConnectionView: View {
|
||||||
VStack{
|
VStack{
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
|
||||||
rootViewModel.goToSelection()
|
|
||||||
|
|
||||||
|
Button {
|
||||||
|
|
||||||
|
self.rootViewModel.goToMain()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.backward")
|
Image(systemName: "arrow.backward")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
.aspectRatio(contentMode: .fit)
|
||||||
.offset(y: 15)
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
.foregroundColor(.black)
|
.foregroundColor(Color("pyleap_gray"))
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
Image("bluetooth")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
|
.offset(y: -5)
|
||||||
|
|
||||||
|
.onReceive(timer) { _ in
|
||||||
|
nextText = 1
|
||||||
|
timer.upstream.connect().cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 15)
|
.padding(.top, 50)
|
||||||
|
.padding(.horizontal, 30)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Image("pyleapLogo")
|
Image("pyleapLogo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
.padding(.horizontal, 60)
|
.padding(.horizontal, 60)
|
||||||
|
|
||||||
|
|
@ -69,29 +76,19 @@ struct BTConnectionView: View {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Text("Bluetooth Connect")
|
Text("Searching for PyLeap compatible device...")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 36))
|
.padding(.horizontal, 30)
|
||||||
.multilineTextAlignment(.center)
|
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||||
.minimumScaleFactor(0.01)
|
.minimumScaleFactor(0.1)
|
||||||
.lineLimit(1)
|
|
||||||
.padding()
|
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 30)
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
BlinkaAnimationView()
|
||||||
BlinkaAnimationView(height: 150, width: 145)
|
.minimumScaleFactor(0.1)
|
||||||
.padding(.bottom, 20)
|
|
||||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||||
.onAppear() {
|
|
||||||
Animation.linear(duration: 1.0)
|
|
||||||
.repeatForever(autoreverses: false)
|
|
||||||
isAnimating = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
Animation.linear(duration: 1.0)
|
Animation.linear(duration: 1.0)
|
||||||
|
|
@ -101,9 +98,6 @@ struct BTConnectionView: View {
|
||||||
|
|
||||||
Text(detailText)
|
Text(detailText)
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.lineLimit(2)
|
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
nextText = 1
|
nextText = 1
|
||||||
|
|
@ -228,7 +222,10 @@ struct BTConnectionView: View {
|
||||||
Text("Pair Device")
|
Text("Pair Device")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
|
|
||||||
|
|
||||||
.padding(.horizontal, 60)
|
.padding(.horizontal, 60)
|
||||||
|
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.background(Color("pyleap_pink"))
|
.background(Color("pyleap_pink"))
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
|
|
@ -278,7 +275,7 @@ struct BTConnectionView: View {
|
||||||
let text: String
|
let text: String
|
||||||
switch model.connectionStatus {
|
switch model.connectionStatus {
|
||||||
case .scanning:
|
case .scanning:
|
||||||
text = "Scanning for PyLeap compatible devices..."
|
text = "Scanning..."
|
||||||
case .restoringConnection:
|
case .restoringConnection:
|
||||||
text = "Restoring connection..."
|
text = "Restoring connection..."
|
||||||
case .connecting:
|
case .connecting:
|
||||||
|
|
@ -295,8 +292,11 @@ struct BTConnectionView: View {
|
||||||
case .disconnected(let error):
|
case .disconnected(let error):
|
||||||
if let error = error {
|
if let error = error {
|
||||||
text = "Disconnected: \(error.localizedDescription)"
|
text = "Disconnected: \(error.localizedDescription)"
|
||||||
|
//self.showSheetView.toggle()
|
||||||
} else {
|
} else {
|
||||||
text = "Disconnected"
|
text = "Disconnected"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ struct TroubleshootView: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|
||||||
UIApplication.shared.open(URL(string: "App-Prefs:root=Bluetooth")!)
|
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
|
||||||
} label: {
|
} label: {
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,17 @@ struct FillerView: View {
|
||||||
Image("pyleapLogo")
|
Image("pyleapLogo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.offset(y: -30)
|
.offset(y: -20)
|
||||||
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
|
||||||
}
|
}
|
||||||
.preferredColorScheme(.light)
|
.preferredColorScheme(.light)
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 20)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
.modifier(Alerts(activeAlert: $model.activeAlert, model: model))
|
.modifier(Alerts(activeAlert: $model.activeAlert, model: model))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
print("Filler View")
|
||||||
model.setupBluetooth()
|
model.setupBluetooth()
|
||||||
}
|
}
|
||||||
.onChange(of: model.isStartupFinished) { isStartupFinished in
|
.onChange(of: model.isStartupFinished) { isStartupFinished in
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,12 @@ import SwiftUI
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
|
|
||||||
class BleContentCommands: ObservableObject {
|
class BleContentCommands {
|
||||||
|
|
||||||
private weak var fileTransferClient: FileTransferClient?
|
private weak var fileTransferClient: FileTransferClient?
|
||||||
@Published var transmissionProgress: TransmissionProgress?
|
@Published var transmissionProgress: TransmissionProgress?
|
||||||
@Published var isTransmiting = false
|
@Published var isTransmiting = false
|
||||||
@Published var bootUpInfo = String()
|
@Published var bootUpInfo = String()
|
||||||
@Published var counter = 0
|
|
||||||
|
|
||||||
enum ProjectViewError: LocalizedError {
|
enum ProjectViewError: LocalizedError {
|
||||||
case fileTransferUndefined
|
case fileTransferUndefined
|
||||||
|
|
@ -104,8 +103,6 @@ class BleContentCommands: ObservableObject {
|
||||||
let str = String(decoding: data, as: UTF8.self)
|
let str = String(decoding: data, as: UTF8.self)
|
||||||
print("Read: \(str)")
|
print("Read: \(str)")
|
||||||
self.bootUpInfo = str
|
self.bootUpInfo = str
|
||||||
sharedBootinfo = str
|
|
||||||
|
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
|
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
|
||||||
|
|
@ -237,10 +234,8 @@ class BleContentCommands: ObservableObject {
|
||||||
func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
|
func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
|
||||||
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.counter += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -257,11 +252,9 @@ class BleContentCommands: ObservableObject {
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
DLog("writeFile \(path) success. Size: \(data.count)")
|
DLog("writeFile \(path) success. Size: \(data.count)")
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DLog("writeFile \(path) error: \(error)")
|
DLog("writeFile \(path) error: \(error)")
|
||||||
print("Deep Error")
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -8,8 +8,8 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
class ExpandedBLECellState: ObservableObject {
|
class SpotlightCounter: ObservableObject {
|
||||||
@Published var currentCell = ""
|
@Published var counter = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BleModuleView: View {
|
struct BleModuleView: View {
|
||||||
|
|
@ -25,43 +25,52 @@ struct BleModuleView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
|
||||||
|
|
||||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
||||||
|
|
||||||
@StateObject var viewModel = BleModuleViewModel()
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
|
|
||||||
|
|
||||||
|
@StateObject var viewModel = BleModuleViewModel()
|
||||||
|
@ObservedObject var networkServiceModel = NetworkService()
|
||||||
|
@StateObject var globalString = GlobalString()
|
||||||
|
@StateObject var btConnectionViewModel = BTConnectionViewModel()
|
||||||
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
@StateObject var downloadModel = DownloadViewModel()
|
||||||
|
@StateObject var spotlight = SpotlightCounter()
|
||||||
|
|
||||||
|
//clearKnownPeripheralUUIDs
|
||||||
|
|
||||||
@State private var isConnected = false
|
@State private var isConnected = false
|
||||||
|
//@State private var switchedView = false
|
||||||
@State private var errorOccured = false
|
@State private var errorOccured = false
|
||||||
|
@State private var downloadState = DownloadState.idle
|
||||||
@State var notExpanded = false
|
|
||||||
@State var isExpanded = true
|
|
||||||
|
|
||||||
@State private var scrollViewID = UUID()
|
@State private var scrollViewID = UUID()
|
||||||
|
@State var currentHightlight: Int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@State private var activeAlert: ActiveAlert?
|
@State private var activeAlert: ActiveAlert?
|
||||||
|
@State private var internetAlert = false
|
||||||
|
@State private var showAlert1 = false
|
||||||
|
|
||||||
|
|
||||||
@State private var boardBootInfo = ""
|
@State private var boardBootInfo = ""
|
||||||
|
|
||||||
|
|
||||||
@State private var inConnectedInSelectionView = true
|
@State private var inConnectedInSelectionView = true
|
||||||
|
|
||||||
@AppStorage("shouldShowOnboarding123") var switchedView: Bool = false
|
@AppStorage("shouldShowOnboarding123") var switchedView: Bool = false
|
||||||
|
|
||||||
@State var subviewHeight : CGFloat = 0
|
|
||||||
|
|
||||||
func showConfirmationPrompt() {
|
|
||||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
|
||||||
connectionManager.isDisconnectingFromCurrent = true
|
|
||||||
} cancel: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
var connectedPeripherals = connectionManager.peripherals.filter{$0.state == .connected }
|
||||||
|
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
//Start
|
//Start
|
||||||
|
|
||||||
|
|
@ -69,7 +78,6 @@ struct BleModuleView: View {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("bluetooth")
|
Image("bluetooth")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
|
|
@ -170,139 +178,68 @@ struct BleModuleView: View {
|
||||||
|
|
||||||
HeaderView()
|
HeaderView()
|
||||||
|
|
||||||
|
// Button {
|
||||||
|
// print("Disconnect")
|
||||||
|
//
|
||||||
|
// activeAlert = .confirmUnpair(blePeripheral: connectedPeripherals[0])
|
||||||
|
// connectedPeripherals = []
|
||||||
|
//
|
||||||
|
// connectionManager.isConnectedOrReconnecting = false
|
||||||
|
// FileTransferConnectionManager.shared
|
||||||
|
// connectionManager.selectedPeripheral = nil
|
||||||
|
// connectionManager.isAnyPeripheralConnecting = false
|
||||||
|
// connectionManager.isSelectedPeripheralReconnecting = false
|
||||||
|
// // connectionManager.clearAllPeripheralInfo()
|
||||||
|
// rootViewModel.goToMain()
|
||||||
|
//
|
||||||
|
// print("Destination: \(rootViewModel.destination)")
|
||||||
|
// } label: {
|
||||||
|
// Text("Disconnection")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Sub-Header
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if boardBootInfo == "circuitplayground_bluefruit" {
|
if boardBootInfo == "circuitplayground_bluefruit" {
|
||||||
HStack {
|
Text("Connected to Circuit Playground Bluefruit")
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
Image("bluetoothLogo")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
|
|
||||||
|
|
||||||
Text("Circuit Playground Bluefruit.")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
showConfirmationPrompt()
|
|
||||||
} label: {
|
|
||||||
Text("Disconnect")
|
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 14))
|
|
||||||
.underline()
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if boardBootInfo == "clue_nrf52840_express" {
|
if boardBootInfo == "clue_nrf52840_express" {
|
||||||
VStack {
|
Text("Connected to Adafruit CLUE")
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
|
||||||
HStack {
|
|
||||||
Image("bluetoothLogo")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
|
|
||||||
Text("Adafruit CLUE.")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
|
||||||
|
|
||||||
Button {
|
|
||||||
showConfirmationPrompt()
|
|
||||||
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Text("Disconnect")
|
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 14))
|
|
||||||
.underline()
|
|
||||||
//.minimumScaleFactor(0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// Expandable
|
|
||||||
VStack {
|
|
||||||
Text("More Info")
|
|
||||||
}
|
|
||||||
.background(GeometryReader {
|
|
||||||
Color.clear.preference(key: ViewHeightKey.self,
|
|
||||||
value: $0.frame(in: .local).size.height)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.onPreferenceChange(ViewHeightKey.self) { subviewHeight = $0 }
|
|
||||||
.frame(height: isExpanded ? subviewHeight : 50, alignment: .top)
|
|
||||||
|
|
||||||
.clipped()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.transition(.move(edge: .bottom))
|
|
||||||
|
|
||||||
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation(.easeIn(duration: 0.5)) {
|
|
||||||
isExpanded.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(maxHeight: 40)
|
.frame(maxHeight: 40)
|
||||||
.background(Color("adafruit_blue"))
|
.background(Color("pyleap_green"))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
|
|
||||||
ScrollViewReader { scroll in
|
ScrollViewReader { scroll in
|
||||||
|
|
||||||
if boardBootInfo == "clue_nrf52840_express" {
|
MainSubHeaderView()
|
||||||
MainSubHeaderView(device: "Adafruit CLUE")
|
// .spotlight(enabled: spotlight.counter == 1, title: "1")
|
||||||
|
|
||||||
|
let check = networkServiceModel.pdemos.filter {
|
||||||
|
$0.compatibility[0] == boardBootInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
if boardBootInfo == "circuitplayground_bluefruit" {
|
|
||||||
MainSubHeaderView(device: "Circuit Playground")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let check = viewModel.pdemos.filter {
|
|
||||||
$0.compatibility.contains(boardBootInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ForEach(check) { demo in
|
ForEach(check) { demo in
|
||||||
|
|
||||||
if demo.bundleLink == expandedState.currentCell {
|
|
||||||
|
|
||||||
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
})
|
|
||||||
.onAppear(){
|
|
||||||
print("Cell Appeared")
|
|
||||||
withAnimation {
|
|
||||||
scroll.scrollTo(demo.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DemoViewCell(result: demo, isConnected: $inConnectedInSelectionView, bootOne: $boardBootInfo, onViewGeometryChanged: {
|
||||||
|
withAnimation {
|
||||||
|
scroll.scrollTo(demo.id)
|
||||||
}
|
}
|
||||||
|
}, stateBinder: $downloadState)
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,38 +247,143 @@ struct BleModuleView: View {
|
||||||
|
|
||||||
.id(self.scrollViewID)
|
.id(self.scrollViewID)
|
||||||
}
|
}
|
||||||
.environmentObject(expandedState)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.alert("Cannot Write To Device", isPresented: $errorOccured) {
|
||||||
|
Button("OK") {
|
||||||
|
// Handle acknowledgement.
|
||||||
|
print("OK")
|
||||||
|
errorOccured = false
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("""
|
||||||
|
Unplug device from computer and use external power source.
|
||||||
|
|
||||||
|
Then press RESET on device to continue.
|
||||||
|
""")
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.onTapGesture {
|
||||||
|
|
||||||
|
print("\(networkServiceModel.pdemos.count)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color.white)
|
.background(Color.white)
|
||||||
|
.environmentObject(globalString)
|
||||||
|
|
||||||
|
.onChange(of: viewModel.isConnectedToInternet, perform: { newValue in
|
||||||
|
|
||||||
|
if newValue {
|
||||||
|
internetAlert = false
|
||||||
|
} else {
|
||||||
|
internetAlert = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.modifier(Alerts(activeAlert: $activeAlert))
|
||||||
|
|
||||||
|
.onChange(of: viewModel.state, perform: { newValue in
|
||||||
|
print("State: \(newValue)")
|
||||||
|
downloadState = newValue
|
||||||
|
print("State Change: \(newValue )")
|
||||||
|
if newValue == .failed {
|
||||||
|
print("Failed Value")
|
||||||
|
errorOccured = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.writeError, perform: { newValue in
|
||||||
|
print("Change happened! \(newValue)")
|
||||||
|
|
||||||
|
globalString.bundleHasBeenDownloaded = newValue
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.sendingBundle, perform: { newValue in
|
||||||
|
globalString.isSendingG = newValue
|
||||||
|
if newValue {
|
||||||
|
print("Is transferring...")
|
||||||
|
} else {
|
||||||
|
print("Not transferring...")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.numOfFiles, perform: { newValue in
|
||||||
|
globalString.numberOfFilesG = newValue
|
||||||
|
print("NumOfFiles: \(newValue)")
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.counter, perform: { newValue in
|
||||||
|
globalString.counterG = newValue
|
||||||
|
print("Change for counterG happened: Value should be \(newValue)")
|
||||||
|
})
|
||||||
|
|
||||||
.onChange(of: viewModel.bootUpInfo, perform: { newValue in
|
.onChange(of: viewModel.bootUpInfo, perform: { newValue in
|
||||||
viewModel.readMyStatus()
|
viewModel.readMyStatus()
|
||||||
|
|
||||||
print("newValue \(newValue)")
|
print("newValue \(newValue)")
|
||||||
boardBootInfo = newValue
|
boardBootInfo = newValue
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.onChange(of: globalString.projectString, perform: { newValue in
|
||||||
|
print("Start Transfer")
|
||||||
|
// viewModel.getProjectURL(nameOf: newValue)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: globalString.attemptToDownload, perform: { newValue in
|
||||||
|
print("Start Download Process\(globalString.downloadLinkString) - \(globalString.projectString)")
|
||||||
|
downloadModel.startDownload(urlString: globalString.downloadLinkString, projectTitle: globalString.projectString)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: globalString.attemptToSend, perform: { newValue in
|
||||||
|
|
||||||
|
viewModel.getProjectURL(nameOf: globalString.projectString)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: downloadModel.attemptToSendBunle, perform: { newValue in
|
||||||
|
print("Attempting transfer of: \(globalString.projectString)")
|
||||||
|
viewModel.getProjectURL(nameOf: globalString.projectString)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: connectionManager.selectedClient) { selectedClient in
|
.onChange(of: connectionManager.selectedClient) { selectedClient in
|
||||||
viewModel.setup(fileTransferClient: selectedClient)
|
viewModel.setup(fileTransferClient: selectedClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
print("Opened BleModuleView")
|
|
||||||
// networkServiceModel.fetch()
|
|
||||||
|
|
||||||
viewModel.setup(fileTransferClient:connectionManager.selectedClient)
|
|
||||||
|
|
||||||
connectionManager.isSelectedPeripheralReconnecting = true
|
|
||||||
|
|
||||||
|
print("On Appear")
|
||||||
|
networkServiceModel.fetch()
|
||||||
|
viewModel.setup(fileTransferClient: connectionManager.selectedClient)
|
||||||
viewModel.readFile(filename: "boot_out.txt")
|
viewModel.readFile(filename: "boot_out.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ButtonStatus: CaseIterable, Identifiable {
|
||||||
|
case download
|
||||||
|
case transfer
|
||||||
|
case complete
|
||||||
|
|
||||||
|
var id: String { return title }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .download: return "Download"
|
||||||
|
case .transfer: return "Transfer"
|
||||||
|
case .complete: return "Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct Alerts: ViewModifier {
|
struct Alerts: ViewModifier {
|
||||||
@Binding var activeAlert: ActiveAlert?
|
@Binding var activeAlert: ActiveAlert?
|
||||||
|
|
||||||
|
|
@ -364,9 +406,4 @@ struct BleModuleView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ViewHeightKey: PreferenceKey {
|
|
||||||
static var defaultValue: CGFloat { 0 }
|
|
||||||
static func reduce(value: inout Value, nextValue: () -> Value) {
|
|
||||||
value = value + nextValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,655 @@ import FileTransferClient
|
||||||
|
|
||||||
class BleModuleViewModel: ObservableObject {
|
class BleModuleViewModel: ObservableObject {
|
||||||
|
|
||||||
private weak var fileTransferClient: FileTransferClient?
|
@StateObject var globalString = GlobalString()
|
||||||
@StateObject var contentTransfer = BleContentTransfer()
|
|
||||||
|
|
||||||
|
private weak var fileTransferClient: FileTransferClient?
|
||||||
@Published var entries = [BlePeripheral.DirectoryEntry]()
|
@Published var entries = [BlePeripheral.DirectoryEntry]()
|
||||||
@Published var isTransmiting = false
|
@Published var isTransmiting = false
|
||||||
@Published var bootUpInfo = ""
|
@Published var bootUpInfo = ""
|
||||||
|
|
||||||
let dataStore = DataStore()
|
var projectDirectories: [URL] = []
|
||||||
|
@Published var sendingBundle = false
|
||||||
|
@Published var didCompleteTranfer = false
|
||||||
|
@Published var writeError = false
|
||||||
|
|
||||||
@Published var pdemos : [ResultItem] = []
|
|
||||||
|
|
||||||
init() {
|
@Published var counter = 0
|
||||||
pdemos = dataStore.loadDefaultList()
|
@Published var numOfFiles = 0
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Published var fileArray: [ContentFile] = []
|
||||||
|
@Published var contentList: [URLData] = []
|
||||||
|
|
||||||
|
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
|
||||||
|
var networkMonitor = NetworkMonitor()
|
||||||
|
static let shared = BleModuleViewModel()
|
||||||
|
|
||||||
|
@Published var isConnectedToInternet = false
|
||||||
|
@Published var showAlert = false
|
||||||
|
|
||||||
|
var downloadPhases: String = ""
|
||||||
|
|
||||||
|
|
||||||
|
@Published var state: DownloadState = .idle
|
||||||
|
|
||||||
|
|
||||||
enum ProjectViewError: LocalizedError {
|
enum ProjectViewError: LocalizedError {
|
||||||
case fileTransferUndefined
|
case fileTransferUndefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayErrorMessage() {
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.writeError = true
|
||||||
|
self.sendingBundle = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func internetMonitoring() {
|
||||||
|
|
||||||
|
networkMonitor.startMonitoring()
|
||||||
|
networkMonitor.monitor.pathUpdateHandler = { path in
|
||||||
|
if path.status == .satisfied {
|
||||||
|
print("Connected to internet.")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.showAlert = false
|
||||||
|
self.isConnectedToInternet = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("No connection.")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.showAlert = true
|
||||||
|
self.isConnectedToInternet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("isExpensive: \(path.isExpensive)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
internetMonitoring()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all files and dic. on Bluefruit device *Except boot_out.txt*
|
||||||
|
func removeAllFiles(){
|
||||||
|
self.listDirectoryCommand(path: "") { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
|
||||||
|
case .success(let contents):
|
||||||
|
|
||||||
|
for i in contents! where i.name != "boot_out.txt" {
|
||||||
|
self.deleteFileCommand(path: i.name) { deletionResult in
|
||||||
|
switch deletionResult {
|
||||||
|
case .success:
|
||||||
|
print("Successfully Deleted")
|
||||||
|
case .failure:
|
||||||
|
print("Failed to delete.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .failure:
|
||||||
|
print("No content listed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
- Find URL by name - send it to filesDownloaded
|
||||||
|
- Enumerate thru found URL
|
||||||
|
- Get collection of files and directories - then send URL to startFileTransfer
|
||||||
|
*/
|
||||||
|
|
||||||
|
func getProjectURL(nameOf project: String) {
|
||||||
|
print("getProjectURL called")
|
||||||
|
counter = 0
|
||||||
|
state = .transferring
|
||||||
|
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||||
|
// for case condition: Only process URLs
|
||||||
|
for case let fileURL as URL in enumerator {
|
||||||
|
|
||||||
|
do {
|
||||||
|
print("Starting a loop...")
|
||||||
|
|
||||||
|
if fileURL.lastPathComponent == project {
|
||||||
|
|
||||||
|
print("Searching for... \(project)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
print(#function)
|
||||||
|
print("Found \(project) project at this location...")
|
||||||
|
print("URL Path: \(fileURL.path)")
|
||||||
|
print("URL : \(fileURL)")
|
||||||
|
let newURL = URL(fileURLWithPath: fileURL.path, relativeTo: directoryPath)
|
||||||
|
print("URL: \(newURL)")
|
||||||
|
filesDownloaded(url: fileURL)
|
||||||
|
|
||||||
|
return
|
||||||
|
} catch { print(error, fileURL) }
|
||||||
|
} else {
|
||||||
|
|
||||||
|
print("Project was not found for...\(project)")
|
||||||
|
print("\(state)")
|
||||||
|
state = .idle
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func filesDownloaded(url: URL) {
|
||||||
|
print("filesDownloaded was called")
|
||||||
|
//Cycles through files and directories in File Manager Document Directory
|
||||||
|
fileArray.removeAll()
|
||||||
|
|
||||||
|
var files = [URL]()
|
||||||
|
// Returns a directory enumerator object that can be used to perform a deep enumeration of the directory at the specified URL.
|
||||||
|
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||||
|
// for case condition: Only process URLs
|
||||||
|
for case let fileURL as URL in enumerator {
|
||||||
|
|
||||||
|
do {
|
||||||
|
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey, .addedToDirectoryDateKey,.isDirectoryKey])
|
||||||
|
|
||||||
|
print("INCOMING FILE: \(fileURL.path)")
|
||||||
|
|
||||||
|
if fileURL.path.contains("adafruit-circuitpython-bundle-7.x-mpy") {
|
||||||
|
print("Removing adafruit-circuitpython-bundle-7.x-mpy: \(fileURL.path)")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
print("FILTERED INCOMING FILE: \(fileURL.path)")
|
||||||
|
contentList.append(.init(urlTitle: fileURL))
|
||||||
|
if fileAttributes.isRegularFile! {
|
||||||
|
|
||||||
|
files.append(fileURL)
|
||||||
|
|
||||||
|
let resources = try fileURL.resourceValues(forKeys:[.fileSizeKey])
|
||||||
|
let fileSize = resources.fileSize!
|
||||||
|
|
||||||
|
let addedFile = ContentFile(title: fileURL.lastPathComponent, fileSize: fileSize)
|
||||||
|
fileArray.append(addedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
let addedFile = ContentFile(title: fileURL.lastPathComponent, fileSize: 0 )
|
||||||
|
fileArray.append(addedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch { print(error, fileURL) }
|
||||||
|
}
|
||||||
|
|
||||||
|
startFileTransfer(url: url)
|
||||||
|
|
||||||
|
numOfFiles = files.count
|
||||||
|
print("Contents in URL \(fileArray.count)")
|
||||||
|
print("Number of Files in URL \(files.count)")
|
||||||
|
|
||||||
|
for i in contentList {
|
||||||
|
|
||||||
|
print("CL: \(i.urlTitle.pathComponents)")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentList.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func startFileTransfer(url: URL) {
|
||||||
|
print("Project Location: \(url)")
|
||||||
|
let localFileManager = FileManager()
|
||||||
|
let resourceKeys = Set<URLResourceKey>([.nameKey, .isDirectoryKey])
|
||||||
|
var fileURLs: [URL] = []
|
||||||
|
|
||||||
|
let dirEnumerator = localFileManager.enumerator(at: url, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles)!
|
||||||
|
|
||||||
|
for case let fileURL as URL in dirEnumerator {
|
||||||
|
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
|
||||||
|
let isDirectory = resourceValues.isDirectory,
|
||||||
|
let name = resourceValues.name
|
||||||
|
else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileURL.path.contains("adafruit-circuitpython-bundle-7.x-mpy") {
|
||||||
|
print("Removing adafruit-circuitpython-bundle-7.x-mpy: \(fileURL.path)")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if isDirectory {
|
||||||
|
print("Directories Found")
|
||||||
|
print(fileURL.lastPathComponent)
|
||||||
|
if name == "_extras" {
|
||||||
|
dirEnumerator.skipDescendants()
|
||||||
|
}
|
||||||
|
//adafruit-circuitpython-bundle
|
||||||
|
if fileURL.lastPathComponent.contains("adafruit-circuitpython-bundle") {
|
||||||
|
print("We got one!")
|
||||||
|
print("Bad file - \(fileURL)")
|
||||||
|
} else {
|
||||||
|
if fileURL.pathComponents.count > 12 {
|
||||||
|
print("File Path component count: \(fileURL.pathComponents.count)")
|
||||||
|
projectDirectories.append(fileURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("APPENDED: \(fileURL.path)")
|
||||||
|
fileURLs.append(fileURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
print("List of Directories")
|
||||||
|
for i in projectDirectories {
|
||||||
|
print("Directory: \(i.path)")
|
||||||
|
}
|
||||||
|
print("List of Files")
|
||||||
|
for i in fileURLs {
|
||||||
|
print("Files: \(i.path)")
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.sendingBundle = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
print("Current projectDirectories: \(projectDirectories[0])")
|
||||||
|
|
||||||
|
sortDirectory(dirList: projectDirectories, filesUrls: fileURLs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func sortDirectory(dirList: [URL], filesUrls: [URL]) {
|
||||||
|
|
||||||
|
print(#function)
|
||||||
|
//Creates a sorted list of directories
|
||||||
|
var tempDirectory = dirList.sorted(by: { $1.pathComponents.count > $0.pathComponents.count} )
|
||||||
|
print("Evaluating: \(String(describing: tempDirectory.first?.lastPathComponent))")
|
||||||
|
print("With Path: \(String(describing: tempDirectory.first?.path))")
|
||||||
|
|
||||||
|
print("Sorted Directory")
|
||||||
|
for i in tempDirectory{
|
||||||
|
|
||||||
|
print(i.lastPathComponent)
|
||||||
|
}
|
||||||
|
// If directories are not found, start transferring files over to directories.
|
||||||
|
if dirList.isEmpty {
|
||||||
|
print("No directories left in queue")
|
||||||
|
projectDirectories.removeAll()
|
||||||
|
self.transferFiles(files: filesUrls)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
guard let firstDirectory = tempDirectory.first else {
|
||||||
|
print("No directory exist here")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If lib/ directory is found in the project bundle, make a lib directory on client.
|
||||||
|
if firstDirectory.lastPathComponent == "lib" {
|
||||||
|
mkLibDir(libDirectory: firstDirectory, copiedDirectory: tempDirectory, filesUrl: filesUrls)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mkSubLibDir(subdirectory: firstDirectory, copiedDirectory: tempDirectory, filesURL: filesUrls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.projectDirectories.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make lib/ Directory
|
||||||
|
func mkLibDir(libDirectory: URL, copiedDirectory: [URL], filesUrl: [URL]) {
|
||||||
|
print(#function)
|
||||||
|
var temp = copiedDirectory
|
||||||
|
// print(temp)
|
||||||
|
|
||||||
|
print("mkLibDir list")
|
||||||
|
for i in temp {
|
||||||
|
print("\(i)")
|
||||||
|
}
|
||||||
|
|
||||||
|
listDirectoryCommand(path: "") { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
// Check that lib/ exist.
|
||||||
|
case .success(let contents):
|
||||||
|
print("ListDirCommand: \(String(describing: contents))")
|
||||||
|
|
||||||
|
if contents!.contains(where: { name in name.name == libDirectory.lastPathComponent}) {
|
||||||
|
print("lib directory exist")
|
||||||
|
|
||||||
|
temp.removeFirst()
|
||||||
|
self.sortDirectory(dirList: temp, filesUrls: filesUrl)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("lib directory does not exist")
|
||||||
|
print("XXXX mkLibDir")
|
||||||
|
var tempURL = libDirectory.pathComponents
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
print("FIXED PATHxx:\(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
self.makeDirectoryCommand(path: joined) { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
print("Success")
|
||||||
|
|
||||||
|
temp.removeFirst()
|
||||||
|
self.sortDirectory(dirList: temp, filesUrls: filesUrl)
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
print("Failed to create directory \(joined)")
|
||||||
|
temp.removeAll()
|
||||||
|
self.projectDirectories.removeAll()
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
print("Failure - mkLibDir")
|
||||||
|
temp.removeAll()
|
||||||
|
self.projectDirectories.removeAll()
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func mkSubLibDir(subdirectory: URL, copiedDirectory: [URL], filesURL: [URL]) {
|
||||||
|
print(#function)
|
||||||
|
var temp = copiedDirectory
|
||||||
|
|
||||||
|
print("List of Directories Currently in mkSubLibDir")
|
||||||
|
for i in temp {
|
||||||
|
|
||||||
|
print("\(i.path)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempURL = subdirectory.pathComponents
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
|
||||||
|
print("Modified Path top: \(joined)")
|
||||||
|
|
||||||
|
var pathDirectoryForListCommand = tempURL
|
||||||
|
pathDirectoryForListCommand.removeLast()
|
||||||
|
let pathDirectoryForListCommandJoined = pathDirectoryForListCommand.joined(separator: "/")
|
||||||
|
|
||||||
|
|
||||||
|
print("pathDirectoryForListCommandJoined: \(pathDirectoryForListCommandJoined)")
|
||||||
|
print("How its taken: \(pathDirectoryForListCommandJoined)/")
|
||||||
|
|
||||||
|
|
||||||
|
listDirectoryCommand(path: "\(pathDirectoryForListCommandJoined)/") { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
|
||||||
|
case .success(let contents):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if contents!.contains(where: { name in name.name == subdirectory.lastPathComponent}) {
|
||||||
|
print("FULL PATH OF: \(subdirectory.lastPathComponent)")
|
||||||
|
print("\(subdirectory.path)")
|
||||||
|
// Skips the existing directory.
|
||||||
|
temp.removeFirst()
|
||||||
|
self.sortDirectory(dirList: temp, filesUrls: filesURL)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
print("\(subdirectory.lastPathComponent) directory does not exist")
|
||||||
|
print("Here's the full path of \(subdirectory.lastPathComponent): \(subdirectory.path)")
|
||||||
|
print("XXXX mkSubLibDir")
|
||||||
|
|
||||||
|
var tempURL = subdirectory.pathComponents
|
||||||
|
|
||||||
|
print("Incoming URL: \(tempURL)")
|
||||||
|
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
|
||||||
|
print("Modified Path without seperators: \(tempURL)")
|
||||||
|
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
|
||||||
|
print("Modified Path: \(joined)")
|
||||||
|
|
||||||
|
var pathDirectoryForListCommand = tempURL
|
||||||
|
pathDirectoryForListCommand.removeLast()
|
||||||
|
let pathDirectoryForListCommandJoined = pathDirectoryForListCommand.joined(separator: "/")
|
||||||
|
|
||||||
|
|
||||||
|
print("pathDirectoryForListCommandJoined: \(pathDirectoryForListCommandJoined)")
|
||||||
|
|
||||||
|
|
||||||
|
self.makeDirectoryCommand(path: joined) { result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
print("Success")
|
||||||
|
|
||||||
|
temp.removeFirst()
|
||||||
|
self.sortDirectory(dirList: temp, filesUrls: filesURL)
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
print("Failed to create directory - 2")
|
||||||
|
temp.removeAll()
|
||||||
|
self.projectDirectories.removeAll()
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
print("Fail in: \(#function)")
|
||||||
|
temp.removeAll()
|
||||||
|
self.projectDirectories.removeAll()
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func completedTransfer() {
|
||||||
|
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.didCompleteTranfer = true
|
||||||
|
self.numOfFiles = 0
|
||||||
|
self.counter = 0
|
||||||
|
self.state = .complete
|
||||||
|
|
||||||
|
}
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
self.didCompleteTranfer = false
|
||||||
|
self.state = .idle
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferFiles(files: [URL]) {
|
||||||
|
print(#function)
|
||||||
|
var copiedFiles = files
|
||||||
|
print("Number of files in filesArray \(files.count)")
|
||||||
|
print(files)
|
||||||
|
|
||||||
|
if files.isEmpty {
|
||||||
|
print("Array of contents empty - Check other directories")
|
||||||
|
self.completedTransfer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2){
|
||||||
|
self.sendingBundle = false
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
self.numOfFiles = 0
|
||||||
|
self.contentList.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
guard let selectedUrl = files.first else {
|
||||||
|
print("No such file exist here")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: selectedUrl.deletingPathExtension().lastPathComponent, relativeTo: selectedUrl).appendingPathExtension(selectedUrl.pathExtension)) else {
|
||||||
|
print("File not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedUrl.deletingLastPathComponent().lastPathComponent == "CircuitPython 7.x"{
|
||||||
|
|
||||||
|
print("Selected Path: \(selectedUrl.path)")
|
||||||
|
|
||||||
|
var tempURL = selectedUrl.pathComponents
|
||||||
|
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
|
||||||
|
|
||||||
|
var newModPath = tempURL
|
||||||
|
newModPath.removeLast()
|
||||||
|
print("Test file path: \(tempURL)")
|
||||||
|
|
||||||
|
print("File transfer modified path xx: \(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.writeFileCommand(path: joined, data: data) { result in
|
||||||
|
switch result {
|
||||||
|
|
||||||
|
case .success(_):
|
||||||
|
copiedFiles.removeFirst()
|
||||||
|
self.transferFiles(files: copiedFiles)
|
||||||
|
|
||||||
|
case .failure(_):
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
|
print("Transfer Failure")
|
||||||
|
print("\(joined)")
|
||||||
|
self.state = .failed
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
|
self.state = .idle
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
else if selectedUrl.deletingLastPathComponent().lastPathComponent == "lib" {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var tempURL = selectedUrl.pathComponents
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
print("File transfer modified path 11:\(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("Updated Path:\(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
writeFileCommand(path: joined, data: data) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(_):
|
||||||
|
copiedFiles.removeFirst()
|
||||||
|
self.transferFiles(files: copiedFiles)
|
||||||
|
|
||||||
|
case .failure(_):
|
||||||
|
print("Transfer Failure - 2")
|
||||||
|
self.state = .failed
|
||||||
|
self.displayErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if selectedUrl.lastPathComponent == "README.txt" {
|
||||||
|
print("Got one")
|
||||||
|
copiedFiles.removeFirst()
|
||||||
|
self.transferFiles(files: copiedFiles)
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var tempURL = selectedUrl.pathComponents
|
||||||
|
|
||||||
|
tempURL.removeFirst(12)
|
||||||
|
let joined = tempURL.joined(separator: "/")
|
||||||
|
print("File transfer modified path: \(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("Updated Path:\(joined)")
|
||||||
|
|
||||||
|
|
||||||
|
writeFileCommand(path: joined, data: data) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(_):
|
||||||
|
copiedFiles.removeFirst()
|
||||||
|
self.transferFiles(files: copiedFiles)
|
||||||
|
case .failure(let error):
|
||||||
|
print("Failed: \(error): \(result)")
|
||||||
|
// self.displayErrorMessage()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.sendingBundle = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func readMyStatus() {
|
func readMyStatus() {
|
||||||
|
// model.readFile(filename: "boot_out.txt")
|
||||||
|
print(#function)
|
||||||
|
|
||||||
print("BOOT INFO: \(bootUpInfo)")
|
print("BOOT INFO: \(bootUpInfo)")
|
||||||
|
|
||||||
|
|
@ -41,21 +668,29 @@ class BleModuleViewModel: ObservableObject {
|
||||||
case let str where str.contains("circuitplayground_bluefruit"):
|
case let str where str.contains("circuitplayground_bluefruit"):
|
||||||
print("Circuit Playground Bluefruit device")
|
print("Circuit Playground Bluefruit device")
|
||||||
bootUpInfo = "circuitplayground_bluefruit"
|
bootUpInfo = "circuitplayground_bluefruit"
|
||||||
|
// DispatchQueue.main.async { [self] in
|
||||||
|
// self.globalString.compatibilityString = "circuitplayground_bluefruit"
|
||||||
|
// }
|
||||||
case let str where str.contains("clue_nrf52840_express"):
|
case let str where str.contains("clue_nrf52840_express"):
|
||||||
print("Clue device")
|
print("Clue device")
|
||||||
bootUpInfo = "clue_nrf52840_express"
|
bootUpInfo = "clue_nrf52840_express"
|
||||||
|
// DispatchQueue.main.async { [self] in
|
||||||
|
// globalString.compatibilityString = "clue_nrf52840_express"
|
||||||
|
//
|
||||||
|
// }
|
||||||
default:
|
default:
|
||||||
print("Unknown Device")
|
print("Unknown Device")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func readBoardForCircuitPythonVersion() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: System
|
// MARK: System
|
||||||
|
|
||||||
struct TransmissionProgress {
|
struct TransmissionProgress {
|
||||||
|
|
@ -70,10 +705,6 @@ class BleModuleViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var transmissionProgress: TransmissionProgress?
|
@Published var transmissionProgress: TransmissionProgress?
|
||||||
@Published var lastTransmit: TransmissionLog? = TransmissionLog(type: .write(size: 334))
|
|
||||||
@Published var activeAlert: ActiveAlert?
|
|
||||||
// Data
|
|
||||||
private let bleManager = BleManager.shared
|
|
||||||
|
|
||||||
struct TransmissionLog: Equatable {
|
struct TransmissionLog: Equatable {
|
||||||
enum TransmissionType: Equatable {
|
enum TransmissionType: Equatable {
|
||||||
|
|
@ -100,6 +731,15 @@ class BleModuleViewModel: ObservableObject {
|
||||||
return modeText
|
return modeText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Published var lastTransmit: TransmissionLog? = TransmissionLog(type: .write(size: 334))
|
||||||
|
|
||||||
|
|
||||||
|
@Published var activeAlert: ActiveAlert?
|
||||||
|
|
||||||
|
// Data
|
||||||
|
private let bleManager = BleManager.shared
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
func onAppear() {
|
func onAppear() {
|
||||||
|
|
@ -133,10 +773,8 @@ class BleModuleViewModel: ObservableObject {
|
||||||
case .success(let data):
|
case .success(let data):
|
||||||
self.lastTransmit = TransmissionLog(type: .read(data: data))
|
self.lastTransmit = TransmissionLog(type: .read(data: data))
|
||||||
let str = String(decoding: data, as: UTF8.self)
|
let str = String(decoding: data, as: UTF8.self)
|
||||||
|
|
||||||
print("Read: \(str)")
|
print("Read: \(str)")
|
||||||
self.bootUpInfo = str
|
self.bootUpInfo = str
|
||||||
sharedBootinfo = str
|
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
|
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
|
||||||
|
|
@ -268,6 +906,10 @@ class BleModuleViewModel: ObservableObject {
|
||||||
private func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
|
private func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
|
||||||
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.counter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DLog("start writeFile \(path)")
|
DLog("start writeFile \(path)")
|
||||||
fileTransferClient.writeFile(path: path, data: data, progress: { [weak self] written, total in
|
fileTransferClient.writeFile(path: path, data: data, progress: { [weak self] written, total in
|
||||||
|
|
@ -348,8 +990,6 @@ class BleModuleViewModel: ObservableObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var sharedBootinfo = ""
|
|
||||||
|
|
||||||
enum ActiveAlert: Identifiable {
|
enum ActiveAlert: Identifiable {
|
||||||
case error(error: Error)
|
case error(error: Error)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,83 +8,76 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalString: ObservableObject {
|
||||||
|
@Published var projectString = ""
|
||||||
|
@Published var downloadLinkString = ""
|
||||||
|
@Published var compatibilityString = ""
|
||||||
|
|
||||||
|
|
||||||
|
@Published var counterG = 0
|
||||||
|
@Published var numberOfFilesG = 0
|
||||||
|
@Published var isSendingG = false
|
||||||
|
@Published var bundleHasBeenDownloaded = false
|
||||||
|
@Published var numberOfTimesDownloaded = 0
|
||||||
|
@Published var attemptToDownload = false
|
||||||
|
@Published var attemptToSend = false
|
||||||
|
}
|
||||||
|
|
||||||
struct DemoSubview: View {
|
struct DemoSubview: View {
|
||||||
|
@State var transferInProgress = false
|
||||||
|
@State var isDownloaded = false
|
||||||
|
|
||||||
|
@EnvironmentObject var globalString : GlobalString
|
||||||
|
|
||||||
@Binding var bindingString: String
|
@Binding var bindingString: String
|
||||||
|
|
||||||
let result: ResultItem
|
@Binding var downloadStateBinder: DownloadState
|
||||||
|
|
||||||
|
@State private var toggleView: Bool = false
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let image: String
|
||||||
|
let description: String
|
||||||
|
let learnGuideLink: URLRequest
|
||||||
|
let downloadLink: String
|
||||||
|
let compatibility: [String]
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
@StateObject var downloadModel = DownloadViewModel()
|
||||||
@StateObject var viewModel = SubCellViewModel()
|
@StateObject var viewModel = SubCellViewModel()
|
||||||
|
@StateObject var selectionModel = BleModuleViewModel()
|
||||||
@StateObject var contentTransfer = BleContentTransfer()
|
@StateObject var contentTransfer = BleContentTransfer()
|
||||||
|
|
||||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
|
||||||
|
|
||||||
@Binding var isConnected : Bool
|
@Binding var isConnected : Bool
|
||||||
|
|
||||||
@State private var showWebViewPopover: Bool = false
|
@State private var showWebViewPopover: Bool = false
|
||||||
|
@State var errorOccured = false
|
||||||
|
@State private var presentAlert = false
|
||||||
|
|
||||||
func showAlertMessage() {
|
@State var offlineWithoutProject = false
|
||||||
alertMessage(title: """
|
|
||||||
There's a problem with your internet connection.
|
|
||||||
Try again later.
|
|
||||||
""", exitTitle: "Ok") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showTransferErrorMessage() {
|
|
||||||
alertMessage(title: """
|
|
||||||
Transfer Failed
|
|
||||||
|
|
||||||
Disconnect device from the computer.
|
|
||||||
|
|
||||||
Press "Reset" on the device and use a battery source.
|
|
||||||
""", exitTitle: "Retry") {
|
|
||||||
contentTransfer.transferError = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDownloadErrorMessage() {
|
|
||||||
alertMessage(title: """
|
|
||||||
Server Error
|
|
||||||
|
|
||||||
This project can not be downloaded at this time
|
|
||||||
|
|
||||||
Try again later
|
|
||||||
""", exitTitle: "Ok") {
|
|
||||||
contentTransfer.downloaderror = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
if viewModel.projectDownloaded {
|
|
||||||
|
|
||||||
// HStack {
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// Text("Downloaded")
|
|
||||||
// .foregroundColor(.green)
|
|
||||||
// .padding(.trailing, -15)
|
|
||||||
// Circle()
|
|
||||||
// .fill(.green)
|
|
||||||
// .frame(width: 15, height: 15)
|
|
||||||
// .padding()
|
|
||||||
// }
|
|
||||||
// .padding(.vertical, -8)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0, content: {
|
VStack(alignment: .leading, spacing: 0, content: {
|
||||||
|
|
||||||
ImageWithURL(result.projectImage)
|
ImageWithURL(image)
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
|
||||||
Text(result.description)
|
Text(description)
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
.font(Font.custom("ReadexPro-Regular", size: 18))
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.minimumScaleFactor(0.1)
|
.minimumScaleFactor(0.1)
|
||||||
|
|
@ -94,7 +87,7 @@ Try again later
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
|
||||||
ForEach(result.compatibility, id: \.self) { string in
|
ForEach(compatibility, id: \.self) { string in
|
||||||
if string == "circuitplayground_bluefruit" {
|
if string == "circuitplayground_bluefruit" {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -130,19 +123,14 @@ Try again later
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 30)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if !viewModel.isConnectedToInternet {
|
showWebViewPopover = true
|
||||||
showAlertMessage()
|
|
||||||
} else {
|
|
||||||
showWebViewPopover = true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
LearnGuideButton()
|
LearnGuideButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showWebViewPopover, content: {
|
.sheet(isPresented: $showWebViewPopover, content: {
|
||||||
SwiftUIWebView(webAddress: result.learnGuideLink)
|
WebView(URLRequest(url: learnGuideLink.url!))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -150,72 +138,123 @@ Try again later
|
||||||
|
|
||||||
if isConnected {
|
if isConnected {
|
||||||
|
|
||||||
if result.compatibility.contains(bindingString) {
|
if compatibility.contains(bindingString) {
|
||||||
|
|
||||||
|
|
||||||
|
Button {
|
||||||
|
contentTransfer.getProjectURL(nameOf: title)
|
||||||
|
} label: {
|
||||||
|
Text("XXX")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if downloadStateBinder == .idle {
|
||||||
|
|
||||||
// Button {
|
|
||||||
// viewModel.deleteStoredFilesInFM()
|
|
||||||
// } label: {
|
|
||||||
// Text("Delete File Manager Contents")
|
|
||||||
// .bold()
|
|
||||||
// .padding(12)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if contentTransfer.downloadState == .idle {
|
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
|
||||||
/// Condition: Connected to the internet
|
downloadStateBinder = .transferring
|
||||||
///- If you're not connected to the internet, but you've downloaded the project...
|
globalString.isSendingG = true
|
||||||
/// - If you're not connected to the internet, and you're project is not downloaded...
|
globalString.counterG = 0
|
||||||
/// *Show Alert*
|
globalString.numberOfFilesG = 1
|
||||||
|
|
||||||
|
|
||||||
|
globalString.downloadLinkString = downloadLink
|
||||||
|
globalString.projectString = title
|
||||||
|
globalString.attemptToDownload.toggle()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if selectionModel.isConnectedToInternet == false {
|
||||||
|
print("Going offline...")
|
||||||
|
downloadStateBinder = .transferring
|
||||||
|
|
||||||
|
globalString.projectString = title
|
||||||
|
globalString.attemptToSend.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.projectDownloaded == false && selectionModel.isConnectedToInternet == false {
|
||||||
|
offlineWithoutProject = true
|
||||||
|
downloadStateBinder = .idle
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.projectDownloaded == false {
|
||||||
|
|
||||||
if viewModel.projectDownloaded == false && viewModel.isConnectedToInternet == false {
|
|
||||||
showAlertMessage()
|
|
||||||
} else {
|
|
||||||
contentTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
RunItButton()
|
RunItButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentTransfer.downloadState == .failed {
|
if downloadStateBinder == .failed {
|
||||||
|
|
||||||
FailedButton()
|
FailedButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentTransfer.downloadState == .transferring {
|
|
||||||
DownloadingButton()
|
|
||||||
.padding(.top, 20)
|
|
||||||
.disabled(true)
|
|
||||||
|
|
||||||
VStack(alignment: .center, spacing: 5) {
|
if downloadStateBinder == .transferring {
|
||||||
ProgressView("", value: CGFloat(contentTransfer.counter), total: CGFloat(contentTransfer.numOfFiles) )
|
|
||||||
.padding(.horizontal, 90)
|
|
||||||
.padding(.top, -8)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
.accentColor(Color.gray)
|
|
||||||
.scaleEffect(x: 1, y: 2, anchor: .center)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.frame(height: 10)
|
|
||||||
|
|
||||||
ProgressView()
|
Button(action: {
|
||||||
|
|
||||||
|
print("Project Selected: \(title) - DemoSubView")
|
||||||
|
|
||||||
|
globalString.projectString = title
|
||||||
|
globalString.numberOfTimesDownloaded += 1
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
|
DownloadingButton()
|
||||||
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
.disabled(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if globalString.isSendingG {
|
||||||
|
|
||||||
|
VStack(alignment: .center, spacing: 0) {
|
||||||
|
ProgressView("", value: CGFloat(globalString.counterG), total: CGFloat(globalString.numberOfFilesG) )
|
||||||
|
.padding(.horizontal, 90)
|
||||||
|
.padding(.top, -8)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
.accentColor(Color.gray)
|
||||||
|
.scaleEffect(x: 1, y: 2, anchor: .center)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.frame(height: 10)
|
||||||
|
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentTransfer.downloadState == .complete {
|
|
||||||
|
|
||||||
|
if downloadStateBinder == .complete {
|
||||||
|
|
||||||
CompleteButton()
|
CompleteButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
rootViewModel.goToSelection()
|
rootViewModel.goTobluetoothPairing()
|
||||||
} label: {
|
} label: {
|
||||||
ConnectButton()
|
ConnectButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
@ -225,48 +264,58 @@ Try again later
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
.onAppear(){
|
|
||||||
|
|
||||||
print("On Appear")
|
|
||||||
contentTransfer.contentCommands.setup(fileTransferClient: connectionManager.selectedClient)
|
|
||||||
|
|
||||||
// viewModel.readFile(filename: "boot_out.txt")
|
.alert("Project Not Found", isPresented: $offlineWithoutProject) {
|
||||||
|
Button("OK") {
|
||||||
|
// Handle acknowledgement.
|
||||||
|
print("OK")
|
||||||
|
offlineWithoutProject = false
|
||||||
|
downloadStateBinder = .idle
|
||||||
|
selectionModel.state = .idle
|
||||||
|
print("\(offlineWithoutProject)")
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("""
|
||||||
|
To use this project, connect to the internet.
|
||||||
|
""")
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
.onChange(of: downloadModel.isDownloading, perform: { newValue in
|
||||||
|
viewModel.getProjectForSubClass(nameOf: title)
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
|
||||||
|
print("For project: \(title), project download is \(newValue)")
|
||||||
|
|
||||||
|
globalString.projectString = title
|
||||||
|
|
||||||
|
if newValue {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
print("Getting project from Subclass \(title)")
|
||||||
|
viewModel.getProjectForSubClass(nameOf: title)
|
||||||
|
isDownloaded = true
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
print("Is not downloaded")
|
||||||
|
isDownloaded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
.onChange(of: contentTransfer.transferError, perform: { newValue in
|
})
|
||||||
if newValue {
|
.onAppear(perform: {
|
||||||
showTransferErrorMessage()
|
viewModel.getProjectForSubClass(nameOf: title)
|
||||||
}
|
if viewModel.projectDownloaded {
|
||||||
})
|
isDownloaded = true
|
||||||
|
} else {
|
||||||
.onChange(of: contentTransfer.downloaderror, perform: { newValue in
|
isDownloaded = false
|
||||||
if newValue {
|
|
||||||
showDownloadErrorMessage()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// .onChange(of: connectionManager.selectedClient) { selectedClient in
|
|
||||||
// viewModel.setup(fileTransferClient: selectedClient)
|
|
||||||
// }
|
|
||||||
|
|
||||||
.onAppear(perform: {
|
|
||||||
|
|
||||||
contentTransfer.readMyStatus()
|
|
||||||
viewModel.searchPathForProject(nameOf: result.projectName)
|
|
||||||
|
|
||||||
if viewModel.projectDownloaded {
|
|
||||||
viewModel.projectDownloaded = true
|
|
||||||
} else {
|
|
||||||
viewModel.projectDownloaded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
print("is downloaded? \(isDownloaded)")
|
||||||
|
})
|
||||||
|
.padding(.top, 8)
|
||||||
|
|
||||||
)
|
|
||||||
.padding(.top, 8)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,23 @@ import Foundation
|
||||||
|
|
||||||
struct DemoViewCell: View {
|
struct DemoViewCell: View {
|
||||||
|
|
||||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
|
||||||
|
|
||||||
|
@StateObject var spotlight = SpotlightCounter()
|
||||||
|
|
||||||
let result : ResultItem
|
let result : ResultItem
|
||||||
|
@State private var isExpanded: Bool = false {
|
||||||
@State var isExpanded: Bool = false {
|
|
||||||
didSet {
|
didSet {
|
||||||
onViewGeometryChanged()
|
onViewGeometryChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binding var isConnected: Bool
|
@Binding var isConnected: Bool
|
||||||
@Binding var deviceInfo: String
|
@Binding var bootOne: String
|
||||||
|
|
||||||
let onViewGeometryChanged: ()->Void
|
let onViewGeometryChanged: ()->Void
|
||||||
|
|
||||||
|
@Binding var stateBinder: DownloadState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
content
|
content
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
@ -33,10 +35,19 @@ struct DemoViewCell: View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
header
|
header
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
DemoSubview(bindingString: $deviceInfo, result: result, isConnected: $isConnected)
|
DemoSubview(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
|
||||||
|
image: result.projectImage,
|
||||||
|
description: result.description,
|
||||||
|
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
|
||||||
|
downloadLink: result.bundleLink,
|
||||||
|
compatibility: result.compatibility,
|
||||||
|
isConnected: $isConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -64,10 +75,7 @@ struct DemoViewCell: View {
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.background(Color("pyleap_purple"))
|
.background(Color("pyleap_purple"))
|
||||||
.onTapGesture {
|
.onTapGesture { isExpanded.toggle() }
|
||||||
expandedState.currentCell = result.bundleLink
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ struct ScrollRefreshableView<Content: View>: View {
|
||||||
var onRefresh: ()->()
|
var onRefresh: ()->()
|
||||||
|
|
||||||
|
|
||||||
init(title: String, tintColor: Color, @ViewBuilder content: @escaping () -> Content, onRefresh: @escaping () -> ()) {
|
init(title: String, tintColor: Color, @ViewBuilder content: @escaping ()->Content, onRefresh: @escaping ()->()) {
|
||||||
self.content = content()
|
self.content = content()
|
||||||
self.onRefresh = onRefresh
|
self.onRefresh = onRefresh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,74 +11,37 @@ class SubCellViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var projectDownloaded = false
|
@Published var projectDownloaded = false
|
||||||
@Published var failedProjectLaunch = false
|
@Published var failedProjectLaunch = false
|
||||||
@Published var isConnectedToInternet = false
|
|
||||||
|
|
||||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
var manager = FileManager.default
|
|
||||||
|
|
||||||
var networkMonitor = NetworkMonitor()
|
func getProjectForSubClass(nameOf project: String) {
|
||||||
|
|
||||||
init() {
|
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||||
internetMonitoring()
|
// for case condition: Only process URLs
|
||||||
}
|
for case let fileURL as URL in enumerator {
|
||||||
|
|
||||||
func deleteStoredFilesInFM () {
|
if fileURL.lastPathComponent == project {
|
||||||
print("\(#function) @Line: \(#line)")
|
failedProjectLaunch = false
|
||||||
do {
|
projectDownloaded = true
|
||||||
try manager.removeItem(at: directoryPath)
|
print(#function)
|
||||||
|
print("Searching for... \(project)")
|
||||||
|
print("URL Path: \(fileURL.path)")
|
||||||
|
print("URL : \(fileURL)")
|
||||||
|
|
||||||
} catch {
|
return
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func find(projectWith title: String) {
|
} else {
|
||||||
|
failedProjectLaunch = true
|
||||||
|
projectDownloaded = false
|
||||||
|
print("Project was not found...")
|
||||||
|
|
||||||
let nestedFolderURL = directoryPath.appendingPathComponent(title)
|
}
|
||||||
|
|
||||||
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
|
|
||||||
print("\(title) - Exists")
|
|
||||||
projectDownloaded = true
|
|
||||||
} else {
|
|
||||||
print("\(title) - Does not exist.")
|
|
||||||
projectDownloaded = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func internetMonitoring() {
|
|
||||||
|
|
||||||
networkMonitor.startMonitoring()
|
|
||||||
networkMonitor.monitor.pathUpdateHandler = { path in
|
|
||||||
if path.status == .satisfied {
|
|
||||||
print("Connected to internet.")
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
// self.showAlert = false
|
|
||||||
self.isConnectedToInternet = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("No connection.")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
// self.showAlert = true
|
|
||||||
self.isConnectedToInternet = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
print("isExpensive: \(path.isExpensive)")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func searchPathForProject(nameOf project: String) {
|
|
||||||
var manager = FileManager.default
|
|
||||||
|
|
||||||
let nestedFolderURL = directoryPath.appendingPathComponent(project)
|
|
||||||
|
|
||||||
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
|
|
||||||
print("\(project) - Exist")
|
|
||||||
projectDownloaded = true
|
|
||||||
} else {
|
|
||||||
print("Does not exist - \(project)")
|
|
||||||
projectDownloaded = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ import FileTransferClient
|
||||||
struct RootView: View {
|
struct RootView: View {
|
||||||
|
|
||||||
@StateObject private var model = RootViewModel()
|
@StateObject private var model = RootViewModel()
|
||||||
@StateObject var currentCellID = ExpandedState()
|
|
||||||
@StateObject var currentBLECellID = ExpandedBLECellState()
|
|
||||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
||||||
@AppStorage("onboarding") var onboardingSeen = true
|
@AppStorage("onboarding") var onboardingSeen = true
|
||||||
|
|
||||||
|
|
@ -21,7 +19,7 @@ struct RootView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
Group {
|
Group{
|
||||||
switch model.destination {
|
switch model.destination {
|
||||||
|
|
||||||
case .onboard :
|
case .onboard :
|
||||||
|
|
@ -45,35 +43,15 @@ struct RootView: View {
|
||||||
case .fileTransfer:
|
case .fileTransfer:
|
||||||
BleModuleView()
|
BleModuleView()
|
||||||
|
|
||||||
case .wifiServiceSelection:
|
|
||||||
WifiServiceSelectionView()
|
|
||||||
|
|
||||||
case .wifi:
|
case .wifi:
|
||||||
WifiView()
|
WifiView()
|
||||||
|
|
||||||
case .selection:
|
|
||||||
SelectionView()
|
|
||||||
|
|
||||||
case .wifiSelection:
|
|
||||||
WifiSelection()
|
|
||||||
|
|
||||||
case .wifiPairingTutorial:
|
|
||||||
WifiPairingView()
|
|
||||||
|
|
||||||
case .settings:
|
case .settings:
|
||||||
SettingsView()
|
SettingsView()
|
||||||
|
|
||||||
case .bleSettings:
|
|
||||||
BLESettingsView()
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
FillerView()
|
FillerView()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.environmentObject(currentCellID)
|
|
||||||
.environmentObject(currentBLECellID)
|
|
||||||
|
|
||||||
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
|
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
|
||||||
if !Config.isSimulatingBluetooth {
|
if !Config.isSimulatingBluetooth {
|
||||||
|
|
@ -97,20 +75,6 @@ struct RootView: View {
|
||||||
} else {
|
} else {
|
||||||
isReconnecting = false
|
isReconnecting = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.onChange(of: connectionManager.isDisconnectingFromCurrent) { isDisconnected in
|
|
||||||
|
|
||||||
if isDisconnected {
|
|
||||||
print("Is disconnected.")
|
|
||||||
isReconnecting = false
|
|
||||||
connectionManager.clearAllPeripheralInfo()
|
|
||||||
connectionManager.peripherals = []
|
|
||||||
connectionManager.isDisconnectingFromCurrent = false
|
|
||||||
model.destination = .selection
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,7 +82,6 @@ struct RootView: View {
|
||||||
DLog("App moving to the foreground. Force reconnect")
|
DLog("App moving to the foreground. Force reconnect")
|
||||||
FileTransferConnectionManager.shared.reconnect()
|
FileTransferConnectionManager.shared.reconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
.environmentObject(model)
|
.environmentObject(model)
|
||||||
.environmentObject(connectionManager)
|
.environmentObject(connectionManager)
|
||||||
.background(Color.white)
|
.background(Color.white)
|
||||||
|
|
@ -126,7 +89,6 @@ struct RootView: View {
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
.preferredColorScheme(.light)
|
.preferredColorScheme(.light)
|
||||||
.statusBar(hidden: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
public class RootViewModel: ObservableObject {
|
class RootViewModel: ObservableObject {
|
||||||
|
|
||||||
// public var shared = RootViewModel()
|
|
||||||
|
|
||||||
enum Destination {
|
enum Destination {
|
||||||
//case splash
|
//case splash
|
||||||
|
|
@ -22,13 +20,7 @@ public class RootViewModel: ObservableObject {
|
||||||
case fileTransfer
|
case fileTransfer
|
||||||
case wifi
|
case wifi
|
||||||
case settings
|
case settings
|
||||||
case bleSettings
|
|
||||||
case mainSelection
|
case mainSelection
|
||||||
case wifiSelection
|
|
||||||
case wifiPairingTutorial
|
|
||||||
case wifiServiceSelection
|
|
||||||
case selection
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup
|
@Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup
|
||||||
|
|
@ -38,18 +30,6 @@ public class RootViewModel: ObservableObject {
|
||||||
//destination = .test
|
//destination = .test
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToWiFiServiceSelection() {
|
|
||||||
destination = .wifiServiceSelection
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToWifiPairingTutorial() {
|
|
||||||
destination = .wifiPairingTutorial
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToWiFiSelection() {
|
|
||||||
destination = .wifiSelection
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToWifiView() {
|
func goToWifiView() {
|
||||||
destination = .wifi
|
destination = .wifi
|
||||||
}
|
}
|
||||||
|
|
@ -58,10 +38,6 @@ public class RootViewModel: ObservableObject {
|
||||||
destination = .bluetoothPairing
|
destination = .bluetoothPairing
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToSelection(){
|
|
||||||
destination = .selection
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToMainSelection(){
|
func goToMainSelection(){
|
||||||
destination = .mainSelection
|
destination = .mainSelection
|
||||||
}
|
}
|
||||||
|
|
@ -76,10 +52,6 @@ public class RootViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func backToMain() {
|
|
||||||
destination = .main
|
|
||||||
}
|
|
||||||
|
|
||||||
func goToStartup(){
|
func goToStartup(){
|
||||||
destination = .startup
|
destination = .startup
|
||||||
}
|
}
|
||||||
|
|
@ -92,14 +64,10 @@ public class RootViewModel: ObservableObject {
|
||||||
destination = .fileTransfer
|
destination = .fileTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToSettings(content: SettingState){
|
func goToSettings(){
|
||||||
destination = .settings
|
destination = .settings
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToBLESettings(){
|
|
||||||
destination = .bleSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func showWarningIfBluetoothStateIsNotReady() {
|
func showWarningIfBluetoothStateIsNotReady() {
|
||||||
let bluetoothState = BleManager.shared.state
|
let bluetoothState = BleManager.shared.state
|
||||||
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized
|
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized
|
||||||
|
|
|
||||||
317
PyLeap/Views/SettingsView/SettingsView.swift
Normal file
317
PyLeap/Views/SettingsView/SettingsView.swift
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
//
|
||||||
|
// SettingsView.swift
|
||||||
|
// PyLeap
|
||||||
|
//
|
||||||
|
// Created by Trevor Beaton on 9/7/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
|
||||||
|
|
||||||
|
@State private var jsonFileName: String = ""
|
||||||
|
@State private var pythonFileName: String = ""
|
||||||
|
|
||||||
|
@State private var presentJSONAlert = false
|
||||||
|
@State private var presentPythonAlert = false
|
||||||
|
|
||||||
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
@ObservedObject var networkModel = NetworkService()
|
||||||
|
@StateObject var viewModel = SettingsViewModel()
|
||||||
|
|
||||||
|
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func showInvalidURLEntryAlert() {
|
||||||
|
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showDownloadConfirmationAlert() {
|
||||||
|
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showDisconnectionPrompt() {
|
||||||
|
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
||||||
|
viewModel.clearKnownIPAddress()
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} cancel: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Button {
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} label: {
|
||||||
|
Text("Back")
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding(.all,20)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
//.frame(width: .infinity)
|
||||||
|
.frame(height: UIScreen.main.bounds.height / 19)
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
|
||||||
|
.padding(.all,20)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
|
||||||
|
Form {
|
||||||
|
|
||||||
|
VStack() {
|
||||||
|
|
||||||
|
if viewModel.connectedToDevice {
|
||||||
|
|
||||||
|
|
||||||
|
Section() {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("Host Name:")
|
||||||
|
.bold()
|
||||||
|
Text(viewModel.hostName)
|
||||||
|
Text("IP Address:")
|
||||||
|
.bold()
|
||||||
|
Text(viewModel.ipAddress)
|
||||||
|
Text("Device:")
|
||||||
|
.bold()
|
||||||
|
Text(viewModel.device)
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(.leading,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Section() {
|
||||||
|
Button {
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} label: {
|
||||||
|
Text("Connect to Adafruit Device")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.connectedToDevice {
|
||||||
|
Section() {
|
||||||
|
Button {
|
||||||
|
showDisconnectionPrompt()
|
||||||
|
} label: {
|
||||||
|
Text("Disconnect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: .constant(false)) {
|
||||||
|
Text("Dark Mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header: {
|
||||||
|
Text("Display")
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.connectedToDevice {
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Text("Enter project URL")
|
||||||
|
TextField("https://", text: $pythonFileName)
|
||||||
|
.keyboardType(.URL)
|
||||||
|
.textContentType(.URL)
|
||||||
|
.onSubmit {
|
||||||
|
NetworkService.shared.fetchThirdParyProject(urlString: pythonFileName)
|
||||||
|
print(pythonFileName)
|
||||||
|
pythonFileName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
} header: {
|
||||||
|
Text("Add Project")
|
||||||
|
}
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
|
||||||
|
|
||||||
|
Section {
|
||||||
|
|
||||||
|
Button("Create Python File"){
|
||||||
|
presentPythonAlert = true
|
||||||
|
}
|
||||||
|
.alert("Create Python File", isPresented: $presentPythonAlert, actions: {
|
||||||
|
|
||||||
|
TextField("", text: $pythonFileName)
|
||||||
|
|
||||||
|
Button("Add", action: {})
|
||||||
|
|
||||||
|
Button("Cancel", role: .cancel, action: {
|
||||||
|
presentPythonAlert = false
|
||||||
|
})
|
||||||
|
}, message: {
|
||||||
|
Text("Please enter your username and password.")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
Button("Create JSON File"){
|
||||||
|
presentJSONAlert = true
|
||||||
|
}
|
||||||
|
.alert("Create JSON File", isPresented: $presentJSONAlert, actions: {
|
||||||
|
|
||||||
|
TextField("", text: $jsonFileName)
|
||||||
|
|
||||||
|
Button("Add", action: {})
|
||||||
|
|
||||||
|
Button("Cancel", role: .cancel, action: {
|
||||||
|
presentJSONAlert = false
|
||||||
|
})
|
||||||
|
}, message: {
|
||||||
|
Text("Please enter your username and password.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
header: {
|
||||||
|
Text("Create")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Section{
|
||||||
|
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
|
||||||
|
}
|
||||||
|
.font(.system(size: 16, weight: .semibold))
|
||||||
|
|
||||||
|
}
|
||||||
|
.onChange(of: viewModel.invalidURL, perform: { newValue in
|
||||||
|
showInvalidURLEntryAlert()
|
||||||
|
viewModel.invalidURL = false
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.confirmDownload, perform: { newValue in
|
||||||
|
showDownloadConfirmationAlert()
|
||||||
|
viewModel.confirmDownload = false
|
||||||
|
})
|
||||||
|
|
||||||
|
.navigationTitle("Settings")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} label: {
|
||||||
|
Text("Back")
|
||||||
|
// .font(.system(size: 18, weight: .regular, design: .default))
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
.padding(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//.padding(.all, 11.0)
|
||||||
|
.background(Color(UIColor.systemGroupedBackground))
|
||||||
|
|
||||||
|
// .safeAreaInset(edge: .top) {
|
||||||
|
// HStack {
|
||||||
|
// Button {
|
||||||
|
// rootViewModel.goToWifiView()
|
||||||
|
// } label: {
|
||||||
|
// Text("Back")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// .padding(.all,20)
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
|
// // .background(Color(UIColor.systemGroupedBackground))
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
|
||||||
|
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||||||
|
|
||||||
|
alert.addAction(.init(title: primaryTitle, style: .destructive, handler: { _ in
|
||||||
|
disconnect()
|
||||||
|
}))
|
||||||
|
|
||||||
|
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
|
||||||
|
cancel()
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
rootController().present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertMessage(title: String, exitTitle: String, cancel: @escaping()->()){
|
||||||
|
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||||||
|
|
||||||
|
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
|
||||||
|
cancel()
|
||||||
|
}))
|
||||||
|
rootController().present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertTF(title: String, message: String, hintText: String, primaryTitle: String, secondaryTitle: String, primaryAction: @escaping(String)->(), secondaryAction: @escaping()->()) {
|
||||||
|
|
||||||
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
|
alert.addTextField { field in
|
||||||
|
field.keyboardType = .decimalPad
|
||||||
|
field.placeholder = hintText
|
||||||
|
|
||||||
|
}
|
||||||
|
alert.addAction(.init(title: secondaryTitle, style: .cancel, handler: { _ in
|
||||||
|
secondaryAction()
|
||||||
|
|
||||||
|
}))
|
||||||
|
alert.addAction(.init(title: primaryTitle, style: .default, handler: { _ in
|
||||||
|
if let text = alert.textFields?[0].text {
|
||||||
|
primaryAction(text)
|
||||||
|
} else {
|
||||||
|
primaryAction("")
|
||||||
|
}
|
||||||
|
|
||||||
|
}))
|
||||||
|
rootController().present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootController()->UIViewController{
|
||||||
|
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
|
||||||
|
return .init()
|
||||||
|
}
|
||||||
|
guard let root = screen.windows.first?.rootViewController else {
|
||||||
|
return .init()
|
||||||
|
}
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ class SettingsViewModel: ObservableObject {
|
||||||
@Published var invalidURL = false
|
@Published var invalidURL = false
|
||||||
@Published var confirmDownload = false
|
@Published var confirmDownload = false
|
||||||
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
check()
|
check()
|
||||||
registerNotifications(enabled: true)
|
registerNotifications(enabled: true)
|
||||||
|
|
@ -57,9 +56,10 @@ errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkReq
|
||||||
|
|
||||||
func check() {
|
func check() {
|
||||||
print(#function)
|
print(#function)
|
||||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
||||||
connectedToDevice = false
|
connectedToDevice = false
|
||||||
} else {
|
} else {
|
||||||
|
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||||
|
|
||||||
connectedToDevice = true
|
connectedToDevice = true
|
||||||
|
|
||||||
|
|
@ -16,16 +16,13 @@ struct BlinkaAnimationView: View {
|
||||||
.repeatForever(autoreverses: false)
|
.repeatForever(autoreverses: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var height: CGFloat
|
|
||||||
var width: CGFloat
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
Image("BlinkaLoading")
|
Image("BlinkaLoading")
|
||||||
.resizable(resizingMode: .stretch)
|
.resizable(resizingMode: .stretch)
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(width: width, height: height)
|
.frame(width: 300, height: 300)
|
||||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||||
.animation(self.isAnimating ? foreverAnimation : .default)
|
.animation(self.isAnimating ? foreverAnimation : .default)
|
||||||
|
|
||||||
|
|
@ -37,6 +34,6 @@ struct BlinkaAnimationView: View {
|
||||||
|
|
||||||
struct ScanningView_Previews: PreviewProvider {
|
struct ScanningView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
BlinkaAnimationView(height: 300, width: 300)
|
BlinkaAnimationView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ struct RunItButton: View {
|
||||||
.cornerRadius(25)
|
.cornerRadius(25)
|
||||||
.foregroundColor(Color("pyleap_pink"))
|
.foregroundColor(Color("pyleap_pink"))
|
||||||
|
|
||||||
Text("Run")
|
Text("Run it!")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
|
|
@ -35,7 +35,7 @@ struct PairingTutorialButton: View {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
.frame(width: 270, height: 50, alignment: .center)
|
||||||
.cornerRadius(25)
|
.cornerRadius(25)
|
||||||
.foregroundColor(Color("bluetooth_button_color"))
|
.foregroundColor(Color("pyleap_pink"))
|
||||||
|
|
||||||
Text("Pairing Tutorial")
|
Text("Pairing Tutorial")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
|
|
|
||||||
|
|
@ -10,44 +10,50 @@ import SwiftUI
|
||||||
struct HeaderView: View {
|
struct HeaderView: View {
|
||||||
@State var showSheetView = false
|
@State var showSheetView = false
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
|
||||||
HStack (alignment: .center, spacing: 0) {
|
Button {
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "wifi.circle")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
|
.offset(y: 15)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
Image(systemName: "gearshape")
|
}
|
||||||
.resizable()
|
.padding()
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: 15)
|
|
||||||
.padding(.leading, CGFloat(20))
|
|
||||||
.foregroundColor(.clear)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Image("pyleap_logo_white")
|
Spacer()
|
||||||
.resizable()
|
Image("pyleap_logo_white")
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 125, height: 125)
|
|
||||||
.offset(y: 12)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToBLESettings()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "plus")
|
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
.scaledToFit()
|
||||||
.offset(y: 15)
|
.frame(width: 125, height: 125)
|
||||||
.padding(.trailing, CGFloat(20))
|
.offset(y: 12)
|
||||||
.foregroundColor(.white)
|
// .padding(.leading, 60)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
self.showSheetView.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
|
.offset(y: 15)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}.sheet(isPresented: $showSheetView) {
|
||||||
|
CreditView(isPresented: $showSheetView)
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(maxHeight: 120)
|
.frame(maxHeight: 120)
|
||||||
.background(Color("pyleap_gray"))
|
.background(Color("pyleap_gray"))
|
||||||
|
|
@ -101,7 +107,6 @@ struct MainHeaderView: View {
|
||||||
|
|
||||||
|
|
||||||
struct HeaderView_Previews: PreviewProvider {
|
struct HeaderView_Previews: PreviewProvider {
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
HeaderView()
|
HeaderView()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ struct SubHeaderView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Text("Browse available WiFi PyLeap Projects")
|
Text("Browse available Wi-Fi PyLeap Projects")
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
|
|
@ -22,14 +22,10 @@ struct SubHeaderView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainSubHeaderView: View {
|
struct MainSubHeaderView: View {
|
||||||
let device: String
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Text("Pick a project to run on your \(device)")
|
Text("Browse available PyLeap Projects")
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct WifiHeaderView: View {
|
struct WifiHeaderView: View {
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
@State var showSheetView = false
|
@State var showSheetView = false
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
|
||||||
|
|
@ -16,38 +15,41 @@ struct WifiHeaderView: View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
rootViewModel.goToMain()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "arrow.backward")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
|
.offset(y: 15)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
|
||||||
HStack (alignment: .center, spacing: 0) {
|
.padding()
|
||||||
|
|
||||||
Image(systemName: "gearshape")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: 15)
|
|
||||||
.padding(.leading, CGFloat(20))
|
|
||||||
.foregroundColor(.clear)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("pyleap_logo_white")
|
Image("pyleap_logo_white")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 125, height: 125)
|
.frame(width: 125, height: 125)
|
||||||
.offset(y: 12)
|
.offset(y: 12)
|
||||||
|
// .padding(.leading, 60)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
rootViewModel.goToSettings(content: .wifi)
|
rootViewModel.goToSettings()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "gearshape")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
.offset(y: 15)
|
.offset(y: 15)
|
||||||
.padding(.trailing, CGFloat(20))
|
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,15 @@ enum AdafruitDevices {
|
||||||
case esp32s2
|
case esp32s2
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainSelectionView: View {
|
struct MainSelectionView: View {
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
@EnvironmentObject private var connectionManager: FileTransferConnectionManager
|
||||||
|
|
||||||
@State private var showWebViewPopover: Bool = false
|
@State private var showWebViewPopover: Bool = false
|
||||||
|
|
||||||
@State private var inConnectedInSelectionView = true
|
|
||||||
@State private var boardBootInfo = ""
|
|
||||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
|
||||||
|
|
||||||
|
@ObservedObject var networkModel = NetworkService()
|
||||||
@ObservedObject var viewModel = MainSelectionViewModel()
|
@ObservedObject var viewModel = MainSelectionViewModel()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ enum AdafruitDevices {
|
||||||
@State private var test = ""
|
@State private var test = ""
|
||||||
|
|
||||||
@State private var nilBinder = DownloadState.idle
|
@State private var nilBinder = DownloadState.idle
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
|
||||||
@AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
|
@AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
|
||||||
|
|
@ -39,11 +41,30 @@ enum AdafruitDevices {
|
||||||
VStack(alignment: .center, spacing: 0) {
|
VStack(alignment: .center, spacing: 0) {
|
||||||
MainHeaderView()
|
MainHeaderView()
|
||||||
|
|
||||||
|
|
||||||
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
rootViewModel.goToWifiView()
|
||||||
|
} label: {
|
||||||
|
Text("Connect to Wi-Fi Mode")
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
.underline()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.padding(.all, 0.0)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(maxHeight: 40)
|
||||||
|
.background(Color("pyleap_blue"))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 8, content: {
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
Text("Not Connected to a Device.")
|
Text("Not Connected to a Device.")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
Button {
|
Button {
|
||||||
rootViewModel.goToSelection()
|
rootViewModel.goTobluetoothPairing()
|
||||||
} label: {
|
} label: {
|
||||||
Text("Connect Now")
|
Text("Connect Now")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
|
@ -57,11 +78,14 @@ enum AdafruitDevices {
|
||||||
.background(Color("pyleap_burg"))
|
.background(Color("pyleap_burg"))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
||||||
MainSubHeaderView(device: "Adafruit device")
|
MainSubHeaderView()
|
||||||
|
|
||||||
if viewModel.pdemos.isEmpty {
|
if networkModel.pdemos.isEmpty {
|
||||||
HStack{
|
HStack{
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
|
@ -74,42 +98,18 @@ enum AdafruitDevices {
|
||||||
|
|
||||||
ScrollViewReader { scroll in
|
ScrollViewReader { scroll in
|
||||||
|
|
||||||
|
ForEach(networkModel.pdemos) { demo in
|
||||||
|
DemoViewCell(result: demo, isConnected: $isConnected, bootOne: $test, onViewGeometryChanged: {
|
||||||
|
withAnimation {
|
||||||
ForEach(viewModel.pdemos) { demo in
|
scroll.scrollTo(demo.id)
|
||||||
|
|
||||||
if demo.bundleLink == expandedState.currentCell {
|
|
||||||
|
|
||||||
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
})
|
|
||||||
.onAppear(){
|
|
||||||
print("Cell Appeared")
|
|
||||||
withAnimation {
|
|
||||||
scroll.scrollTo(demo.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}, stateBinder: $nilBinder)
|
||||||
} else {
|
|
||||||
|
|
||||||
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.onDisappear() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// **Pull down to Refresh feature**
|
/// **Pull down to Refresh feature**
|
||||||
// ScrollRefreshableView(title: "Refresh", tintColor: .purple) {
|
// ScrollRefreshableView(title: "Refresh", tintColor: .purple) {
|
||||||
|
|
@ -144,9 +144,7 @@ enum AdafruitDevices {
|
||||||
|
|
||||||
})
|
})
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
|
networkModel.fetch()
|
||||||
|
|
||||||
print("Opened MainSelectionView")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
|
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
|
||||||
|
|
@ -158,89 +156,12 @@ enum AdafruitDevices {
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct MainSelectionView_Previews: PreviewProvider {
|
|
||||||
// static var previews: some View {
|
|
||||||
// MainSelectionView()
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//struct MainSelectionView: View {
|
|
||||||
//
|
|
||||||
// @State private var showWebViewPopover: Bool = false
|
|
||||||
// @ObservedObject var networkModel = NetworkService()
|
|
||||||
// @ObservedObject var viewModel = MainSelectionViewModel()
|
|
||||||
// @State private var test = ""
|
|
||||||
// @State private var nilBinder = DownloadState.idle
|
|
||||||
// @EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
// @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
|
|
||||||
//
|
|
||||||
// var body: some View {
|
|
||||||
// VStack(alignment: .center, spacing: 0) {
|
|
||||||
// MainHeaderView()
|
|
||||||
// connectionMessageView()
|
|
||||||
// ScrollView {
|
|
||||||
// MainSubHeaderView(device: "Adafruit device")
|
|
||||||
// if networkModel.pdemos.isEmpty {
|
|
||||||
// loadingIndicatorView()
|
|
||||||
// }
|
|
||||||
// ScrollViewReader { scroll in
|
|
||||||
// ForEach(networkModel.pdemos) { demo in
|
|
||||||
// demoViewCell(demo: demo, scroll: scroll)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func connectionMessageView() -> some View {
|
|
||||||
// HStack(alignment: .center, spacing: 8) {
|
|
||||||
// Text("Not Connected to a Device.")
|
|
||||||
// .font(Font.custom("ReadexPro-Regular", size: 16))
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToSelection()
|
|
||||||
// } label: {
|
|
||||||
// Text("Connect Now")
|
|
||||||
// .font(Font.custom("ReadexPro-Regular", size: 16))
|
|
||||||
// .underline()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .padding(.all, 0.0)
|
|
||||||
// .frame(maxWidth: .infinity)
|
|
||||||
// .frame(maxHeight: 40)
|
|
||||||
// .background(Color("pyleap_burg"))
|
|
||||||
// .foregroundColor(.white)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func loadingIndicatorView() -> some View {
|
|
||||||
// HStack{
|
|
||||||
// Spacer()
|
|
||||||
// ProgressView()
|
|
||||||
// .scaleEffect(2)
|
|
||||||
// Spacer()
|
|
||||||
// }
|
|
||||||
// .padding(0)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func demoViewCell(demo: Demo, scroll: ScrollViewProxy) -> some View {
|
|
||||||
// if demo.bundleLink == expandedState.currentCell {
|
|
||||||
// DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
// })
|
|
||||||
// .onAppear(){
|
|
||||||
// print("Cell Appeared")
|
|
||||||
// withAnimation {
|
|
||||||
// scroll.scrollTo(demo.id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
|
struct MainSelectionView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
MainSelectionView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,82 +7,44 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Network
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
class InternetConnectionManager: ObservableObject {
|
|
||||||
|
|
||||||
private let monitor = NWPathMonitor()
|
|
||||||
private let queue = DispatchQueue(label: "InternetConnectionMonitor")
|
|
||||||
@Published var isConnected = false
|
|
||||||
|
|
||||||
init() {
|
|
||||||
|
|
||||||
startMonitoring(completion: {
|
|
||||||
monitor.pathUpdateHandler = { path in
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let newIsConnected = path.status == .satisfied
|
|
||||||
if self.isConnected != newIsConnected {
|
|
||||||
self.isConnected = newIsConnected
|
|
||||||
print("net: \(path.status) \(self.isConnected)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startMonitoring(completion:()->Void) {
|
|
||||||
print("Start Monitoring Network")
|
|
||||||
monitor.start(queue: queue)
|
|
||||||
completion()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
print("Network Deinit")
|
|
||||||
monitor.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainSelectionViewModel: ObservableObject {
|
class MainSelectionViewModel: ObservableObject {
|
||||||
|
|
||||||
@ObservedObject var networkModel = NetworkService()
|
@ObservedObject var networkModel = NetworkService()
|
||||||
@ObservedObject var networkMonitor = InternetConnectionManager()
|
|
||||||
|
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
let fileManager = FileManager.default
|
|
||||||
|
|
||||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
|
|
||||||
let dataStore = DataStore()
|
|
||||||
|
|
||||||
@Published var pdemos : [ResultItem] = []
|
@Published var pdemos : [ResultItem] = []
|
||||||
var networkMonitorCancellable: AnyCancellable?
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
// load()
|
||||||
|
// self.pdemos = load()
|
||||||
|
}
|
||||||
networkMonitorCancellable = networkMonitor.$isConnected.sink { isConnected in
|
|
||||||
if isConnected {
|
|
||||||
print("The device is currently connected to the internet.")
|
|
||||||
// Perform some action when the device is connected to the internet.
|
|
||||||
self.networkModel.fetch {
|
|
||||||
self.pdemos = self.dataStore.loadDefaultList()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("The device is not currently connected to the internet.")
|
|
||||||
// Perform some action when the device is not connected to the internet.
|
|
||||||
print("Loading cached remote data.")
|
|
||||||
self.pdemos = self.dataStore.loadDefaultList()
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func makeNetworkCall(){
|
||||||
|
networkModel.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func load() -> [ResultItem] {
|
||||||
|
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||||
|
|
||||||
|
print("Load saved projects")
|
||||||
|
print(loadedProjects)
|
||||||
|
// pdemos = loadedProjects
|
||||||
|
return loadedProjects
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
print("Returned Empty pdemos")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// SelectionView.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 10/24/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import FileTransferClient
|
|
||||||
|
|
||||||
struct SelectionView: View {
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Button {
|
|
||||||
// rootViewModel.goToSelection()
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.backward")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: 15)
|
|
||||||
.foregroundColor(.clear)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.top, 15)
|
|
||||||
|
|
||||||
|
|
||||||
Image("pyleapLogo")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.padding(.top, 100)
|
|
||||||
.padding(.horizontal, 60)
|
|
||||||
|
|
||||||
Text("What type of device do you want to connect to?")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 36))
|
|
||||||
.minimumScaleFactor(0.01)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.padding(.top, 100)
|
|
||||||
.padding(.horizontal, 30)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWiFiSelection()
|
|
||||||
} label: {
|
|
||||||
Text("Wifi")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_pink"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goTobluetoothPairing()
|
|
||||||
} label: {
|
|
||||||
Text("Bluetooth")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("bluetooth_button_color"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.backToMain()
|
|
||||||
} label: {
|
|
||||||
Text("I Don't Know")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.padding(.horizontal, 60)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color.gray)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goTobluetoothPairing()
|
|
||||||
} label: {
|
|
||||||
Text("Reconnect to a device")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.bottom, 60)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SelectionView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
SelectionView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
//
|
|
||||||
// WifiSelection.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 11/7/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WifiSelection: View {
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
@StateObject var viewModel = WifiViewModel()
|
|
||||||
|
|
||||||
let userDefaults = UserDefaults.standard
|
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
|
||||||
|
|
||||||
func toggleViewModelIP() {
|
|
||||||
viewModel.isInvalidIP.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeResolvedAddress(service: ResolvedService) {
|
|
||||||
print("Storing resolved address")
|
|
||||||
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
|
||||||
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
|
|
||||||
userDefaults.set(service.device, forKey: kPrefix+".storeResolvedAddress.device" )
|
|
||||||
|
|
||||||
print("Stored UserDefaults")
|
|
||||||
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func showConfirmationPrompt(service: ResolvedService, hostName: String) {
|
|
||||||
comfirmationAlertMessage(title: "Would you like to connect to \(hostName)?", exitTitle: "Cancel", primaryTitle: "Connect") {
|
|
||||||
storeResolvedAddress(service: service)
|
|
||||||
viewModel.printStoredInfo()
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} cancel: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showValidationPrompt() {
|
|
||||||
alertTF(title: "Enter Device IP Address",
|
|
||||||
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
|
|
||||||
hintText: "IP Address...",
|
|
||||||
primaryTitle: "Done",
|
|
||||||
secondaryTitle: "Cancel") { text in
|
|
||||||
viewModel.checkServices(ip: text)
|
|
||||||
|
|
||||||
} secondaryAction: {
|
|
||||||
print("Cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showAlertMessage() {
|
|
||||||
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
|
|
||||||
showValidationPrompt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToSelection()
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.backward")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: 15)
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
.padding(.leading, 30)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.top, 15)
|
|
||||||
|
|
||||||
Image("pyleapLogo")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.padding(.top, 100)
|
|
||||||
.padding(.horizontal, 60)
|
|
||||||
|
|
||||||
Text("How do you want to connect to your WiFi enabled device?")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 36))
|
|
||||||
.minimumScaleFactor(0.01)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.padding(.top, 100)
|
|
||||||
.padding(.horizontal, 30)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWiFiServiceSelection()
|
|
||||||
} label: {
|
|
||||||
Text("Scan for a Device")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_pink"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
showValidationPrompt()
|
|
||||||
} label: {
|
|
||||||
Text("Manually Connect")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_purple"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
Link("Learn More", destination: URL(string: "https://learn.adafruit.com/pyleap-app")!)
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color.gray)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goTobluetoothPairing()
|
|
||||||
} label: {
|
|
||||||
Text("Reconnect to a device")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.bottom, 60)
|
|
||||||
|
|
||||||
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
|
|
||||||
if newValue {
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
viewModel.ipInputValidation.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
|
||||||
print("viewModel.isInvalidIP .onChange")
|
|
||||||
if newValue {
|
|
||||||
showAlertMessage()
|
|
||||||
toggleViewModelIP()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WifiSelection_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
WifiSelection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
//
|
|
||||||
// BLESettingsView.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 12/15/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct BLESettingsView: View {
|
|
||||||
|
|
||||||
@State private var thirdPartyLink: String = ""
|
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
@StateObject var viewModel = SettingsViewModel()
|
|
||||||
@ObservedObject var networkModel = NetworkService()
|
|
||||||
|
|
||||||
|
|
||||||
func showInvalidURLEntryAlert() {
|
|
||||||
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDownloadConfirmationAlert() {
|
|
||||||
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDisconnectionPrompt() {
|
|
||||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
|
||||||
viewModel.clearKnownIPAddress()
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} cancel: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
|
|
||||||
Text("Add Custom Project")
|
|
||||||
.font(Font.custom("ReadexPro-SemiBold", size: 24))
|
|
||||||
.padding([.horizontal, .top])
|
|
||||||
|
|
||||||
Text("""
|
|
||||||
Please enter the URL link to your own project that you would like to add to the current list of projects in PyLeap.
|
|
||||||
""")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
|
||||||
.font(.callout)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
TextField("https://", text: $thirdPartyLink)
|
|
||||||
.background(Color.white)
|
|
||||||
.cornerRadius(5)
|
|
||||||
.keyboardType(.URL)
|
|
||||||
.textContentType(.URL)
|
|
||||||
.onSubmit {
|
|
||||||
networkModel.fetchThirdPartyProject(urlString: thirdPartyLink)
|
|
||||||
thirdPartyLink = ""
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
|
|
||||||
Form {
|
|
||||||
|
|
||||||
Section{
|
|
||||||
Label("[Go to GitHub](https://github.com/adafruit/pyleap.github.io)", systemImage: "link")
|
|
||||||
}
|
|
||||||
header: {
|
|
||||||
Text("""
|
|
||||||
Find more information on adding your own project here:
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
Section{
|
|
||||||
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
|
|
||||||
}
|
|
||||||
.font(.system(size: 16, weight: .semibold))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.invalidURL, perform: { newValue in
|
|
||||||
showInvalidURLEntryAlert()
|
|
||||||
viewModel.invalidURL = false
|
|
||||||
})
|
|
||||||
|
|
||||||
.onChange(of: viewModel.confirmDownload, perform: { newValue in
|
|
||||||
showDownloadConfirmationAlert()
|
|
||||||
viewModel.confirmDownload = false
|
|
||||||
})
|
|
||||||
|
|
||||||
.navigationTitle("Settings")
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Text("Back")
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
}
|
|
||||||
.padding(12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.background(Color(UIColor.systemGroupedBackground))
|
|
||||||
.safeAreaInset(edge: .top) {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToFileTransfer()
|
|
||||||
} label: {
|
|
||||||
Text("Back")
|
|
||||||
}
|
|
||||||
.padding(.leading, 20)
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// switch appState {
|
|
||||||
//
|
|
||||||
// case .ble:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToFileTransfer()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// case .wifi:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToWifiView()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// case .none:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToMainSelection()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame(height: UIScreen.main.bounds.height / 19)
|
|
||||||
.background(Color(.systemGroupedBackground))
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Settings")
|
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 32))
|
|
||||||
.padding(.leading,20)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.top, 25)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BLESettingsView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
BLESettingsView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
//
|
|
||||||
// BLESettingsViewModel.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 12/15/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class BLESettingsViewModel: ObservableObject {
|
|
||||||
|
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
|
||||||
let userDefaults = UserDefaults.standard
|
|
||||||
@Published var hostName = ""
|
|
||||||
@Published var device = ""
|
|
||||||
@Published var ipAddress = ""
|
|
||||||
var connectedToDevice = false
|
|
||||||
@Published var invalidURL = false
|
|
||||||
@Published var confirmDownload = false
|
|
||||||
|
|
||||||
|
|
||||||
init() {
|
|
||||||
check()
|
|
||||||
registerNotifications(enabled: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private weak var errorObserver: NSObjectProtocol?
|
|
||||||
private weak var confirmDownloadObserver: NSObjectProtocol?
|
|
||||||
private weak var invalidIPObserver: NSObjectProtocol?
|
|
||||||
|
|
||||||
|
|
||||||
private func registerNotifications(enabled: Bool) {
|
|
||||||
let notificationCenter = NotificationCenter.default
|
|
||||||
|
|
||||||
if enabled {
|
|
||||||
errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkRequest, object: nil, queue: .main, using: {[weak self] _ in self?.showError()})
|
|
||||||
|
|
||||||
|
|
||||||
confirmDownloadObserver = notificationCenter.addObserver(forName: .didCollectCustomProject, object: nil, queue: .main, using: {[weak self] _ in self?.showConfirmationAlert()})
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if let testObserver = errorObserver {notificationCenter.removeObserver(testObserver)}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func showError() {
|
|
||||||
invalidURL = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func showConfirmationAlert() {
|
|
||||||
confirmDownload = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func check() {
|
|
||||||
print(#function)
|
|
||||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
|
||||||
connectedToDevice = false
|
|
||||||
} else {
|
|
||||||
|
|
||||||
connectedToDevice = true
|
|
||||||
|
|
||||||
ipAddress = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") as! String
|
|
||||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
|
||||||
device = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device") as! String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearKnownIPAddress() {
|
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.hostName" )
|
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.device" )
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
//
|
|
||||||
// SettingsView.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 9/7/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
enum SettingState {
|
|
||||||
case ble
|
|
||||||
case wifi
|
|
||||||
case none
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct SettingsView: View {
|
|
||||||
|
|
||||||
// @State public var appState: SettingState = .none
|
|
||||||
|
|
||||||
@State private var thirdPartyLink: String = ""
|
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
@StateObject var viewModel = SettingsViewModel()
|
|
||||||
@ObservedObject var networkModel = NetworkService()
|
|
||||||
|
|
||||||
func showInvalidURLEntryAlert() {
|
|
||||||
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDownloadConfirmationAlert() {
|
|
||||||
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDisconnectionPrompt() {
|
|
||||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
|
||||||
viewModel.clearKnownIPAddress()
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} cancel: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
|
|
||||||
Text("Add Custom Project")
|
|
||||||
.font(Font.custom("ReadexPro-SemiBold", size: 24))
|
|
||||||
.padding([.horizontal, .top])
|
|
||||||
|
|
||||||
Text("""
|
|
||||||
Please enter the URL link to your own project that you would like to add to the current list of projects in PyLeap.
|
|
||||||
""")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
|
||||||
.font(.callout)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
TextField("https://", text: $thirdPartyLink)
|
|
||||||
.background(Color.white)
|
|
||||||
.cornerRadius(5)
|
|
||||||
.keyboardType(.URL)
|
|
||||||
.textContentType(.URL)
|
|
||||||
.onSubmit {
|
|
||||||
networkModel.fetchThirdPartyProject(urlString: thirdPartyLink)
|
|
||||||
thirdPartyLink = ""
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
|
|
||||||
Form {
|
|
||||||
|
|
||||||
Section{
|
|
||||||
Label("[Go to GitHub](https://github.com/adafruit/pyleap.github.io)", systemImage: "link")
|
|
||||||
}
|
|
||||||
header: {
|
|
||||||
Text("""
|
|
||||||
Find more information on adding your own project here:
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
Section{
|
|
||||||
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
|
|
||||||
}
|
|
||||||
.font(.system(size: 16, weight: .semibold))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.invalidURL, perform: { newValue in
|
|
||||||
showInvalidURLEntryAlert()
|
|
||||||
viewModel.invalidURL = false
|
|
||||||
})
|
|
||||||
|
|
||||||
.onChange(of: viewModel.confirmDownload, perform: { newValue in
|
|
||||||
showDownloadConfirmationAlert()
|
|
||||||
viewModel.confirmDownload = false
|
|
||||||
})
|
|
||||||
|
|
||||||
.navigationTitle("Settings")
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Text("Back")
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
}
|
|
||||||
.padding(12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.background(Color(UIColor.systemGroupedBackground))
|
|
||||||
.safeAreaInset(edge: .top) {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} label: {
|
|
||||||
Text("Back")
|
|
||||||
}
|
|
||||||
.padding(.leading, 20)
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// switch appState {
|
|
||||||
//
|
|
||||||
// case .ble:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToFileTransfer()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// case .wifi:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToWifiView()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// case .none:
|
|
||||||
// Button {
|
|
||||||
// rootViewModel.goToMainSelection()
|
|
||||||
// } label: {
|
|
||||||
// Text("Back")
|
|
||||||
// }
|
|
||||||
// .padding(.leading, 20)
|
|
||||||
// Spacer()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame(height: UIScreen.main.bounds.height / 19)
|
|
||||||
.background(Color(.systemGroupedBackground))
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Settings")
|
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 32))
|
|
||||||
.padding(.leading,20)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.top, 25)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
|
|
||||||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
|
||||||
|
|
||||||
alert.addAction(.init(title: primaryTitle, style: .destructive, handler: { _ in
|
|
||||||
disconnect()
|
|
||||||
}))
|
|
||||||
|
|
||||||
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
|
|
||||||
cancel()
|
|
||||||
}))
|
|
||||||
|
|
||||||
rootController().present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func alertMessage(title: String, exitTitle: String, cancel: @escaping()->()){
|
|
||||||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
|
||||||
|
|
||||||
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
|
|
||||||
cancel()
|
|
||||||
}))
|
|
||||||
rootController().present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func alertTF(title: String, message: String, hintText: String, primaryTitle: String, secondaryTitle: String, primaryAction: @escaping(String)->(), secondaryAction: @escaping()->()) {
|
|
||||||
|
|
||||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
||||||
alert.addTextField { field in
|
|
||||||
field.keyboardType = .decimalPad
|
|
||||||
field.placeholder = hintText
|
|
||||||
|
|
||||||
}
|
|
||||||
alert.addAction(.init(title: secondaryTitle, style: .cancel, handler: { _ in
|
|
||||||
secondaryAction()
|
|
||||||
|
|
||||||
}))
|
|
||||||
alert.addAction(.init(title: primaryTitle, style: .default, handler: { _ in
|
|
||||||
if let text = alert.textFields?[0].text {
|
|
||||||
primaryAction(text)
|
|
||||||
} else {
|
|
||||||
primaryAction("")
|
|
||||||
}
|
|
||||||
|
|
||||||
}))
|
|
||||||
rootController().present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rootController()->UIViewController{
|
|
||||||
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
|
|
||||||
return .init()
|
|
||||||
}
|
|
||||||
guard let root = screen.windows.first?.rootViewController else {
|
|
||||||
return .init()
|
|
||||||
}
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SettingsView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
SettingsView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,37 +8,22 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class ExpandedState: ObservableObject {
|
|
||||||
@Published var currentCell = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct WifiCell: View {
|
struct WifiCell: View {
|
||||||
|
|
||||||
@EnvironmentObject var expandedState : ExpandedState
|
|
||||||
|
|
||||||
let result : ResultItem
|
let result : ResultItem
|
||||||
|
|
||||||
@State var isExpanded: Bool = false {
|
@State private var isExpanded: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
onViewGeometryChanged()
|
onViewGeometryChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@State var isExpandedTest: String = ""
|
|
||||||
|
|
||||||
|
|
||||||
@ObservedObject var viewModel = WifiCellViewModel()
|
|
||||||
@Binding var isConnected: Bool
|
@Binding var isConnected: Bool
|
||||||
@Binding var bootOne: String
|
@Binding var bootOne: String
|
||||||
@Binding var stateBinder: DownloadState
|
@Binding var stateBinder: DownloadState
|
||||||
|
|
||||||
var showRunItButton = false
|
var showRunItButton = false
|
||||||
|
|
||||||
var projectName = String()
|
|
||||||
|
|
||||||
let onViewGeometryChanged: ()->Void
|
let onViewGeometryChanged: ()->Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -46,53 +31,48 @@ struct WifiCell: View {
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var header: some View {
|
private var content: some View {
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text(result.projectName)
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
.padding(8)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Image(systemName: "chevron.down")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 30, height: 15, alignment: .center)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding(.trailing, 30)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.padding(.vertical, 5)
|
|
||||||
.padding(.leading)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background(Color("pyleap_purple"))
|
|
||||||
.onTapGesture {
|
|
||||||
expandedState.currentCell = result.bundleLink
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var content: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
header
|
header
|
||||||
|
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
Group {
|
|
||||||
WifiSubViewCell(result: result, bindingString: $bootOne, downloadStateBinder: $stateBinder,isConnected: $isConnected)
|
|
||||||
|
|
||||||
|
Group {
|
||||||
|
WifiSubViewCell(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
|
||||||
|
image: result.projectImage,
|
||||||
|
description: result.description,
|
||||||
|
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
|
||||||
|
downloadLink: result.bundleLink,
|
||||||
|
compatibility: result.compatibility,
|
||||||
|
isConnected: $isConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var header: some View {
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text(result.projectName)
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||||
|
.padding(8)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "chevron.down")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 15, alignment: .center)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.trailing, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.padding(.leading)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color("pyleap_purple"))
|
||||||
|
.onTapGesture { isExpanded.toggle() }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// WifiCellViewModel.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 12/2/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class WifiCellViewModel: ObservableObject {
|
|
||||||
|
|
||||||
@Published var isExpanded : Bool = false
|
|
||||||
|
|
||||||
init() {
|
|
||||||
print("Initialized")
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
print("Deinitialized")
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var projectBundle = ""
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,163 +0,0 @@
|
||||||
//
|
|
||||||
// WifiPairingView.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 11/8/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
struct SwiftUIWebView: UIViewRepresentable {
|
|
||||||
typealias UIViewType = WKWebView
|
|
||||||
let webAddress: String
|
|
||||||
let webView: WKWebView
|
|
||||||
|
|
||||||
init(webAddress: String) {
|
|
||||||
self.webAddress = webAddress
|
|
||||||
webView = WKWebView(frame: .zero)
|
|
||||||
webView.load(URLRequest(url: URL(string: webAddress)!))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> WKWebView {
|
|
||||||
webView
|
|
||||||
}
|
|
||||||
func updateUIView(_ uiView: WKWebView, context: Context) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WifiPairingView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
|
|
||||||
@State private var showModal = false
|
|
||||||
@State private var showProgress = false
|
|
||||||
|
|
||||||
@State private var userWaitThreshold = false
|
|
||||||
@State private var nextText = 0
|
|
||||||
@State var showSheetView = false
|
|
||||||
@State var showConnectionErrorView = false
|
|
||||||
|
|
||||||
let tutorialAddress = "https://learn.adafruit.com/pyleap-app/wifi-pairing"
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
|
|
||||||
self.rootViewModel.goToWiFiSelection()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.backward")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.foregroundColor(Color("pyleap_gray"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "wifi")
|
|
||||||
.resizable()
|
|
||||||
.foregroundColor(.clear)
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: -5)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.top, 50)
|
|
||||||
.padding(.horizontal, 30)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Image("pyleapLogo")
|
|
||||||
// .resizable()
|
|
||||||
// .aspectRatio(contentMode: .fit)
|
|
||||||
// .padding(.top, 50)
|
|
||||||
// .padding(.horizontal, 60)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// if nextText == 0 {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Text("""
|
|
||||||
// PyLeap’s WiFi mode requires EPS32 devices to have WiFi credentials in an ./env file.
|
|
||||||
//
|
|
||||||
// If you’re having trouble connecting, check this documentation:
|
|
||||||
//
|
|
||||||
// https://docs.circuitpython.org/en/latest/docs/workflows.html#web
|
|
||||||
//
|
|
||||||
// """)
|
|
||||||
// .font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
// .minimumScaleFactor(0.01)
|
|
||||||
// .multilineTextAlignment(.leading)
|
|
||||||
// .padding(.top, 100)
|
|
||||||
// .padding(.horizontal, 30)
|
|
||||||
// .padding(.bottom, 69)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Spacer()
|
|
||||||
//
|
|
||||||
// Button(action: {
|
|
||||||
// rootViewModel.goToWiFiSelection()
|
|
||||||
// }) {
|
|
||||||
//
|
|
||||||
// Text("Back")
|
|
||||||
// .font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
// .foregroundColor(Color.white)
|
|
||||||
//
|
|
||||||
// .padding()
|
|
||||||
// .padding(.horizontal, 60)
|
|
||||||
//
|
|
||||||
// .frame(height: 50)
|
|
||||||
// .background(Color("pyleap_purple"))
|
|
||||||
// .clipShape(Capsule())
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// Spacer()
|
|
||||||
// .frame(height: 60)
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
SwiftUIWebView(webAddress: tutorialAddress)
|
|
||||||
.padding(.vertical)
|
|
||||||
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
rootViewModel.goToWiFiSelection()
|
|
||||||
}) {
|
|
||||||
|
|
||||||
Text("Back")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
|
|
||||||
.padding()
|
|
||||||
.padding(.horizontal, 60)
|
|
||||||
|
|
||||||
.frame(height: 50)
|
|
||||||
.background(Color("pyleap_purple"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
}
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WifiPairingView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
WifiPairingView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,8 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ResolvedService: Identifiable, Equatable {
|
struct ResolvedService {
|
||||||
var id = UUID()
|
|
||||||
var ipAddress: String
|
var ipAddress: String
|
||||||
var hostName: String
|
var hostName: String
|
||||||
var device: String
|
var device: String
|
||||||
|
|
@ -38,58 +37,36 @@ class WifiServiceManager: NSObject, ObservableObject {
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
print("Wifi Module Used")
|
|
||||||
serviceManagerBrowser.delegate = self
|
serviceManagerBrowser.delegate = self
|
||||||
findService()
|
findService()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
print("Wifi Module Removed")
|
|
||||||
self.serviceManagerBrowser.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func findService() {
|
func findService() {
|
||||||
|
print("Start Scan")
|
||||||
|
// self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
||||||
|
resolvedServices.removeAll()
|
||||||
|
|
||||||
print("Current state of isSearching: \(isSearching)")
|
print("Current state of isSearching: \(isSearching)")
|
||||||
|
|
||||||
if isSearching == false {
|
if isSearching == false {
|
||||||
print("Start Scanning")
|
|
||||||
startDiscovery()
|
startDiscovery()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDiscovery() {
|
func startDiscovery() {
|
||||||
print("Start Discovery")
|
// connectionStatus = .connected
|
||||||
DispatchQueue.main.async {
|
print("Start Scan")
|
||||||
self.isSearching = true
|
isSearching = true
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
print("Current state of isSearching: \(isSearching)")
|
|
||||||
self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
||||||
|
|
||||||
|
|
||||||
let timer = Timer.scheduledTimer(withTimeInterval: 12, repeats: false) { timer in
|
|
||||||
|
|
||||||
self.stopDiscoveryScan()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopDiscoveryScan() {
|
func stopDiscoveryScan() {
|
||||||
if isSearching {
|
if isSearching {
|
||||||
isSearching = false
|
isSearching = false
|
||||||
self.serviceManagerBrowser.stop()
|
self.serviceManagerBrowser.stop()
|
||||||
print(resolvedServices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||||
|
|
||||||
|
|
@ -111,26 +88,11 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||||
print("didFindDomain")
|
print("didFindDomain")
|
||||||
}
|
}
|
||||||
|
|
||||||
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
|
|
||||||
print("netServiceBrowserDidStopSearch")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func netServiceWillResolve(_ sender: NetService) {
|
|
||||||
print("netServiceWillResolve")
|
|
||||||
}
|
|
||||||
|
|
||||||
func netServiceBrowser(_ browser: NetServiceBrowser, didRemoveDomain domainString: String, moreComing: Bool) {
|
|
||||||
"didRemoveDomain"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
||||||
print(#function)
|
print(#function)
|
||||||
discoveredService = service
|
discoveredService = service
|
||||||
discoveredService?.delegate = self
|
discoveredService?.delegate = self
|
||||||
discoveredService?.resolve(withTimeout: 7)
|
discoveredService?.resolve(withTimeout: 10)
|
||||||
|
|
||||||
if services.contains(service) {
|
if services.contains(service) {
|
||||||
print("All ready in service array")
|
print("All ready in service array")
|
||||||
|
|
@ -139,22 +101,13 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||||
services.append(service)
|
services.append(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if moreComing == false {
|
||||||
|
browser.stop()
|
||||||
if !moreComing {
|
|
||||||
serviceManagerBrowser.stop()
|
|
||||||
isSearching = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print("Service: \(service)")
|
|
||||||
|
|
||||||
print("Service count: \(services.count)")
|
print("Service count: \(services.count)")
|
||||||
self.serviceManagerBrowser.remove(from: .main, forMode: .common)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func netServiceDidStop(_ sender: NetService) {
|
func netServiceDidStop(_ sender: NetService) {
|
||||||
isSearching = false
|
isSearching = false
|
||||||
print("isSearching: \(isSearching)")
|
print("isSearching: \(isSearching)")
|
||||||
|
|
@ -183,16 +136,7 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||||
|
|
||||||
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
|
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
|
||||||
|
|
||||||
|
resolvedServices.append(resolvedService)
|
||||||
if resolvedServices.contains(where: {$0.ipAddress == resolvedService.ipAddress}) {
|
|
||||||
// it exists, do nothing
|
|
||||||
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) exists in network")
|
|
||||||
} else {
|
|
||||||
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) Added to Network List")
|
|
||||||
resolvedServices.append(resolvedService)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
print("resolvedServices count: \(resolvedServices.count)")
|
print("resolvedServices count: \(resolvedServices.count)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +148,17 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||||
|
|
||||||
|
|
||||||
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
||||||
print("didRemove")
|
|
||||||
|
//self.discovered.removeAll { $0.name == service.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// func netServiceDidResolveAddress(_ sender: NetService) {
|
||||||
|
// print("netServiceDidResolveAddress")
|
||||||
|
// if let data = sender.txtRecordData() {
|
||||||
|
// let dict = NetService.dictionary(fromTXTRecord: data)
|
||||||
|
// /// do stuff with txtRecord dict here and then add to discovered array.
|
||||||
|
// // discoveredService = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
//
|
|
||||||
// WifiServiceSelectionView.swift
|
|
||||||
// PyLeap
|
|
||||||
//
|
|
||||||
// Created by Trevor Beaton on 10/24/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WifiServiceSelectionView: View {
|
|
||||||
|
|
||||||
@ObservedObject var wifiServiceViewModel = WifiServiceManager()
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
@StateObject var viewModel = WifiViewModel()
|
|
||||||
|
|
||||||
let userDefaults = UserDefaults.standard
|
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
|
||||||
|
|
||||||
@State private var scrollViewID = UUID()
|
|
||||||
|
|
||||||
// For Blinka spinning animation
|
|
||||||
@State private var isAnimating = false
|
|
||||||
var foreverAnimation: Animation {
|
|
||||||
Animation.linear(duration: 2.0)
|
|
||||||
.repeatForever(autoreverses: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeResolvedAddress(service: ResolvedService) {
|
|
||||||
print("Storing resolved address")
|
|
||||||
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
|
||||||
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
|
|
||||||
userDefaults.set(service.device, forKey: kPrefix+".storeResolvedAddress.device" )
|
|
||||||
|
|
||||||
print("Stored UserDefaults")
|
|
||||||
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func showConfirmationPrompt(service: ResolvedService, hostName: String) {
|
|
||||||
comfirmationAlertMessage(title: "Would you like to connect to \(hostName)?", exitTitle: "Cancel", primaryTitle: "Connect") {
|
|
||||||
storeResolvedAddress(service: service)
|
|
||||||
viewModel.printStoredInfo()
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} cancel: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showValidationPrompt() {
|
|
||||||
alertTF(title: "Enter Device IP Address",
|
|
||||||
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
|
|
||||||
hintText: "IP Address...",
|
|
||||||
primaryTitle: "Done",
|
|
||||||
secondaryTitle: "Cancel") { text in
|
|
||||||
viewModel.checkServices(ip: text)
|
|
||||||
|
|
||||||
} secondaryAction: {
|
|
||||||
print("Cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showAlertMessage() {
|
|
||||||
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
|
|
||||||
showValidationPrompt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleViewModelIP() {
|
|
||||||
viewModel.isInvalidIP.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToSelection()
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.backward")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.offset(y: 15)
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
.padding(.leading, 30)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.top, 15)
|
|
||||||
|
|
||||||
|
|
||||||
Image("pyleapLogo")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.padding(.top, 50)
|
|
||||||
.padding(.horizontal, 60)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if wifiServiceViewModel.isSearching && wifiServiceViewModel.resolvedServices.isEmpty {
|
|
||||||
Text("WiFi Connect")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 36))
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.lineLimit(1)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
|
|
||||||
BlinkaAnimationView(height: 150, width: 145)
|
|
||||||
.padding(.bottom, 20)
|
|
||||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
|
||||||
.onAppear() {
|
|
||||||
Animation.linear(duration: 1.0)
|
|
||||||
.repeatForever(autoreverses: false)
|
|
||||||
isAnimating = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
Text("""
|
|
||||||
Scanning for PyLeap
|
|
||||||
compatible devices...
|
|
||||||
""")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.lineLimit(2)
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWifiPairingTutorial()
|
|
||||||
} label: {
|
|
||||||
Text("Pairing Tutorial")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_pink"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding(.horizontal, 30)
|
|
||||||
.padding(.bottom, 60)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
if !wifiServiceViewModel.resolvedServices.isEmpty {
|
|
||||||
Text("WiFi Devices Found")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Text("No WiFi Devices Found")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Image(systemName: "arrow.clockwise")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
|
|
||||||
Button {
|
|
||||||
wifiServiceViewModel.findService()
|
|
||||||
} label: {
|
|
||||||
Text("Rescan")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.frame(width: 146, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_purple"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if wifiServiceViewModel.isSearching {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.vertical, 40)
|
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: true) {
|
|
||||||
ScrollViewReader { scroll in
|
|
||||||
ForEach(wifiServiceViewModel.resolvedServices) { service in
|
|
||||||
WifiServiceCellView(resolvedService: service, onViewGeometryChanged: {
|
|
||||||
withAnimation {
|
|
||||||
scroll.scrollTo(service.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onTapGesture {
|
|
||||||
// Save Cred to User Defaults
|
|
||||||
showConfirmationPrompt(service: service, hostName: service.hostName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.id(self.scrollViewID)
|
|
||||||
}
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
|
|
||||||
if wifiServiceViewModel.resolvedServices.isEmpty && !wifiServiceViewModel.isSearching {
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
Text("""
|
|
||||||
Unable to find any WiFi
|
|
||||||
compatible Adafruit devices
|
|
||||||
on your network
|
|
||||||
""")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
|
||||||
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.lineLimit(3)
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToWifiPairingTutorial()
|
|
||||||
} label: {
|
|
||||||
Text("Pairing Tutorial")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
.frame(width: 270, height: 50, alignment: .center)
|
|
||||||
.background(Color("pyleap_pink"))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 30)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.bottom, 60)
|
|
||||||
|
|
||||||
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
|
|
||||||
if newValue {
|
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
viewModel.ipInputValidation.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
|
||||||
print("viewModel.isInvalidIP .onChange")
|
|
||||||
if newValue {
|
|
||||||
showAlertMessage()
|
|
||||||
toggleViewModelIP()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
struct WifiServiceSelectionView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
WifiServiceSelectionView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,39 +12,14 @@ struct WifiStatusConnectedView: View {
|
||||||
|
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
|
||||||
|
|
||||||
@Binding var hostName: String
|
@Binding var hostName: String
|
||||||
|
|
||||||
func showConfirmationPrompt() {
|
|
||||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
|
||||||
rootViewModel.goToSelection()
|
|
||||||
} cancel: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 0, content: {
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
|
|
||||||
Image(systemName: "wifi")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(5)
|
|
||||||
|
|
||||||
Text("Connected to \(hostName). ")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
|
||||||
|
|
||||||
Button {
|
|
||||||
showConfirmationPrompt()
|
|
||||||
} label: {
|
|
||||||
Text("Disconnect")
|
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 14))
|
|
||||||
.underline()
|
|
||||||
.minimumScaleFactor(0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Text("Connected To \(hostName)")
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
})
|
})
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
@ -59,7 +34,7 @@ struct WifiStatusNoConnectionView: View {
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 8, content: {
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
Text("No Device Detected")
|
Text("No Device Detected")
|
||||||
.font(Font.custom("ReadexPro-SemiBold", size: 14))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
|
||||||
})
|
})
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
|
|
@ -75,7 +50,7 @@ struct WifiStatusConnectingView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 8, content: {
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
Text("Searching for Adafruit Devices...")
|
Text("Searching for Adafruit Devices...")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
})
|
})
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
@ -93,7 +68,7 @@ struct NetworkConnectionBanner: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 8, content: {
|
HStack(alignment: .center, spacing: 8, content: {
|
||||||
Text("Searching local network...")
|
Text("Searching local network...")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
|
||||||
// ProgressView()
|
// ProgressView()
|
||||||
//.resizable()
|
//.resizable()
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct WifiSubViewCell: View {
|
struct WifiSubViewCell: View {
|
||||||
|
|
||||||
@State var transferInProgress = false
|
@State var transferInProgress = false
|
||||||
|
|
||||||
@State var isDownloaded = false
|
@State var isDownloaded = false
|
||||||
|
|
||||||
|
@EnvironmentObject var globalString : GlobalString
|
||||||
|
|
||||||
@StateObject var wifiFileTransfer = WifiFileTransfer()
|
@StateObject var wifiFileTransfer = WifiFileTransfer()
|
||||||
@StateObject var wifiTransferService = WifiTransferService()
|
|
||||||
let result : ResultItem
|
|
||||||
|
|
||||||
@Binding var bindingString: String
|
@Binding var bindingString: String
|
||||||
|
|
||||||
|
|
@ -23,17 +23,18 @@ struct WifiSubViewCell: View {
|
||||||
|
|
||||||
@State private var toggleView: Bool = false
|
@State private var toggleView: Bool = false
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let image: String
|
||||||
|
let description: String
|
||||||
|
let learnGuideLink: URLRequest
|
||||||
|
let downloadLink: String
|
||||||
|
let compatibility: [String]
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
@StateObject var downloadModel = DownloadViewModel()
|
@StateObject var downloadModel = DownloadViewModel()
|
||||||
@ObservedObject var viewModel = WifiSubViewCellModel()
|
|
||||||
|
|
||||||
@Binding var isConnected : Bool
|
@Binding var isConnected : Bool
|
||||||
|
|
||||||
@State private var counter = 0
|
|
||||||
@State private var numOfFiles = 0
|
|
||||||
@State var downloadState: DownloadState = .idle
|
|
||||||
|
|
||||||
|
|
||||||
@State private var showWebViewPopover: Bool = false
|
@State private var showWebViewPopover: Bool = false
|
||||||
@State var errorOccured = false
|
@State var errorOccured = false
|
||||||
@State private var presentAlert = false
|
@State private var presentAlert = false
|
||||||
|
|
@ -41,100 +42,6 @@ struct WifiSubViewCell: View {
|
||||||
@State var offlineWithoutProject = false
|
@State var offlineWithoutProject = false
|
||||||
|
|
||||||
|
|
||||||
func showTransferErrorMessage() {
|
|
||||||
alertMessage(title: """
|
|
||||||
Download Error
|
|
||||||
Unable to download project
|
|
||||||
Try again later
|
|
||||||
""", exitTitle: "Retry") {
|
|
||||||
wifiFileTransfer.transferError = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func usbInUseErrorMessage() {
|
|
||||||
alertMessage(title: """
|
|
||||||
USB In Use
|
|
||||||
|
|
||||||
Files cannot be tranferred or moved while USB is in use.
|
|
||||||
|
|
||||||
Remove device from USB. Press "Reset" on the device.
|
|
||||||
""", exitTitle: "Retry") {
|
|
||||||
// wifiFileTransfer.transferError = false
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func startTransferProcess() {
|
|
||||||
|
|
||||||
|
|
||||||
if isDownloaded {
|
|
||||||
print("Project found")
|
|
||||||
wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("Project not found")
|
|
||||||
downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func testOperation() {
|
|
||||||
let operationQueue = OperationQueue()
|
|
||||||
|
|
||||||
let operation1 = BlockOperation {
|
|
||||||
wifiTransferService.optionRequest(handler: { results in
|
|
||||||
|
|
||||||
switch results {
|
|
||||||
|
|
||||||
case .success(let contents):
|
|
||||||
|
|
||||||
if contents.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("Connected to USB")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
usbInUseErrorMessage()
|
|
||||||
wifiFileTransfer.stopTransfer = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .failure:
|
|
||||||
print("Failure")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let operation2 = BlockOperation {
|
|
||||||
|
|
||||||
if isDownloaded {
|
|
||||||
print("Project found")
|
|
||||||
wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("Project not found")
|
|
||||||
downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add operations to the operation queue
|
|
||||||
operationQueue.addOperation(operation1)
|
|
||||||
operationQueue.addOperation(operation2)
|
|
||||||
|
|
||||||
// Block the current thread until all operations have finished executing
|
|
||||||
operationQueue.waitUntilAllOperationsAreFinished()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
|
@ -142,14 +49,14 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0, content: {
|
VStack(alignment: .leading, spacing: 0, content: {
|
||||||
|
|
||||||
ImageWithURL(result.projectImage)
|
ImageWithURL(image)
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
|
||||||
Text(result.description)
|
Text(description)
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
.font(Font.custom("ReadexPro-Regular", size: 18))
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.minimumScaleFactor(0.1)
|
.minimumScaleFactor(0.1)
|
||||||
|
|
@ -158,19 +65,8 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
.font(Font.custom("ReadexPro-Bold", size: 18))
|
.font(Font.custom("ReadexPro-Bold", size: 18))
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 25, height: 22, alignment: .center)
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("ESP32-S2")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
.padding(.top, 10)
|
|
||||||
|
|
||||||
|
ForEach(compatibility, id: \.self) { string in
|
||||||
ForEach(result.compatibility, id: \.self) { string in
|
|
||||||
if string == "circuitplayground_bluefruit" {
|
if string == "circuitplayground_bluefruit" {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -213,7 +109,7 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showWebViewPopover, content: {
|
.sheet(isPresented: $showWebViewPopover, content: {
|
||||||
SwiftUIWebView(webAddress: result.learnGuideLink)
|
WebView(URLRequest(url: learnGuideLink.url!))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -221,16 +117,18 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
|
|
||||||
if isConnected {
|
if isConnected {
|
||||||
|
|
||||||
if result.compatibility.contains(bindingString) {
|
if compatibility.contains(bindingString) {
|
||||||
|
|
||||||
|
|
||||||
if wifiFileTransfer.testIndex.downloadState == .idle {
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
|
print("Wifi Project Attempt \(title)")
|
||||||
|
|
||||||
testOperation()
|
if wifiFileTransfer.projectDownloaded {
|
||||||
|
wifiFileTransfer.projectValidation(nameOf: title)
|
||||||
|
} else {
|
||||||
|
downloadModel.startDownload(urlString: downloadLink, projectTitle: title) {
|
||||||
|
print("DONE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
RunItButton()
|
RunItButton()
|
||||||
|
|
@ -239,41 +137,9 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if wifiFileTransfer.testIndex.downloadState == .transferring {
|
|
||||||
DownloadingButton()
|
|
||||||
.padding(.top, 20)
|
|
||||||
.disabled(true)
|
|
||||||
|
|
||||||
VStack(alignment: .center, spacing: 5) {
|
|
||||||
ProgressView("", value: CGFloat(counter), total: CGFloat(numOfFiles))
|
|
||||||
.padding(.horizontal, 90)
|
|
||||||
.padding(.top, -5)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
.accentColor(Color.gray)
|
|
||||||
.scaleEffect(x: 1, y: 2, anchor: .center)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.frame(height: 10)
|
|
||||||
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if wifiFileTransfer.testIndex.downloadState == .complete {
|
|
||||||
CompleteButton()
|
|
||||||
.padding(.top, 20)
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if wifiFileTransfer.testIndex.downloadState == .failed {
|
|
||||||
FailedButton()
|
|
||||||
.padding(.top, 20)
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|
@ -285,87 +151,61 @@ Remove device from USB. Press "Reset" on the device.
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
|
|
||||||
.onAppear(perform: {
|
|
||||||
|
|
||||||
wifiFileTransfer.registerWifiNotification(enabled: true)
|
.alert("Project Not Found", isPresented: $offlineWithoutProject) {
|
||||||
|
Button("OK") {
|
||||||
|
// Handle acknowledgement.
|
||||||
|
print("OK")
|
||||||
|
offlineWithoutProject = false
|
||||||
|
downloadStateBinder = .idle
|
||||||
|
// selectionModel.state = .idle
|
||||||
|
print("\(offlineWithoutProject)")
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("""
|
||||||
|
To use this project, connect to the internet.
|
||||||
|
""")
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.searchPathForProject(nameOf: result.projectName)
|
.onChange(of: downloadModel.isDownloading, perform: { newValue in
|
||||||
|
wifiFileTransfer.getProjectForSubClass(nameOf: title)
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
|
||||||
|
print("For project: \(title), project download is \(newValue)")
|
||||||
|
|
||||||
|
// globalString.projectString = title
|
||||||
|
|
||||||
|
if newValue {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
print("Getting project from Subclass \(title)")
|
||||||
|
// viewModel.getProjectForSubClass(nameOf: title)
|
||||||
|
wifiFileTransfer.projectValidation(nameOf: title)
|
||||||
|
|
||||||
if viewModel.projectDownloaded {
|
|
||||||
isDownloaded = true
|
isDownloaded = true
|
||||||
} else {
|
|
||||||
isDownloaded = false
|
|
||||||
}
|
|
||||||
print("is downloaded? \(viewModel.projectDownloaded)")
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
.padding(.top, 8)
|
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
|
|
||||||
if newValue {
|
|
||||||
showTransferErrorMessage()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.onChange(of: viewModel.showUsbInUseError) { newValue in
|
|
||||||
if newValue {
|
|
||||||
usbInUseErrorMessage()
|
|
||||||
}
|
}
|
||||||
|
}else {
|
||||||
|
print("Is not downloaded")
|
||||||
|
isDownloaded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.counter) { newValue in
|
})
|
||||||
print("New counter : \(newValue)")
|
.onAppear(perform: {
|
||||||
counter = newValue
|
wifiFileTransfer.getProjectForSubClass(nameOf: title)
|
||||||
}
|
if wifiFileTransfer.projectDownloaded {
|
||||||
|
isDownloaded = true
|
||||||
.onChange(of: wifiFileTransfer.numOfFiles) { newValue in
|
} else {
|
||||||
print("New numOfFiles : \(newValue)")
|
isDownloaded = false
|
||||||
numOfFiles = newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.testIndex.count) { newValue in
|
|
||||||
print("New count index : \(newValue)")
|
|
||||||
}
|
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.testIndex.numberOfFiles) { newValue in
|
|
||||||
print("New numberOfFiles index : \(newValue)")
|
|
||||||
}
|
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.testIndex.downloadState) { newValue in
|
|
||||||
print("New download state : \(newValue)")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: wifiFileTransfer.downloadState) { newValue in
|
|
||||||
switch newValue {
|
|
||||||
case .idle:
|
|
||||||
downloadState = .idle
|
|
||||||
print("idle")
|
|
||||||
case .transferring:
|
|
||||||
downloadState = .transferring
|
|
||||||
print("transferring")
|
|
||||||
case .complete:
|
|
||||||
downloadState = .complete
|
|
||||||
print("complete")
|
|
||||||
case .downloading:
|
|
||||||
downloadState = .downloading
|
|
||||||
print("downloading")
|
|
||||||
case .failed:
|
|
||||||
downloadState = .failed
|
|
||||||
print("failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
print("is downloaded? \(isDownloaded)")
|
||||||
|
})
|
||||||
|
.padding(.top, 8)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,119 +6,40 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class WifiSubViewCellModel: ObservableObject {
|
class WifiSubViewCellModel: ObservableObject {
|
||||||
|
|
||||||
@ObservedObject var wifiTransferService = WifiTransferService()
|
|
||||||
|
|
||||||
@ObservedObject var wifiFileTransfer = WifiFileTransfer()
|
|
||||||
|
|
||||||
@Published var downloadState: DownloadState = .idle
|
|
||||||
|
|
||||||
@Published var projectDownloaded = false
|
@Published var projectDownloaded = false
|
||||||
@Published var failedProjectLaunch = false
|
@Published var failedProjectLaunch = false
|
||||||
|
|
||||||
@Published var usbInUse = false
|
|
||||||
@Published var showUsbInUseError = false
|
|
||||||
|
|
||||||
|
|
||||||
init() {
|
|
||||||
registerForUSBInUseErrorNotification(enabled: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private weak var usbInUseErrorNotification: NSObjectProtocol?
|
|
||||||
|
|
||||||
private func registerForUSBInUseErrorNotification(enabled: Bool) {
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
|
|
||||||
let notificationCenter = NotificationCenter.default
|
|
||||||
|
|
||||||
if enabled {
|
|
||||||
|
|
||||||
// NotificationCenter.default.addObserver(self, selector: #selector(zipSuccess(_:)), name: .usbInUseErrorNotification,object: nil)
|
|
||||||
|
|
||||||
usbInUseErrorNotification = notificationCenter.addObserver(forName: .usbInUseErrorNotification, object: nil, queue: .main, using: {[weak self] _ in self?.zipSuccess()})
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func zipSuccess() {
|
|
||||||
showUsbInUseError = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfUSBInUse() {
|
|
||||||
|
|
||||||
wifiTransferService.optionRequest(handler: { result in
|
|
||||||
switch result {
|
|
||||||
|
|
||||||
case .success:
|
|
||||||
print("Success")
|
|
||||||
|
|
||||||
self.wifiTransferService.getRequest(read: "boot_out.txt") { result in
|
|
||||||
print(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case .failure:
|
|
||||||
print("Failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
|
|
||||||
//
|
|
||||||
// print("USB not in use.")
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// self.usbInUse = false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
|
||||||
// // if wifiFileTransfer.projectDownloaded {
|
|
||||||
// //
|
|
||||||
// // wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
|
|
||||||
// //
|
|
||||||
// // } else {
|
|
||||||
// // downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
//
|
|
||||||
// } else {
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// self.usbInUse = true
|
|
||||||
// }
|
|
||||||
// print("USB in use - files cannot be tranferred or moved while USB is in use. Show Error")
|
|
||||||
// }
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
|
||||||
func searchPathForProject(nameOf project: String) {
|
func getProjectForSubClass(nameOf project: String) {
|
||||||
var manager = FileManager.default
|
|
||||||
|
|
||||||
let nestedFolderURL = directoryPath.appendingPathComponent(project)
|
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||||
|
// for case condition: Only process URLs
|
||||||
|
for case let fileURL as URL in enumerator {
|
||||||
|
|
||||||
|
if fileURL.lastPathComponent == project {
|
||||||
|
failedProjectLaunch = false
|
||||||
|
projectDownloaded = true
|
||||||
|
print(#function)
|
||||||
|
print("Searching for... \(project)")
|
||||||
|
print("URL Path: \(fileURL.path)")
|
||||||
|
print("URL : \(fileURL)")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
} else {
|
||||||
|
failedProjectLaunch = true
|
||||||
|
projectDownloaded = false
|
||||||
|
print("Project was not found...")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
|
|
||||||
print("\(project) - Exist")
|
|
||||||
projectDownloaded = true
|
|
||||||
} else {
|
|
||||||
print("Does not exist - \(project)")
|
|
||||||
projectDownloaded = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ protocol WifiTransferServiceDelegate: AnyObject {
|
||||||
|
|
||||||
class WifiTransferService: ObservableObject {
|
class WifiTransferService: ObservableObject {
|
||||||
|
|
||||||
// weak var delegate: WifiTransferServiceDelegate?
|
weak var delegate: WifiTransferServiceDelegate?
|
||||||
|
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
|
|
||||||
@Published var counter = 0
|
|
||||||
|
|
||||||
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
||||||
|
|
||||||
@Published var hostName = ""
|
@Published var hostName = ""
|
||||||
|
|
||||||
func startup() {
|
func startup() {
|
||||||
print("\(#function) @Line: \(#line)")
|
print(#function)
|
||||||
|
print("Startup")
|
||||||
if (userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName")) != nil {
|
if (userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName")) != nil {
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String)
|
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String)
|
||||||
|
|
||||||
|
|
@ -43,7 +43,6 @@ class WifiTransferService: ObservableObject {
|
||||||
startup()
|
startup()
|
||||||
}
|
}
|
||||||
|
|
||||||
//*
|
|
||||||
func sendPutRequest(fileName: String,
|
func sendPutRequest(fileName: String,
|
||||||
body: Data,
|
body: Data,
|
||||||
then handler: @escaping(Result<Data, Error>) -> Void) {
|
then handler: @escaping(Result<Data, Error>) -> Void) {
|
||||||
|
|
@ -77,88 +76,30 @@ class WifiTransferService: ObservableObject {
|
||||||
print("Print curl:")
|
print("Print curl:")
|
||||||
print(request.cURL(pretty: true))
|
print(request.cURL(pretty: true))
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
let task = urlSession.dataTask(
|
||||||
|
with: request,
|
||||||
|
completionHandler: { data, response, error in
|
||||||
|
// Validate response and call handler
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("File write error")
|
print("File write error")
|
||||||
|
|
||||||
handler(.failure(error))
|
handler(.failure(error))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = data {
|
||||||
|
print("File write success!")
|
||||||
|
handler(.success(data))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
if let data = data {
|
|
||||||
print("File write success!")
|
|
||||||
handler(.success(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task.resume()
|
task.resume()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestWithCheck() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func optionRequest(handler: @escaping(Result<String, Error>) -> Void) {
|
|
||||||
|
|
||||||
print("HOST | \(hostName)")
|
|
||||||
let username = ""
|
|
||||||
let password = "passw0rd"
|
|
||||||
let loginString = "\(username):\(password)"
|
|
||||||
|
|
||||||
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let base64LoginString = loginData.base64EncodedString()
|
|
||||||
|
|
||||||
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
|
||||||
request.httpMethod = "OPTIONS"
|
|
||||||
|
|
||||||
//request.httpBody = try? JSONSerialization.data(withJSONObject: [:], options: [])
|
|
||||||
|
|
||||||
print("Print curl:")
|
|
||||||
|
|
||||||
print(request.cURL(pretty: true))
|
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
|
|
||||||
if let response = response as? HTTPURLResponse {
|
|
||||||
|
|
||||||
print("Response HTTP Status code: \(response.statusCode)")
|
|
||||||
|
|
||||||
print("Specific header: \(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found")")
|
|
||||||
|
|
||||||
handler(.success(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let error = error {
|
|
||||||
print("File write error")
|
|
||||||
|
|
||||||
handler(.failure(error))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
guard let data = data else {
|
|
||||||
print(String(describing: "Error Found: \(String(describing: error))"))
|
|
||||||
print("Failed! Option Request")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let str = String(data: data, encoding: .utf8) {
|
|
||||||
print("Output: \(str)")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
task.resume()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func getRequest() {
|
func getRequest() {
|
||||||
|
|
@ -175,11 +116,8 @@ class WifiTransferService: ObservableObject {
|
||||||
|
|
||||||
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
|
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
|
||||||
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
|
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
|
||||||
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||||
|
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
|
|
||||||
print("Print curl:")
|
print("Print curl:")
|
||||||
|
|
@ -209,13 +147,8 @@ class WifiTransferService: ObservableObject {
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
//completion: @escaping (Bool) -> Void
|
func getRequest(incoming: String) -> String {
|
||||||
|
|
||||||
typealias CompletionHandler = (_ success:String) -> Void
|
|
||||||
|
|
||||||
|
|
||||||
func getRequest(read: String, completionHandler: @escaping CompletionHandler) {
|
|
||||||
print("Second network call made!")
|
|
||||||
var semaphore = DispatchSemaphore (value: 0)
|
var semaphore = DispatchSemaphore (value: 0)
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
|
|
@ -224,14 +157,13 @@ class WifiTransferService: ObservableObject {
|
||||||
|
|
||||||
var outgoingString = String()
|
var outgoingString = String()
|
||||||
|
|
||||||
let loginData = loginString.data(using: String.Encoding.utf8)
|
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
|
||||||
|
return "Error"
|
||||||
|
}
|
||||||
|
let base64LoginString = loginData.base64EncodedString()
|
||||||
|
|
||||||
|
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
|
||||||
let base64LoginString = loginData!.base64EncodedString()
|
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(incoming)")!,timeoutInterval: Double.infinity)
|
||||||
|
|
||||||
print("Host Name: \(hostName)")
|
|
||||||
|
|
||||||
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(read)")!,timeoutInterval: Double.infinity)
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
|
|
@ -243,116 +175,17 @@ class WifiTransferService: ObservableObject {
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
print(String(describing: "Error Found: \(error)"))
|
print(String(describing: "Error Found: \(error)"))
|
||||||
return
|
semaphore.signal()
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
print("In do-catch loop of getRequest")
|
|
||||||
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.webDirectoryInfo = wifiIncomingData
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print(error.localizedDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let str = String(data: data, encoding: .utf8) {
|
|
||||||
print("Out-going getRequest data: \(str)")
|
|
||||||
outgoingString = str
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
completionHandler(outgoingString)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
print("Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func myFunction() {
|
|
||||||
var a = 0
|
|
||||||
|
|
||||||
let group = DispatchGroup()
|
|
||||||
group.enter()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// avoid deadlocks by not using .main queue here
|
|
||||||
DispatchQueue.global(qos: .default).async {
|
|
||||||
a = 1
|
|
||||||
group.leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait ...
|
|
||||||
group.wait()
|
|
||||||
|
|
||||||
print(a) // you could also `return a` here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typealias CompletionHandlerForCheck = (_ success: [WebDirectoryModel]) -> Void
|
|
||||||
|
|
||||||
|
|
||||||
func getRequestForFileCheck(read: String, completionHandler: @escaping CompletionHandlerForCheck){
|
|
||||||
|
|
||||||
print("Incoming Read String: \(read)")
|
|
||||||
var semaphore = DispatchSemaphore (value: 0)
|
|
||||||
|
|
||||||
let username = ""
|
|
||||||
let password = "passw0rd"
|
|
||||||
let loginString = "\(username):\(password)"
|
|
||||||
|
|
||||||
var outgoingString = String()
|
|
||||||
|
|
||||||
let loginData = loginString.data(using: String.Encoding.utf8)
|
|
||||||
|
|
||||||
|
|
||||||
let base64LoginString = loginData!.base64EncodedString()
|
|
||||||
|
|
||||||
print("Host Name: \(hostName)")
|
|
||||||
|
|
||||||
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(read)")!,timeoutInterval: Double.infinity)
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
|
||||||
request.httpMethod = "GET"
|
|
||||||
|
|
||||||
print("GET Request: \(String(describing: request.url?.absoluteURL))\n")
|
|
||||||
|
|
||||||
print("Print curl:")
|
|
||||||
print(request.cURL(pretty: true))
|
|
||||||
|
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
guard let data = data else {
|
|
||||||
print(String(describing: "Error Found: \(error)"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// print(String(data: data, encoding: .utf8)!)
|
// print(String(data: data, encoding: .utf8)!)
|
||||||
|
semaphore.signal()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.webDirectoryInfo = wifiIncomingData
|
self.webDirectoryInfo = wifiIncomingData
|
||||||
completionHandler(self.webDirectoryInfo)
|
|
||||||
print("On completion \(self.webDirectoryInfo)")
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print(error.localizedDescription)
|
print(error.localizedDescription)
|
||||||
|
|
@ -361,21 +194,16 @@ class WifiTransferService: ObservableObject {
|
||||||
if let str = String(data: data, encoding: .utf8) {
|
if let str = String(data: data, encoding: .utf8) {
|
||||||
print(str)
|
print(str)
|
||||||
outgoingString = str
|
outgoingString = str
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
print("Error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
|
semaphore.wait()
|
||||||
|
return outgoingString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
|
// func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||||
|
|
||||||
func putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) {
|
func putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||||
|
print("Test Transfer")
|
||||||
let parameters = fileContent
|
let parameters = fileContent
|
||||||
let postData = parameters
|
let postData = parameters
|
||||||
|
|
||||||
|
|
@ -413,15 +241,14 @@ class WifiTransferService: ObservableObject {
|
||||||
completion(.success(data))
|
completion(.success(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// print(String(data: data, encoding: .utf8)!)
|
||||||
|
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make
|
// Make
|
||||||
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
|
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||||
print("\(#function) @Line: \(#line)")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
let password = "passw0rd"
|
let password = "passw0rd"
|
||||||
|
|
@ -444,20 +271,12 @@ class WifiTransferService: ObservableObject {
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
|
||||||
|
|
||||||
if let response = response as? HTTPURLResponse {
|
|
||||||
|
|
||||||
print("Response HTTP Status code: \(response.statusCode)")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("Write Directory Failure")
|
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let data = data {
|
if let data = data {
|
||||||
print("Write Directory Success")
|
|
||||||
completion(.success(data))
|
completion(.success(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,7 +301,7 @@ class WifiTransferService: ObservableObject {
|
||||||
let base64LoginString = loginData.base64EncodedString()
|
let base64LoginString = loginData.base64EncodedString()
|
||||||
|
|
||||||
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
|
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
|
||||||
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/test.txt")!,timeoutInterval: Double.infinity)
|
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/testing.txt")!,timeoutInterval: Double.infinity)
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||||
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
|
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Created by Trevor Beaton on 8/9/22.
|
// Created by Trevor Beaton on 8/9/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// My IP Address - 192.168.1.111
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
@ -13,10 +14,12 @@ struct WifiView: View {
|
||||||
|
|
||||||
@StateObject var viewModel = WifiViewModel()
|
@StateObject var viewModel = WifiViewModel()
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
|
@StateObject var wifiviewModel = WifiServiceManager()
|
||||||
// User Defaults
|
// User Defaults
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
|
|
||||||
|
@ObservedObject var networkModel = NetworkService()
|
||||||
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@EnvironmentObject var rootViewModel: RootViewModel
|
||||||
|
|
||||||
@State private var downloadState = DownloadState.idle
|
@State private var downloadState = DownloadState.idle
|
||||||
|
|
@ -25,58 +28,18 @@ struct WifiView: View {
|
||||||
@State private var boardBootInfo = "esp32-s2"
|
@State private var boardBootInfo = "esp32-s2"
|
||||||
@State var hostName = ""
|
@State var hostName = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@EnvironmentObject var test : ExpandedState
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@State var falseTog = false
|
|
||||||
|
|
||||||
@State var trueTog = true
|
|
||||||
|
|
||||||
@State private var showPopover: Bool = false
|
|
||||||
|
|
||||||
func toggleViewModelIP() {
|
|
||||||
viewModel.isInvalidIP.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func scanNetworkWifi() {
|
|
||||||
viewModel.wifiServiceManager.findService()
|
|
||||||
}
|
|
||||||
|
|
||||||
func printArray(array: [Any]) {
|
|
||||||
|
|
||||||
for i in array {
|
|
||||||
print("\(i)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkForStoredIPAddress() {
|
|
||||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
|
|
||||||
print("storeResolvedAddress - not stored")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
|
||||||
viewModel.ipAddressStored = true
|
|
||||||
print("storeResolvedAddress - is stored")
|
|
||||||
viewModel.connectionStatus = .connected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showValidationPrompt() {
|
func showValidationPrompt() {
|
||||||
alertTF(title: "Enter Device IP Address",
|
alertTF(title: "Enter Device IP Address",
|
||||||
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
|
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
|
||||||
hintText: "IP Address...",
|
hintText: "IP Address...",
|
||||||
primaryTitle: "Done",
|
primaryTitle: "Done",
|
||||||
secondaryTitle: "Cancel") { text in
|
secondaryTitle: "Cancel") { text in
|
||||||
viewModel.checkServices(ip: text)
|
viewModel.checkServices(ip: text)
|
||||||
|
|
||||||
} secondaryAction: {
|
} secondaryAction: {
|
||||||
print("Cancel")
|
print("Cancel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showAlertMessage() {
|
func showAlertMessage() {
|
||||||
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
|
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
|
||||||
|
|
@ -84,61 +47,106 @@ struct WifiView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initialIPStoreCheck() {
|
||||||
|
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
||||||
|
print("No IP address found.")
|
||||||
|
showValidationPrompt()
|
||||||
|
} else {
|
||||||
|
viewModel.connectionStatus = .connected
|
||||||
|
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
WifiHeaderView()
|
WifiHeaderView()
|
||||||
|
|
||||||
Group{
|
if wifiviewModel.isSearching {
|
||||||
switch viewModel.connectionStatus {
|
NetworkConnectionBanner()
|
||||||
case .connected:
|
} else {
|
||||||
WifiStatusConnectedView(hostName: $hostName)
|
|
||||||
case .noConnection:
|
|
||||||
WifiStatusNoConnectionView()
|
|
||||||
case .connecting:
|
|
||||||
WifiStatusConnectingView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
Group{
|
||||||
|
switch viewModel.connectionStatus {
|
||||||
|
case .connected:
|
||||||
|
WifiStatusConnectedView(hostName: $hostName)
|
||||||
|
case .noConnection:
|
||||||
|
WifiStatusNoConnectionView()
|
||||||
|
case .connecting:
|
||||||
|
WifiStatusConnectingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viewModel.ipAddressStored {
|
||||||
|
HStack(alignment: .center, content: {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
showValidationPrompt()
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Text("Enter IP address")
|
||||||
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(.indigo)
|
||||||
|
// .cornerRadius(15)
|
||||||
|
.padding(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
wifiviewModel.findService()
|
||||||
|
} label: {
|
||||||
|
Text("Scan Network")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(.indigo)
|
||||||
|
// .cornerRadius(15)
|
||||||
|
.padding(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
rootViewModel.goTobluetoothPairing()
|
||||||
|
} label: {
|
||||||
|
Text("BLE Mode")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(.indigo)
|
||||||
|
// .cornerRadius(15)
|
||||||
|
.padding(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.padding(.all, 0.0)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(maxHeight: 40)
|
||||||
|
.background(Color.clear)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
|
|
||||||
ScrollViewReader { scroll in
|
ScrollViewReader { scroll in
|
||||||
|
|
||||||
SubHeaderView()
|
SubHeaderView()
|
||||||
|
|
||||||
|
let check = networkModel.pdemos.filter {
|
||||||
let check = viewModel.pdemos.filter {
|
|
||||||
$0.compatibility.contains(boardBootInfo)
|
$0.compatibility.contains(boardBootInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(check) { demo in
|
ForEach(check) { demo in
|
||||||
|
|
||||||
if demo.bundleLink == test.currentCell {
|
WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||||
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
withAnimation {
|
||||||
|
scroll.scrollTo(demo.id)
|
||||||
|
|
||||||
})
|
|
||||||
.onAppear(){
|
|
||||||
|
|
||||||
withAnimation {
|
|
||||||
scroll.scrollTo(demo.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
|
||||||
withAnimation {
|
|
||||||
// scroll.scrollTo(demo.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -146,47 +154,36 @@ struct WifiView: View {
|
||||||
.id(self.scrollViewID)
|
.id(self.scrollViewID)
|
||||||
}
|
}
|
||||||
.foregroundColor(.black)
|
.foregroundColor(.black)
|
||||||
.environmentObject(test)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.connectionStatus, perform: { newValue in
|
.onChange(of: viewModel.connectionStatus, perform: { newValue in
|
||||||
if newValue == .connected {
|
if newValue == .connected {
|
||||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.onChange(of: viewModel.wifiServiceManager.resolvedServices, perform: { newValue in
|
|
||||||
print("Credential Check!")
|
|
||||||
print(newValue)
|
|
||||||
|
|
||||||
if newValue.contains(where: { result in
|
|
||||||
result.hostName == userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
|
||||||
}) {
|
|
||||||
print("Matched")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("Un-Matched")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
||||||
print("viewModel.isInvalidIP .onChange")
|
print("viewModel.isInvalidIP .onChange")
|
||||||
if newValue {
|
if newValue {
|
||||||
showAlertMessage()
|
showAlertMessage()
|
||||||
toggleViewModelIP()
|
viewModel.isInvalidIP.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
checkForStoredIPAddress()
|
print("On Appear")
|
||||||
viewModel.printStoredInfo()
|
networkModel.fetch()
|
||||||
viewModel.read()
|
|
||||||
|
// viewModel.checkStoredIP()
|
||||||
|
initialIPStoreCheck()
|
||||||
|
|
||||||
|
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
|
||||||
|
print("Nothing stored.")
|
||||||
|
} else {
|
||||||
|
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,23 +193,13 @@ struct WifiView: View {
|
||||||
struct WifiView_Previews: PreviewProvider {
|
struct WifiView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
WifiView()
|
WifiView()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
private static let kPrefix = Bundle.main.bundleIdentifier!
|
private static let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
public static let testNotificationName = Notification.Name(kPrefix+".testNotificationName")
|
|
||||||
public static let didUpdateState = Notification.Name(kPrefix+".test")
|
public static let didUpdateState = Notification.Name(kPrefix+".test")
|
||||||
public static let invalidIPNotif = Notification.Name(kPrefix+".invalidIPNotif")
|
public static let invalidIPNotif = Notification.Name(kPrefix+".invalidIPNotif")
|
||||||
public static let invalidCustomNetworkRequest = Notification.Name(kPrefix+".invalidCustomNetworkRequest")
|
public static let invalidCustomNetworkRequest = Notification.Name(kPrefix+".invalidCustomNetworkRequest")
|
||||||
public static let didCollectCustomProject = Notification.Name(kPrefix+".didCollectCustomProject")
|
public static let didCollectCustomProject = Notification.Name(kPrefix+".didCollectCustomProject")
|
||||||
public static let didEncounterZipError = Notification.Name(kPrefix+".didEncounterZipError")
|
|
||||||
public static let didCompleteZip = Notification.Name(kPrefix+".didCompleteZip")
|
|
||||||
public static let wifiDownloadComplete = Notification.Name(kPrefix+".wifiDownloadComplete")
|
|
||||||
public static let didCompleteTransfer = Notification.Name(kPrefix+".didCompleteTransfer")
|
|
||||||
public static let didEncounterTransferError = Notification.Name(kPrefix+".didEncounterTransferError")
|
|
||||||
public static let downloadErrorDidOccur = Notification.Name(kPrefix+".downloadErrorDidOccur")
|
|
||||||
public static let usbInUseErrorNotification = Notification.Name(kPrefix+".usbInUseErrorNotification")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
|
|
@ -22,33 +23,21 @@ class WifiViewModel: ObservableObject {
|
||||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
|
|
||||||
@Published var connectionStatus: ConnectionStatus = .noConnection
|
@Published var connectionStatus: ConnectionStatus = .noConnection
|
||||||
|
|
||||||
@Published var isInvalidIP = false
|
@Published var isInvalidIP = false
|
||||||
@Published var ipInputValidation = false
|
|
||||||
//Dependencies
|
//Dependencies
|
||||||
var networkMonitor = NetworkMonitor()
|
var networkMonitor = NetworkMonitor()
|
||||||
var networkAuth = LocalNetworkAuthorization()
|
var networkAuth = LocalNetworkAuthorization()
|
||||||
|
|
||||||
public var wifiNetworkService = WifiNetworkService()
|
public var wifiNetworkService = WifiNetworkService()
|
||||||
|
|
||||||
@Published var wifiTransferService = WifiTransferService()
|
@Published var wifiTransferService = WifiTransferService()
|
||||||
|
|
||||||
@Published var wifiServiceManager = WifiServiceManager()
|
@Published var wifiServiceManager = WifiServiceManager()
|
||||||
|
|
||||||
var circuitPythonVersion = Int()
|
|
||||||
|
|
||||||
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
||||||
|
|
||||||
@Published var hostName = ""
|
@Published var hostName = ""
|
||||||
|
|
||||||
@Published var downloadState: DownloadState = .idle
|
|
||||||
|
|
||||||
let dataStore = DataStore()
|
|
||||||
|
|
||||||
@Published var pdemos : [ResultItem] = []
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// File Manager Data
|
// File Manager Data
|
||||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
@Published var fileArray: [ContentFile] = []
|
@Published var fileArray: [ContentFile] = []
|
||||||
|
|
@ -56,47 +45,21 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
var projectDirectories: [URL] = []
|
var projectDirectories: [URL] = []
|
||||||
|
|
||||||
|
var returnedArray = [[String]]()
|
||||||
|
|
||||||
var ipAddressStored = false
|
var ipAddressStored = false
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
pdemos = dataStore.loadDefaultList()
|
|
||||||
checkIP()
|
checkIP()
|
||||||
registerNotifications(enabled: true)
|
registerNotifications(enabled: true)
|
||||||
wifiServiceManager.findService()
|
wifiServiceManager.findService()
|
||||||
//read()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a network call to populate our project list
|
|
||||||
func fetch() {
|
|
||||||
// networkModel.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var pyleapProjects = [ResultItem]()
|
|
||||||
|
|
||||||
|
|
||||||
func read() {
|
|
||||||
// This method can't be used until the device has permission to communicate.
|
|
||||||
print("READING CP Vers.")
|
|
||||||
wifiTransferService.getRequest(read: "boot_out.txt") { result in
|
|
||||||
|
|
||||||
if result.contains("CircuitPython 7") {
|
|
||||||
WifiCPVersion.versionNumber = 7
|
|
||||||
print("WifiCPVersion.versionNumber set to: \(WifiCPVersion.versionNumber)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.contains("CircuitPython 8") {
|
|
||||||
WifiCPVersion.versionNumber = 8
|
|
||||||
print("WifiCPVersion.versionNumber set to: \(WifiCPVersion.versionNumber)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private weak var invalidIPObserver: NSObjectProtocol?
|
|
||||||
|
|
||||||
private weak var testObserver: NSObjectProtocol?
|
private weak var testObserver: NSObjectProtocol?
|
||||||
|
private weak var invalidIPObserver: NSObjectProtocol?
|
||||||
|
|
||||||
|
|
||||||
private func registerNotifications(enabled: Bool) {
|
private func registerNotifications(enabled: Bool) {
|
||||||
let notificationCenter = NotificationCenter.default
|
let notificationCenter = NotificationCenter.default
|
||||||
|
|
||||||
|
|
@ -113,9 +76,6 @@ class WifiViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func checkIP() {
|
func checkIP() {
|
||||||
|
|
||||||
print("Tiggered checkIP")
|
print("Tiggered checkIP")
|
||||||
|
|
@ -124,7 +84,9 @@ class WifiViewModel: ObservableObject {
|
||||||
ipAddressStored = true
|
ipAddressStored = true
|
||||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||||
} else {
|
} else {
|
||||||
ipAddressStored = false
|
|
||||||
|
print(userDefaults.object(forKey: kPrefix+".storedIP"))
|
||||||
|
ipAddressStored = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -148,18 +110,16 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
// @Published var connectionStatus: ConnectionStatus = AppEnvironment.isRunningTests ? .connected : .noConnection
|
// @Published var connectionStatus: ConnectionStatus = AppEnvironment.isRunningTests ? .connected : .noConnection
|
||||||
|
|
||||||
|
func printIPStorageAtLocation() {
|
||||||
|
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||||
|
|
||||||
|
|
||||||
func printStoredInfo() {
|
|
||||||
print("======Stored UserDefaults======")
|
|
||||||
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func storeIPAddress(ipAddress: String) {
|
||||||
|
userDefaults.set(ipAddress, forKey: kPrefix+".storedIP" )
|
||||||
|
printIPStorageAtLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func storeResolvedAddress(service: ResolvedService) {
|
func storeResolvedAddress(service: ResolvedService) {
|
||||||
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
||||||
|
|
@ -175,6 +135,7 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
func clearKnownIPAddress() {
|
func clearKnownIPAddress() {
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
||||||
|
printIPStorageAtLocation()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,11 +149,14 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
let resolvedService = wifiServiceManager.resolvedServices.filter { $0.ipAddress == ip }
|
let resolvedService = wifiServiceManager.resolvedServices.filter { $0.ipAddress == ip }
|
||||||
|
|
||||||
|
|
||||||
// To store in UserDefaults
|
// To store in UserDefaults
|
||||||
|
|
||||||
|
|
||||||
storeResolvedAddress(service: resolvedService[0])
|
storeResolvedAddress(service: resolvedService[0])
|
||||||
|
storeIPAddress(ipAddress: ip)
|
||||||
connectionStatus = .connected
|
connectionStatus = .connected
|
||||||
ipInputValidation = true
|
|
||||||
} else {
|
} else {
|
||||||
isInvalidIP = true
|
isInvalidIP = true
|
||||||
print("1 does not exists in the array")
|
print("1 does not exists in the array")
|
||||||
|
|
@ -200,19 +164,16 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func checkStoredIP() {
|
func checkStoredIP() {
|
||||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
||||||
print("Nothing stored.")
|
print("Nothing stored.")
|
||||||
} else {
|
} else {
|
||||||
NotificationCenter.default.post(name: .invalidIPNotif, object: nil, userInfo: nil)
|
NotificationCenter.default.post(name: .invalidIPNotif, object: nil, userInfo: nil)
|
||||||
|
|
||||||
|
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public func internetMonitoring() {
|
public func internetMonitoring() {
|
||||||
|
|
||||||
networkMonitor.monitor.pathUpdateHandler = { path in
|
networkMonitor.monitor.pathUpdateHandler = { path in
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue