Compare commits
27 commits
Custom-pro
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02a0f52c0c | ||
|
|
598b340a2c | ||
|
|
bbf94b1b34 | ||
|
|
b546ad5040 | ||
|
|
7938077cd1 | ||
|
|
f848407bdf | ||
|
|
0433aaf55e | ||
|
|
4200663fc5 | ||
|
|
d837bfe230 | ||
|
|
0cc62bb5ce | ||
|
|
ea4e1114ad | ||
|
|
eeb34b5319 | ||
|
|
3ed8a4a792 | ||
|
|
78fffb0103 | ||
|
|
f2888a88a6 | ||
|
|
ee39036ce1 | ||
|
|
4e801cc48e | ||
|
|
3deada47e1 | ||
|
|
ac0d487172 | ||
|
|
d015b65a87 | ||
|
|
feb1762cce | ||
|
|
0f93300bb7 | ||
|
|
509cdb441b | ||
|
|
26a5168941 | ||
|
|
2354bfc786 | ||
|
|
2c57750f1a | ||
|
|
bebffcf86d |
62 changed files with 5245 additions and 3183 deletions
|
|
@ -27,6 +27,8 @@ 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
Normal file
25
LICENSE.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
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.
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
|
|
@ -11,6 +11,14 @@
|
||||||
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 */; };
|
||||||
|
|
@ -22,6 +30,8 @@
|
||||||
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 */; };
|
||||||
|
|
@ -43,6 +53,8 @@
|
||||||
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 */; };
|
||||||
|
|
@ -106,6 +118,14 @@
|
||||||
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; };
|
||||||
|
|
@ -115,6 +135,8 @@
|
||||||
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>"; };
|
||||||
|
|
@ -140,6 +162,8 @@
|
||||||
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>"; };
|
||||||
|
|
@ -163,6 +187,7 @@
|
||||||
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>"; };
|
||||||
|
|
@ -335,25 +360,42 @@
|
||||||
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 */,
|
||||||
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
|
|
||||||
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
|
|
||||||
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
|
|
||||||
D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */,
|
|
||||||
D567E2B728C137880009F768 /* WifiCell.swift */,
|
D567E2B728C137880009F768 /* WifiCell.swift */,
|
||||||
|
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */,
|
||||||
|
D567E2B928C1382E0009F768 /* WifiSubViewCell.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 */,
|
||||||
|
D5DD39A828D11962000FAEB8 /* WifiTransferService.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 */,
|
||||||
|
|
@ -370,7 +412,6 @@
|
||||||
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 */,
|
||||||
|
|
@ -428,6 +469,7 @@
|
||||||
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;
|
||||||
|
|
@ -448,9 +490,11 @@
|
||||||
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>";
|
||||||
|
|
@ -459,6 +503,7 @@
|
||||||
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;
|
||||||
|
|
@ -479,6 +524,7 @@
|
||||||
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";
|
||||||
|
|
@ -617,6 +663,7 @@
|
||||||
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 */,
|
||||||
|
|
@ -631,6 +678,7 @@
|
||||||
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 */,
|
||||||
|
|
@ -644,13 +692,17 @@
|
||||||
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 */,
|
||||||
|
|
@ -659,13 +711,16 @@
|
||||||
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 */,
|
||||||
|
|
@ -673,6 +728,7 @@
|
||||||
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 */,
|
||||||
|
|
@ -684,6 +740,7 @@
|
||||||
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 */,
|
||||||
|
|
@ -692,6 +749,7 @@
|
||||||
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;
|
||||||
|
|
@ -823,17 +881,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 = 14;
|
CURRENT_PROJECT_VERSION = 0;
|
||||||
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.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.1;
|
MARKETING_VERSION = 2.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;
|
||||||
|
|
@ -853,17 +911,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 = 14;
|
CURRENT_PROJECT_VERSION = 0;
|
||||||
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.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.1;
|
MARKETING_VERSION = 2.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,8 +9,6 @@ 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
|
||||||
|
|
@ -20,20 +18,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupAppearances() {
|
private func setupAppearances() {
|
||||||
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
|
// 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" : "0.769",
|
"blue" : "150",
|
||||||
"green" : "0.561",
|
"green" : "100",
|
||||||
"red" : "0.380"
|
"red" : "74"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.773",
|
"blue" : "150",
|
||||||
"green" : "0.561",
|
"green" : "100",
|
||||||
"red" : "0.380"
|
"red" : "74"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
||||||
38
PyLeap/Assets.xcassets/alt-gray.colorset/Contents.json
Normal file
38
PyLeap/Assets.xcassets/alt-gray.colorset/Contents.json
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
21
PyLeap/Assets.xcassets/bluetoothLogo.imageset/Contents.json
vendored
Normal file
21
PyLeap/Assets.xcassets/bluetoothLogo.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "bluetoothLogo.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
PyLeap/Assets.xcassets/bluetoothLogo.imageset/bluetoothLogo.png
vendored
Normal file
BIN
PyLeap/Assets.xcassets/bluetoothLogo.imageset/bluetoothLogo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
|
||||||
// Saving Download task reference for cancelling...
|
var manager = FileManager.default
|
||||||
@Published var downloadTaskSession: URLSessionDownloadTask!
|
|
||||||
|
|
||||||
// Show Progress View
|
// Show Progress View
|
||||||
@Published var downloadProgress: CGFloat = 0
|
@Published var downloadProgress: CGFloat = 0
|
||||||
|
|
@ -31,12 +31,232 @@ 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...")
|
||||||
|
|
@ -54,86 +274,19 @@ 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
|
|
||||||
|
|
||||||
// Download Task...
|
func testCallback(completion: ()->()) {
|
||||||
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
|
print("Do something")
|
||||||
|
|
||||||
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,
|
}
|
||||||
withIntermediateDirectories: true,
|
extension DownloadViewModel {
|
||||||
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() {
|
||||||
|
|
||||||
|
|
@ -182,89 +335,20 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Periodically informs the delegate about the download’s progress - Used for progress UI
|
func makeFileDirectory() {
|
||||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
// Creating a File Manager Object
|
||||||
// Getting Progress
|
|
||||||
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
|
|
||||||
print("Progress: \(numeralProgress)")
|
|
||||||
|
|
||||||
// Since URL Session will be running in the background thread
|
// Creating a folder
|
||||||
// UI will be done on the main thread
|
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
|
||||||
DispatchQueue.main.async {
|
|
||||||
|
|
||||||
self.downloadProgress = numeralProgress
|
|
||||||
print("Recorded downloadProgress Progress: \(self.downloadProgress)")
|
|
||||||
print("Recorded numeralProgress Progress: \(numeralProgress)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
do {
|
||||||
|
|
||||||
// Copy temp file to directory.
|
try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
|
||||||
try FileManager.default.copyItem(at: location, to: destinationURL)
|
withIntermediateDirectories: true,
|
||||||
|
attributes: [:])
|
||||||
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 {
|
} catch {
|
||||||
print(error)
|
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 Bluefruit Compatible 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 Adafruit Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
|
||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(PageTabViewStyle())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,6 @@
|
||||||
<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>
|
||||||
|
|
@ -33,6 +28,11 @@
|
||||||
<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,10 +64,6 @@
|
||||||
<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>
|
||||||
|
|
|
||||||
159
PyLeap/Model/DataStore.swift
Normal file
159
PyLeap/Model/DataStore.swift
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
//
|
||||||
|
// 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,3 +30,5 @@ struct ResultItem: Codable, Identifiable, Equatable {
|
||||||
let learnGuideLink: String
|
let learnGuideLink: String
|
||||||
let compatibility: [String]
|
let compatibility: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
15
PyLeap/Networking/JSONDecoderHelper.swift
Normal file
15
PyLeap/Networking/JSONDecoderHelper.swift
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// 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,221 +14,9 @@ import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class NetworkService: ObservableObject {
|
class NetworkService: ObservableObject {
|
||||||
|
let dataStore = DataStore()
|
||||||
|
|
||||||
static let shared = NetworkService()
|
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent)
|
||||||
|
|
||||||
@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?
|
||||||
|
|
||||||
|
|
@ -238,24 +26,16 @@ class NetworkService: ObservableObject {
|
||||||
|
|
||||||
// Session Configuration & Caching Policy
|
// Session Configuration & Caching Policy
|
||||||
let configuration = URLSessionConfiguration.default
|
let configuration = URLSessionConfiguration.default
|
||||||
// configuration.requestCachePolicy = .useProtocolCachePolicy
|
configuration.requestCachePolicy = .returnCacheDataElseLoad
|
||||||
|
|
||||||
return URLSession(configuration: configuration)
|
return URLSession(configuration: configuration)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@Published var projectInfo = Data()
|
|
||||||
|
|
||||||
func fetch() {
|
func fetch(completion: @escaping() -> Void) {
|
||||||
|
print("Attempting Network Request")
|
||||||
let cache = URLCache.shared
|
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 60.0)
|
||||||
|
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)")
|
||||||
|
|
@ -267,31 +47,34 @@ 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.load()
|
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList)
|
||||||
|
|
||||||
|
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.load()
|
self.dataStore.loadDefaultProjectList()
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchThirdParyProject(urlString: String?) {
|
|
||||||
let cache = URLCache.shared
|
func fetchThirdPartyProject(urlString: String?) {
|
||||||
|
|
||||||
|
thirdPartyBackgroundQueue.async {
|
||||||
|
|
||||||
guard let urlString = urlString else {
|
guard let urlString = urlString else {
|
||||||
print("Error")
|
print("\(#function) @Line: \(#line)")
|
||||||
|
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||||
|
print("Error urlString")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,8 +83,14 @@ class NetworkService: ObservableObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = URLRequest(url: URL(string: urlString)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
|
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.")
|
print("Making Network Request for Custom Project.")
|
||||||
|
|
||||||
|
|
@ -317,28 +106,23 @@ class NetworkService: ObservableObject {
|
||||||
|
|
||||||
if let data = data {
|
if let data = data {
|
||||||
|
|
||||||
print("Updating UIList with new data...")
|
let projectData = JSONDecoderHelper.decode(data: data) as RootResults?
|
||||||
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
|
|
||||||
|
|
||||||
if let projects = projectData?.projects {
|
if let projects = projectData?.projects {
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
print(#function)
|
self.dataStore.save(customProjects: projects, completion: self.dataStore.loadThirdPartyProjectsFromFileManager)
|
||||||
for i in projects {
|
|
||||||
print("\(i.projectName)")
|
|
||||||
}
|
}
|
||||||
// self.pdemos.append(contentsOf: projects)
|
|
||||||
self.saveCustomProjects(content: projects)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
41
PyLeap/Networking/Networking.swift
Normal file
41
PyLeap/Networking/Networking.swift
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// 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 }
|
||||||
|
}
|
||||||
BIN
PyLeap/Resource/AddedFont/ReadexPro/ReadexPro-SemiBold.ttf
Normal file
BIN
PyLeap/Resource/AddedFont/ReadexPro/ReadexPro-SemiBold.ttf
Normal file
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()
|
BlinkaAnimationView(height: 250, width: 250)
|
||||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||||
|
|
||||||
.onAppear(){
|
.onAppear(){
|
||||||
|
|
|
||||||
|
|
@ -29,41 +29,34 @@ struct BTConnectionView: View {
|
||||||
VStack{
|
VStack{
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
rootViewModel.goToSelection()
|
||||||
|
|
||||||
self.rootViewModel.goToMain()
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.backward")
|
Image(systemName: "arrow.backward")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.frame(width: 25, height: 25, alignment: .center)
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
.offset(y: 15)
|
||||||
.foregroundColor(Color("pyleap_gray"))
|
.foregroundColor(.black)
|
||||||
}
|
}
|
||||||
|
.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)
|
||||||
|
|
||||||
|
|
@ -76,19 +69,29 @@ struct BTConnectionView: View {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Text("Searching for PyLeap compatible device...")
|
Text("Bluetooth Connect")
|
||||||
.padding(.horizontal, 30)
|
.font(Font.custom("ReadexPro-Regular", size: 36))
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
.multilineTextAlignment(.center)
|
||||||
.minimumScaleFactor(0.1)
|
.minimumScaleFactor(0.01)
|
||||||
|
.lineLimit(1)
|
||||||
|
.padding()
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 30)
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
BlinkaAnimationView()
|
|
||||||
.minimumScaleFactor(0.1)
|
BlinkaAnimationView(height: 150, width: 145)
|
||||||
|
.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)
|
||||||
|
|
@ -98,6 +101,9 @@ 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
|
||||||
|
|
@ -222,10 +228,7 @@ 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())
|
||||||
|
|
@ -275,7 +278,7 @@ struct BTConnectionView: View {
|
||||||
let text: String
|
let text: String
|
||||||
switch model.connectionStatus {
|
switch model.connectionStatus {
|
||||||
case .scanning:
|
case .scanning:
|
||||||
text = "Scanning..."
|
text = "Scanning for PyLeap compatible devices..."
|
||||||
case .restoringConnection:
|
case .restoringConnection:
|
||||||
text = "Restoring connection..."
|
text = "Restoring connection..."
|
||||||
case .connecting:
|
case .connecting:
|
||||||
|
|
@ -292,11 +295,8 @@ 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: UIApplication.openSettingsURLString)!)
|
UIApplication.shared.open(URL(string: "App-Prefs:root=Bluetooth")!)
|
||||||
} label: {
|
} label: {
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,15 @@ struct FillerView: View {
|
||||||
Image("pyleapLogo")
|
Image("pyleapLogo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.offset(y: -20)
|
.offset(y: -30)
|
||||||
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
|
||||||
}
|
}
|
||||||
.preferredColorScheme(.light)
|
.preferredColorScheme(.light)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 30)
|
||||||
.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,12 +9,13 @@ import SwiftUI
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
|
|
||||||
class BleContentCommands {
|
class BleContentCommands: ObservableObject {
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -103,6 +104,8 @@ class BleContentCommands {
|
||||||
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))
|
||||||
|
|
@ -234,8 +237,10 @@ class BleContentCommands {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -252,9 +257,11 @@ class BleContentCommands {
|
||||||
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 SpotlightCounter: ObservableObject {
|
class ExpandedBLECellState: ObservableObject {
|
||||||
@Published var counter = 0
|
@Published var currentCell = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BleModuleView: View {
|
struct BleModuleView: View {
|
||||||
|
|
@ -25,51 +25,42 @@ 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()
|
@StateObject var viewModel = BleModuleViewModel()
|
||||||
@ObservedObject var networkServiceModel = NetworkService()
|
|
||||||
@StateObject var globalString = GlobalString()
|
|
||||||
@StateObject var btConnectionViewModel = BTConnectionViewModel()
|
|
||||||
@EnvironmentObject var rootViewModel: RootViewModel
|
@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
|
||||||
|
|
||||||
var body: some View {
|
@State var subviewHeight : CGFloat = 0
|
||||||
|
|
||||||
var connectedPeripherals = connectionManager.peripherals.filter{$0.state == .connected }
|
func showConfirmationPrompt() {
|
||||||
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
||||||
|
connectionManager.isDisconnectingFromCurrent = true
|
||||||
|
} cancel: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
//Start
|
//Start
|
||||||
|
|
@ -78,6 +69,7 @@ struct BleModuleView: View {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("bluetooth")
|
Image("bluetooth")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
|
|
@ -178,68 +170,139 @@ 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" {
|
||||||
Text("Connected to Circuit Playground Bluefruit")
|
HStack {
|
||||||
.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" {
|
|
||||||
Text("Connected to Adafruit CLUE")
|
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if boardBootInfo == "clue_nrf52840_express" {
|
||||||
|
VStack {
|
||||||
|
|
||||||
|
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("pyleap_green"))
|
.background(Color("adafruit_blue"))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: true) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
|
||||||
ScrollViewReader { scroll in
|
ScrollViewReader { scroll in
|
||||||
|
|
||||||
MainSubHeaderView()
|
if boardBootInfo == "clue_nrf52840_express" {
|
||||||
// .spotlight(enabled: spotlight.counter == 1, title: "1")
|
MainSubHeaderView(device: "Adafruit CLUE")
|
||||||
|
|
||||||
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, isConnected: $inConnectedInSelectionView, bootOne: $boardBootInfo, onViewGeometryChanged: {
|
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||||
|
})
|
||||||
|
.onAppear(){
|
||||||
|
print("Cell Appeared")
|
||||||
withAnimation {
|
withAnimation {
|
||||||
scroll.scrollTo(demo.id)
|
scroll.scrollTo(demo.id)
|
||||||
}
|
}
|
||||||
}, stateBinder: $downloadState)
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,143 +310,38 @@ 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?
|
||||||
|
|
||||||
|
|
@ -406,4 +364,9 @@ struct BleModuleView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ViewHeightKey: PreferenceKey {
|
||||||
|
static var defaultValue: CGFloat { 0 }
|
||||||
|
static func reduce(value: inout Value, nextValue: () -> Value) {
|
||||||
|
value = value + nextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,655 +11,28 @@ import FileTransferClient
|
||||||
|
|
||||||
class BleModuleViewModel: ObservableObject {
|
class BleModuleViewModel: ObservableObject {
|
||||||
|
|
||||||
@StateObject var globalString = GlobalString()
|
|
||||||
|
|
||||||
private weak var fileTransferClient: FileTransferClient?
|
private weak var fileTransferClient: FileTransferClient?
|
||||||
|
@StateObject var contentTransfer = BleContentTransfer()
|
||||||
|
|
||||||
@Published var entries = [BlePeripheral.DirectoryEntry]()
|
@Published var entries = [BlePeripheral.DirectoryEntry]()
|
||||||
@Published var isTransmiting = false
|
@Published var isTransmiting = false
|
||||||
@Published var bootUpInfo = ""
|
@Published var bootUpInfo = ""
|
||||||
|
|
||||||
var projectDirectories: [URL] = []
|
let dataStore = DataStore()
|
||||||
@Published var sendingBundle = false
|
|
||||||
@Published var didCompleteTranfer = false
|
|
||||||
@Published var writeError = false
|
|
||||||
|
|
||||||
|
@Published var pdemos : [ResultItem] = []
|
||||||
|
|
||||||
@Published var counter = 0
|
init() {
|
||||||
@Published var numOfFiles = 0
|
pdemos = dataStore.loadDefaultList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@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)")
|
||||||
|
|
||||||
|
|
@ -668,27 +41,19 @@ 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
|
||||||
|
|
@ -705,6 +70,10 @@ 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 {
|
||||||
|
|
@ -731,15 +100,6 @@ 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() {
|
||||||
|
|
@ -773,8 +133,10 @@ 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))
|
||||||
|
|
@ -906,10 +268,6 @@ 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
|
||||||
|
|
@ -990,6 +348,8 @@ class BleModuleViewModel: ObservableObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var sharedBootinfo = ""
|
||||||
|
|
||||||
enum ActiveAlert: Identifiable {
|
enum ActiveAlert: Identifiable {
|
||||||
case error(error: Error)
|
case error(error: Error)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,76 +8,83 @@
|
||||||
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
|
||||||
|
|
||||||
@Binding var downloadStateBinder: DownloadState
|
let result: ResultItem
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
@State var offlineWithoutProject = false
|
func showAlertMessage() {
|
||||||
|
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(image)
|
ImageWithURL(result.projectImage)
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
|
||||||
Text(description)
|
Text(result.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)
|
||||||
|
|
@ -87,7 +94,7 @@ struct DemoSubview: View {
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
|
||||||
ForEach(compatibility, id: \.self) { string in
|
ForEach(result.compatibility, id: \.self) { string in
|
||||||
if string == "circuitplayground_bluefruit" {
|
if string == "circuitplayground_bluefruit" {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -123,14 +130,19 @@ struct DemoSubview: View {
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, 30)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
if !viewModel.isConnectedToInternet {
|
||||||
|
showAlertMessage()
|
||||||
|
} else {
|
||||||
showWebViewPopover = true
|
showWebViewPopover = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
LearnGuideButton()
|
LearnGuideButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showWebViewPopover, content: {
|
.sheet(isPresented: $showWebViewPopover, content: {
|
||||||
WebView(URLRequest(url: learnGuideLink.url!))
|
SwiftUIWebView(webAddress: result.learnGuideLink)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -138,92 +150,50 @@ struct DemoSubview: View {
|
||||||
|
|
||||||
if isConnected {
|
if isConnected {
|
||||||
|
|
||||||
if compatibility.contains(bindingString) {
|
if result.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: {
|
||||||
|
|
||||||
downloadStateBinder = .transferring
|
/// Condition: Connected to the internet
|
||||||
globalString.isSendingG = true
|
///- If you're not connected to the internet, but you've downloaded the project...
|
||||||
globalString.counterG = 0
|
/// - If you're not connected to the internet, and you're project is not downloaded...
|
||||||
globalString.numberOfFilesG = 1
|
/// *Show Alert*
|
||||||
|
|
||||||
|
|
||||||
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 downloadStateBinder == .failed {
|
if contentTransfer.downloadState == .failed {
|
||||||
|
|
||||||
FailedButton()
|
FailedButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contentTransfer.downloadState == .transferring {
|
||||||
if downloadStateBinder == .transferring {
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
|
|
||||||
print("Project Selected: \(title) - DemoSubView")
|
|
||||||
|
|
||||||
globalString.projectString = title
|
|
||||||
globalString.numberOfTimesDownloaded += 1
|
|
||||||
|
|
||||||
}) {
|
|
||||||
|
|
||||||
DownloadingButton()
|
DownloadingButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
||||||
|
VStack(alignment: .center, spacing: 5) {
|
||||||
|
ProgressView("", value: CGFloat(contentTransfer.counter), total: CGFloat(contentTransfer.numOfFiles) )
|
||||||
if globalString.isSendingG {
|
|
||||||
|
|
||||||
VStack(alignment: .center, spacing: 0) {
|
|
||||||
ProgressView("", value: CGFloat(globalString.counterG), total: CGFloat(globalString.numberOfFilesG) )
|
|
||||||
.padding(.horizontal, 90)
|
.padding(.horizontal, 90)
|
||||||
.padding(.top, -8)
|
.padding(.top, -8)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
|
|
@ -234,27 +204,18 @@ struct DemoSubview: View {
|
||||||
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contentTransfer.downloadState == .complete {
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if downloadStateBinder == .complete {
|
|
||||||
|
|
||||||
CompleteButton()
|
CompleteButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
rootViewModel.goTobluetoothPairing()
|
rootViewModel.goToSelection()
|
||||||
} label: {
|
} label: {
|
||||||
ConnectButton()
|
ConnectButton()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
@ -266,56 +227,46 @@ struct DemoSubview: View {
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
|
.onAppear(){
|
||||||
|
|
||||||
|
print("On Appear")
|
||||||
|
contentTransfer.contentCommands.setup(fileTransferClient: connectionManager.selectedClient)
|
||||||
|
|
||||||
.alert("Project Not Found", isPresented: $offlineWithoutProject) {
|
// viewModel.readFile(filename: "boot_out.txt")
|
||||||
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
|
.onChange(of: contentTransfer.transferError, 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 {
|
if newValue {
|
||||||
DispatchQueue.main.async {
|
showTransferErrorMessage()
|
||||||
print("Getting project from Subclass \(title)")
|
|
||||||
viewModel.getProjectForSubClass(nameOf: title)
|
|
||||||
isDownloaded = true
|
|
||||||
}
|
}
|
||||||
}else {
|
|
||||||
print("Is not downloaded")
|
|
||||||
isDownloaded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.onChange(of: contentTransfer.downloaderror, perform: { newValue in
|
||||||
|
if newValue {
|
||||||
|
showDownloadErrorMessage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// .onChange(of: connectionManager.selectedClient) { selectedClient in
|
||||||
|
// viewModel.setup(fileTransferClient: selectedClient)
|
||||||
|
// }
|
||||||
|
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
viewModel.getProjectForSubClass(nameOf: title)
|
|
||||||
if viewModel.projectDownloaded {
|
|
||||||
isDownloaded = true
|
|
||||||
} else {
|
|
||||||
isDownloaded = false
|
|
||||||
}
|
|
||||||
print("is downloaded? \(isDownloaded)")
|
|
||||||
})
|
|
||||||
.padding(.top, 8)
|
|
||||||
|
|
||||||
|
contentTransfer.readMyStatus()
|
||||||
|
viewModel.searchPathForProject(nameOf: result.projectName)
|
||||||
|
|
||||||
|
if viewModel.projectDownloaded {
|
||||||
|
viewModel.projectDownloaded = true
|
||||||
|
} else {
|
||||||
|
viewModel.projectDownloaded = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
.padding(.top, 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,21 @@ 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 bootOne: String
|
@Binding var deviceInfo: 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)
|
||||||
|
|
@ -35,19 +33,10 @@ struct DemoViewCell: View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
header
|
header
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
DemoSubview(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
|
DemoSubview(bindingString: $deviceInfo, result: result, isConnected: $isConnected)
|
||||||
image: result.projectImage,
|
|
||||||
description: result.description,
|
|
||||||
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
|
|
||||||
downloadLink: result.bundleLink,
|
|
||||||
compatibility: result.compatibility,
|
|
||||||
isConnected: $isConnected)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +64,10 @@ struct DemoViewCell: View {
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.background(Color("pyleap_purple"))
|
.background(Color("pyleap_purple"))
|
||||||
.onTapGesture { isExpanded.toggle() }
|
.onTapGesture {
|
||||||
|
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,37 +11,74 @@ 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
|
||||||
|
|
||||||
func getProjectForSubClass(nameOf project: String) {
|
var networkMonitor = NetworkMonitor()
|
||||||
|
|
||||||
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
init() {
|
||||||
// for case condition: Only process URLs
|
internetMonitoring()
|
||||||
for case let fileURL as URL in enumerator {
|
}
|
||||||
|
|
||||||
if fileURL.lastPathComponent == project {
|
func deleteStoredFilesInFM () {
|
||||||
failedProjectLaunch = false
|
print("\(#function) @Line: \(#line)")
|
||||||
|
do {
|
||||||
|
try manager.removeItem(at: directoryPath)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(projectWith title: String) {
|
||||||
|
|
||||||
|
let nestedFolderURL = directoryPath.appendingPathComponent(title)
|
||||||
|
|
||||||
|
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
|
||||||
|
print("\(title) - Exists")
|
||||||
projectDownloaded = true
|
projectDownloaded = true
|
||||||
print(#function)
|
|
||||||
print("Searching for... \(project)")
|
|
||||||
print("URL Path: \(fileURL.path)")
|
|
||||||
print("URL : \(fileURL)")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
failedProjectLaunch = true
|
print("\(title) - Does not exist.")
|
||||||
projectDownloaded = false
|
projectDownloaded = false
|
||||||
print("Project was not found...")
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,6 +11,8 @@ 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
|
||||||
|
|
||||||
|
|
@ -19,7 +21,7 @@ struct RootView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
Group{
|
Group {
|
||||||
switch model.destination {
|
switch model.destination {
|
||||||
|
|
||||||
case .onboard :
|
case .onboard :
|
||||||
|
|
@ -43,15 +45,35 @@ 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 {
|
||||||
|
|
@ -75,6 +97,20 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,6 +118,7 @@ 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)
|
||||||
|
|
@ -89,6 +126,7 @@ struct RootView: View {
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
.preferredColorScheme(.light)
|
.preferredColorScheme(.light)
|
||||||
|
.statusBar(hidden: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import FileTransferClient
|
import FileTransferClient
|
||||||
|
|
||||||
class RootViewModel: ObservableObject {
|
public class RootViewModel: ObservableObject {
|
||||||
|
|
||||||
|
// public var shared = RootViewModel()
|
||||||
|
|
||||||
enum Destination {
|
enum Destination {
|
||||||
//case splash
|
//case splash
|
||||||
|
|
@ -20,7 +22,13 @@ 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
|
||||||
|
|
@ -30,6 +38,18 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +58,10 @@ class RootViewModel: ObservableObject {
|
||||||
destination = .bluetoothPairing
|
destination = .bluetoothPairing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func goToSelection(){
|
||||||
|
destination = .selection
|
||||||
|
}
|
||||||
|
|
||||||
func goToMainSelection(){
|
func goToMainSelection(){
|
||||||
destination = .mainSelection
|
destination = .mainSelection
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +76,10 @@ class RootViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func backToMain() {
|
||||||
|
destination = .main
|
||||||
|
}
|
||||||
|
|
||||||
func goToStartup(){
|
func goToStartup(){
|
||||||
destination = .startup
|
destination = .startup
|
||||||
}
|
}
|
||||||
|
|
@ -64,10 +92,14 @@ class RootViewModel: ObservableObject {
|
||||||
destination = .fileTransfer
|
destination = .fileTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToSettings(){
|
func goToSettings(content: SettingState){
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1,317 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -16,13 +16,16 @@ 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: 300, height: 300)
|
.frame(width: width, height: height)
|
||||||
.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)
|
||||||
|
|
||||||
|
|
@ -34,6 +37,6 @@ struct BlinkaAnimationView: View {
|
||||||
|
|
||||||
struct ScanningView_Previews: PreviewProvider {
|
struct ScanningView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
BlinkaAnimationView()
|
BlinkaAnimationView(height: 300, width: 300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ struct RunItButton: View {
|
||||||
.cornerRadius(25)
|
.cornerRadius(25)
|
||||||
.foregroundColor(Color("pyleap_pink"))
|
.foregroundColor(Color("pyleap_pink"))
|
||||||
|
|
||||||
Text("Run it!")
|
Text("Run")
|
||||||
.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("pyleap_pink"))
|
.foregroundColor(Color("bluetooth_button_color"))
|
||||||
|
|
||||||
Text("Pairing Tutorial")
|
Text("Pairing Tutorial")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||||
|
|
|
||||||
|
|
@ -10,50 +10,44 @@ 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 {
|
|
||||||
|
|
||||||
Button {
|
HStack (alignment: .center, spacing: 0) {
|
||||||
rootViewModel.goToWifiView()
|
|
||||||
} label: {
|
Image(systemName: "gearshape")
|
||||||
Image(systemName: "wifi.circle")
|
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
.frame(width: 25, height: 25, alignment: .center)
|
||||||
.offset(y: 15)
|
.offset(y: 15)
|
||||||
.foregroundColor(.white)
|
.padding(.leading, CGFloat(20))
|
||||||
|
.foregroundColor(.clear)
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
|
|
||||||
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 {
|
||||||
self.showSheetView.toggle()
|
rootViewModel.goToBLESettings()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "plus")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
.frame(width: 25, height: 25, alignment: .center)
|
||||||
.offset(y: 15)
|
.offset(y: 15)
|
||||||
|
.padding(.trailing, CGFloat(20))
|
||||||
.foregroundColor(.white)
|
.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"))
|
||||||
|
|
@ -107,6 +101,7 @@ 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 Wi-Fi PyLeap Projects")
|
Text("Browse available WiFi 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,10 +22,14 @@ struct SubHeaderView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainSubHeaderView: View {
|
struct MainSubHeaderView: View {
|
||||||
|
let device: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Text("Browse available PyLeap Projects")
|
Text("Pick a project to run on your \(device)")
|
||||||
.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,6 +8,7 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -15,41 +16,38 @@ struct WifiHeaderView: View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Button {
|
|
||||||
rootViewModel.goToMain()
|
HStack (alignment: .center, spacing: 0) {
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.backward")
|
Image(systemName: "gearshape")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
.frame(width: 25, height: 25, alignment: .center)
|
||||||
.offset(y: 15)
|
.offset(y: 15)
|
||||||
.foregroundColor(.white)
|
.padding(.leading, CGFloat(20))
|
||||||
}
|
.foregroundColor(.clear)
|
||||||
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
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()
|
rootViewModel.goToSettings(content: .wifi)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "gearshape")
|
Image(systemName: "plus")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
.frame(width: 25, height: 25, alignment: .center)
|
||||||
.offset(y: 15)
|
.offset(y: 15)
|
||||||
|
.padding(.trailing, CGFloat(20))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,14 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,7 +30,6 @@ struct MainSelectionView: View {
|
||||||
@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
|
||||||
|
|
@ -41,30 +39,11 @@ struct MainSelectionView: View {
|
||||||
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.goTobluetoothPairing()
|
rootViewModel.goToSelection()
|
||||||
} label: {
|
} label: {
|
||||||
Text("Connect Now")
|
Text("Connect Now")
|
||||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||||
|
|
@ -78,14 +57,11 @@ struct MainSelectionView: View {
|
||||||
.background(Color("pyleap_burg"))
|
.background(Color("pyleap_burg"))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
||||||
MainSubHeaderView()
|
MainSubHeaderView(device: "Adafruit device")
|
||||||
|
|
||||||
if networkModel.pdemos.isEmpty {
|
if viewModel.pdemos.isEmpty {
|
||||||
HStack{
|
HStack{
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
|
@ -98,18 +74,42 @@ struct MainSelectionView: View {
|
||||||
|
|
||||||
ScrollViewReader { scroll in
|
ScrollViewReader { scroll in
|
||||||
|
|
||||||
ForEach(networkModel.pdemos) { demo in
|
|
||||||
DemoViewCell(result: demo, isConnected: $isConnected, bootOne: $test, onViewGeometryChanged: {
|
|
||||||
|
|
||||||
|
ForEach(viewModel.pdemos) { demo in
|
||||||
|
|
||||||
|
if demo.bundleLink == expandedState.currentCell {
|
||||||
|
|
||||||
|
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||||
|
})
|
||||||
|
.onAppear(){
|
||||||
|
print("Cell Appeared")
|
||||||
withAnimation {
|
withAnimation {
|
||||||
scroll.scrollTo(demo.id)
|
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,7 +144,9 @@ struct MainSelectionView: View {
|
||||||
|
|
||||||
})
|
})
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
networkModel.fetch()
|
|
||||||
|
|
||||||
|
print("Opened MainSelectionView")
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
|
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
|
||||||
|
|
@ -156,12 +158,89 @@ struct MainSelectionView: View {
|
||||||
.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,44 +7,82 @@
|
||||||
|
|
||||||
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() {
|
||||||
// load()
|
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeNetworkCall(){
|
} else {
|
||||||
networkModel.fetch()
|
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 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 []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
PyLeap/Views/Unpaired View/SelectionView.swift
Normal file
121
PyLeap/Views/Unpaired View/SelectionView.swift
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
//
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
182
PyLeap/Views/Unpaired View/WifiSelection.swift
Normal file
182
PyLeap/Views/Unpaired View/WifiSelection.swift
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
//
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
//
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
//
|
||||||
|
// 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" )
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
243
PyLeap/Views/Wifi View/SettingsView/SettingsView.swift
Normal file
243
PyLeap/Views/Wifi View/SettingsView/SettingsView.swift
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
//
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ 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)
|
||||||
|
|
@ -56,10 +57,9 @@ errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkReq
|
||||||
|
|
||||||
func check() {
|
func check() {
|
||||||
print(#function)
|
print(#function)
|
||||||
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
||||||
connectedToDevice = false
|
connectedToDevice = false
|
||||||
} else {
|
} else {
|
||||||
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
|
||||||
|
|
||||||
connectedToDevice = true
|
connectedToDevice = true
|
||||||
|
|
||||||
|
|
@ -8,22 +8,37 @@
|
||||||
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 private var isExpanded: Bool = false {
|
@State 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 {
|
||||||
|
|
@ -31,27 +46,7 @@ struct WifiCell: View {
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var content: some View {
|
var header: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
header
|
|
||||||
|
|
||||||
if isExpanded {
|
|
||||||
|
|
||||||
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 {
|
HStack {
|
||||||
Text(result.projectName)
|
Text(result.projectName)
|
||||||
|
|
@ -63,16 +58,41 @@ struct WifiCell: View {
|
||||||
|
|
||||||
Image(systemName: "chevron.down")
|
Image(systemName: "chevron.down")
|
||||||
.resizable()
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
.frame(width: 30, height: 15, alignment: .center)
|
.frame(width: 30, height: 15, alignment: .center)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.trailing, 30)
|
.padding(.trailing, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.background(Color("pyleap_purple"))
|
.background(Color("pyleap_purple"))
|
||||||
.onTapGesture { isExpanded.toggle() }
|
.onTapGesture {
|
||||||
|
expandedState.currentCell = result.bundleLink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var content: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
header
|
||||||
|
|
||||||
|
if isExpanded {
|
||||||
|
Group {
|
||||||
|
WifiSubViewCell(result: result, bindingString: $bootOne, downloadStateBinder: $stateBinder,isConnected: $isConnected)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
PyLeap/Views/Wifi View/WifiCellViewModel.swift
Normal file
27
PyLeap/Views/Wifi View/WifiCellViewModel.swift
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// 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
163
PyLeap/Views/Wifi View/WifiPairingView.swift
Normal file
163
PyLeap/Views/Wifi View/WifiPairingView.swift
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
//
|
||||||
|
// 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,7 +8,8 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ResolvedService {
|
struct ResolvedService: Identifiable, Equatable {
|
||||||
|
var id = UUID()
|
||||||
var ipAddress: String
|
var ipAddress: String
|
||||||
var hostName: String
|
var hostName: String
|
||||||
var device: String
|
var device: String
|
||||||
|
|
@ -37,36 +38,58 @@ class WifiServiceManager: NSObject, ObservableObject {
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
print("Wifi Module Used")
|
||||||
serviceManagerBrowser.delegate = self
|
serviceManagerBrowser.delegate = self
|
||||||
findService()
|
findService()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findService() {
|
deinit {
|
||||||
print("Start Scan")
|
print("Wifi Module Removed")
|
||||||
// self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
self.serviceManagerBrowser.stop()
|
||||||
resolvedServices.removeAll()
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func findService() {
|
||||||
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() {
|
||||||
// connectionStatus = .connected
|
print("Start Discovery")
|
||||||
print("Start Scan")
|
DispatchQueue.main.async {
|
||||||
isSearching = true
|
self.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 {
|
||||||
|
|
||||||
|
|
@ -88,11 +111,26 @@ 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: 10)
|
discoveredService?.resolve(withTimeout: 7)
|
||||||
|
|
||||||
if services.contains(service) {
|
if services.contains(service) {
|
||||||
print("All ready in service array")
|
print("All ready in service array")
|
||||||
|
|
@ -101,13 +139,22 @@ 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)")
|
||||||
|
|
@ -136,7 +183,16 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
resolvedServices.append(resolvedService)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
print("resolvedServices count: \(resolvedServices.count)")
|
print("resolvedServices count: \(resolvedServices.count)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -148,17 +204,7 @@ 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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
293
PyLeap/Views/Wifi View/WifiServiceSelectionView.swift
Normal file
293
PyLeap/Views/Wifi View/WifiServiceSelectionView.swift
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
//
|
||||||
|
// 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,14 +12,39 @@ 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
|
||||||
|
|
||||||
var body: some View {
|
func showConfirmationPrompt() {
|
||||||
HStack(alignment: .center, spacing: 8, content: {
|
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
||||||
|
rootViewModel.goToSelection()
|
||||||
|
} cancel: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center, spacing: 0, 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)
|
||||||
|
|
@ -34,7 +59,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-Regular", size: 16))
|
.font(Font.custom("ReadexPro-SemiBold", size: 14))
|
||||||
|
|
||||||
})
|
})
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
|
|
@ -50,7 +75,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: 16))
|
.font(Font.custom("ReadexPro-Regular", size: 14))
|
||||||
})
|
})
|
||||||
.padding(.all, 0.0)
|
.padding(.all, 0.0)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
@ -68,7 +93,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: 16))
|
.font(Font.custom("ReadexPro-Regular", size: 14))
|
||||||
|
|
||||||
// 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,18 +23,17 @@ 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
|
||||||
|
|
@ -42,6 +41,100 @@ 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 {
|
||||||
|
|
||||||
|
|
@ -49,14 +142,14 @@ struct WifiSubViewCell: View {
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0, content: {
|
VStack(alignment: .leading, spacing: 0, content: {
|
||||||
|
|
||||||
ImageWithURL(image)
|
ImageWithURL(result.projectImage)
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
|
|
||||||
Text(description)
|
Text(result.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)
|
||||||
|
|
@ -65,8 +158,19 @@ struct WifiSubViewCell: View {
|
||||||
.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 {
|
||||||
|
|
@ -109,7 +213,7 @@ struct WifiSubViewCell: View {
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showWebViewPopover, content: {
|
.sheet(isPresented: $showWebViewPopover, content: {
|
||||||
WebView(URLRequest(url: learnGuideLink.url!))
|
SwiftUIWebView(webAddress: result.learnGuideLink)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -117,18 +221,16 @@ struct WifiSubViewCell: View {
|
||||||
|
|
||||||
if isConnected {
|
if isConnected {
|
||||||
|
|
||||||
if compatibility.contains(bindingString) {
|
if result.compatibility.contains(bindingString) {
|
||||||
|
|
||||||
|
|
||||||
|
if wifiFileTransfer.testIndex.downloadState == .idle {
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
print("Wifi Project Attempt \(title)")
|
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
|
||||||
|
|
||||||
if wifiFileTransfer.projectDownloaded {
|
testOperation()
|
||||||
wifiFileTransfer.projectValidation(nameOf: title)
|
|
||||||
} else {
|
|
||||||
downloadModel.startDownload(urlString: downloadLink, projectTitle: title) {
|
|
||||||
print("DONE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
RunItButton()
|
RunItButton()
|
||||||
|
|
@ -137,9 +239,41 @@ struct WifiSubViewCell: View {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
@ -151,62 +285,88 @@ struct WifiSubViewCell: View {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.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
|
|
||||||
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)
|
|
||||||
|
|
||||||
isDownloaded = true
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
print("Is not downloaded")
|
|
||||||
isDownloaded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
wifiFileTransfer.getProjectForSubClass(nameOf: title)
|
|
||||||
if wifiFileTransfer.projectDownloaded {
|
wifiFileTransfer.registerWifiNotification(enabled: true)
|
||||||
|
|
||||||
|
viewModel.searchPathForProject(nameOf: result.projectName)
|
||||||
|
|
||||||
|
|
||||||
|
if viewModel.projectDownloaded {
|
||||||
isDownloaded = true
|
isDownloaded = true
|
||||||
} else {
|
} else {
|
||||||
isDownloaded = false
|
isDownloaded = false
|
||||||
}
|
}
|
||||||
print("is downloaded? \(isDownloaded)")
|
print("is downloaded? \(viewModel.projectDownloaded)")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
|
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
|
||||||
|
if newValue {
|
||||||
|
showTransferErrorMessage()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.onChange(of: viewModel.showUsbInUseError) { newValue in
|
||||||
|
if newValue {
|
||||||
|
usbInUseErrorMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.onChange(of: wifiFileTransfer.counter) { newValue in
|
||||||
|
print("New counter : \(newValue)")
|
||||||
|
counter = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
.onChange(of: wifiFileTransfer.numOfFiles) { newValue in
|
||||||
|
print("New numOfFiles : \(newValue)")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,40 +6,119 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
@Published var usbInUse = false
|
||||||
|
@Published var showUsbInUseError = false
|
||||||
|
|
||||||
func getProjectForSubClass(nameOf project: String) {
|
|
||||||
|
|
||||||
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
init() {
|
||||||
// for case condition: Only process URLs
|
registerForUSBInUseErrorNotification(enabled: true)
|
||||||
for case let fileURL as URL in enumerator {
|
}
|
||||||
|
|
||||||
if fileURL.lastPathComponent == project {
|
private weak var usbInUseErrorNotification: NSObjectProtocol?
|
||||||
failedProjectLaunch = false
|
|
||||||
projectDownloaded = true
|
|
||||||
print(#function)
|
|
||||||
print("Searching for... \(project)")
|
|
||||||
print("URL Path: \(fileURL.path)")
|
|
||||||
print("URL : \(fileURL)")
|
|
||||||
|
|
||||||
return
|
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 {
|
} else {
|
||||||
failedProjectLaunch = true
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
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
|
projectDownloaded = false
|
||||||
print("Project was not found...")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
print("\(#function) @Line: \(#line)")
|
||||||
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,6 +43,7 @@ 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) {
|
||||||
|
|
@ -76,10 +77,7 @@ class WifiTransferService: ObservableObject {
|
||||||
print("Print curl:")
|
print("Print curl:")
|
||||||
print(request.cURL(pretty: true))
|
print(request.cURL(pretty: true))
|
||||||
|
|
||||||
let task = urlSession.dataTask(
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
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")
|
||||||
|
|
@ -92,14 +90,75 @@ class WifiTransferService: ObservableObject {
|
||||||
print("File write success!")
|
print("File write success!")
|
||||||
handler(.success(data))
|
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() {
|
||||||
|
|
@ -116,8 +175,11 @@ 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:")
|
||||||
|
|
@ -147,8 +209,13 @@ class WifiTransferService: ObservableObject {
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRequest(incoming: String) -> String {
|
//completion: @escaping (Bool) -> Void
|
||||||
|
|
||||||
|
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 = ""
|
||||||
|
|
@ -157,13 +224,14 @@ class WifiTransferService: ObservableObject {
|
||||||
|
|
||||||
var outgoingString = String()
|
var outgoingString = String()
|
||||||
|
|
||||||
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
|
let loginData = loginString.data(using: String.Encoding.utf8)
|
||||||
return "Error"
|
|
||||||
}
|
|
||||||
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://\(hostName).local/fs/\(incoming)")!,timeoutInterval: Double.infinity)
|
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("application/json", forHTTPHeaderField: "Accept")
|
||||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
|
|
@ -175,13 +243,11 @@ 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)"))
|
||||||
semaphore.signal()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// print(String(data: data, encoding: .utf8)!)
|
|
||||||
semaphore.signal()
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
print("In do-catch loop of getRequest")
|
||||||
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
@ -192,18 +258,124 @@ class WifiTransferService: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let str = String(data: data, encoding: .utf8) {
|
if let str = String(data: data, encoding: .utf8) {
|
||||||
print(str)
|
print("Out-going getRequest data: \(str)")
|
||||||
outgoingString = str
|
outgoingString = str
|
||||||
|
print("\(#function) @Line: \(#line)")
|
||||||
|
completionHandler(outgoingString)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("\(#function) @Line: \(#line)")
|
||||||
|
print("Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
semaphore.wait()
|
|
||||||
return outgoingString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// print(String(data: data, encoding: .utf8)!)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.webDirectoryInfo = wifiIncomingData
|
||||||
|
completionHandler(self.webDirectoryInfo)
|
||||||
|
print("On completion \(self.webDirectoryInfo)")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let str = String(data: data, encoding: .utf8) {
|
||||||
|
print(str)
|
||||||
|
outgoingString = str
|
||||||
|
print("\(#function) @Line: \(#line)")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("\(#function) @Line: \(#line)")
|
||||||
|
print("Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
|
@ -241,14 +413,15 @@ 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"
|
||||||
|
|
@ -271,12 +444,20 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,7 +482,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/testing.txt")!,timeoutInterval: Double.infinity)
|
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/test.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,7 +5,6 @@
|
||||||
// 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
|
||||||
|
|
@ -14,12 +13,10 @@ 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
|
||||||
|
|
@ -28,6 +25,46 @@ 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",
|
||||||
|
|
@ -47,29 +84,11 @@ 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()
|
||||||
|
|
||||||
if wifiviewModel.isSearching {
|
|
||||||
NetworkConnectionBanner()
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Group{
|
Group{
|
||||||
switch viewModel.connectionStatus {
|
switch viewModel.connectionStatus {
|
||||||
case .connected:
|
case .connected:
|
||||||
|
|
@ -81,109 +100,93 @@ struct WifiView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viewModel.ipAddressStored {
|
|
||||||
HStack(alignment: .center, content: {
|
|
||||||
|
|
||||||
Button {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
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
|
||||||
|
|
||||||
WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
if demo.bundleLink == test.currentCell {
|
||||||
|
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.onAppear(){
|
||||||
|
|
||||||
withAnimation {
|
withAnimation {
|
||||||
scroll.scrollTo(demo.id)
|
scroll.scrollTo(demo.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||||
|
withAnimation {
|
||||||
|
// scroll.scrollTo(demo.id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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.isInvalidIP, perform: { newValue in
|
|
||||||
print("viewModel.isInvalidIP .onChange")
|
.onChange(of: viewModel.wifiServiceManager.resolvedServices, perform: { newValue in
|
||||||
if newValue {
|
print("Credential Check!")
|
||||||
showAlertMessage()
|
print(newValue)
|
||||||
viewModel.isInvalidIP.toggle()
|
|
||||||
|
if newValue.contains(where: { result in
|
||||||
|
result.hostName == userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||||
|
}) {
|
||||||
|
print("Matched")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("Un-Matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.onAppear(){
|
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
||||||
print("On Appear")
|
print("viewModel.isInvalidIP .onChange")
|
||||||
networkModel.fetch()
|
if newValue {
|
||||||
|
showAlertMessage()
|
||||||
// viewModel.checkStoredIP()
|
toggleViewModelIP()
|
||||||
initialIPStoreCheck()
|
|
||||||
|
|
||||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
|
|
||||||
print("Nothing stored.")
|
|
||||||
} else {
|
|
||||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
.onAppear(){
|
||||||
|
checkForStoredIPAddress()
|
||||||
|
viewModel.printStoredInfo()
|
||||||
|
viewModel.read()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,13 +196,23 @@ 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,7 +7,6 @@
|
||||||
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
|
|
@ -23,21 +22,33 @@ 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] = []
|
||||||
|
|
@ -45,21 +56,47 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
private weak var testObserver: NSObjectProtocol?
|
/// 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 invalidIPObserver: NSObjectProtocol?
|
||||||
|
|
||||||
|
private weak var testObserver: NSObjectProtocol?
|
||||||
private func registerNotifications(enabled: Bool) {
|
private func registerNotifications(enabled: Bool) {
|
||||||
let notificationCenter = NotificationCenter.default
|
let notificationCenter = NotificationCenter.default
|
||||||
|
|
||||||
|
|
@ -76,6 +113,9 @@ class WifiViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func checkIP() {
|
func checkIP() {
|
||||||
|
|
||||||
print("Tiggered checkIP")
|
print("Tiggered checkIP")
|
||||||
|
|
@ -84,8 +124,6 @@ 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 {
|
||||||
|
|
||||||
print(userDefaults.object(forKey: kPrefix+".storedIP"))
|
|
||||||
ipAddressStored = false
|
ipAddressStored = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,17 +148,19 @@ 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 storeIPAddress(ipAddress: String) {
|
|
||||||
userDefaults.set(ipAddress, forKey: kPrefix+".storedIP" )
|
|
||||||
printIPStorageAtLocation()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 storeResolvedAddress(service: ResolvedService) {
|
func storeResolvedAddress(service: ResolvedService) {
|
||||||
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
|
||||||
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
|
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
|
||||||
|
|
@ -135,7 +175,6 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
func clearKnownIPAddress() {
|
func clearKnownIPAddress() {
|
||||||
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
||||||
printIPStorageAtLocation()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,14 +188,11 @@ 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")
|
||||||
|
|
@ -164,15 +200,18 @@ class WifiViewModel: ObservableObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func checkStoredIP() {
|
func checkStoredIP() {
|
||||||
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == 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() {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue