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 isAnyPeripheralConnecting = false
|
||||
|
||||
@Published public var isDisconnectingFromCurrent = false
|
||||
|
||||
// Parameters
|
||||
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 */; };
|
||||
D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.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 */; };
|
||||
D52BE7F3269DF62100630900 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = D52BE7F2269DF62100630900 /* Zip */; };
|
||||
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 */; };
|
||||
D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.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 */; };
|
||||
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.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 */; };
|
||||
D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.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 */; };
|
||||
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
|
@ -115,6 +135,8 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -140,6 +162,8 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -163,6 +187,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -335,25 +360,42 @@
|
|||
D567E2DD28C8D3E20009F768 /* SettingsView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D56B75D2294BAA8900D008E7 /* BLESetttings */,
|
||||
D567E2DE28C8D40C0009F768 /* SettingsView.swift */,
|
||||
D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */,
|
||||
);
|
||||
path = SettingsView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D56B75D2294BAA8900D008E7 /* BLESetttings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */,
|
||||
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */,
|
||||
);
|
||||
path = BLESetttings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D58E1C8628A2B0DE00AB683E /* Wifi View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D567E2DD28C8D3E20009F768 /* SettingsView */,
|
||||
D58E1C8728A2B10B00AB683E /* WifiView.swift */,
|
||||
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
|
||||
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
|
||||
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
|
||||
D5DD39A828D11962000FAEB8 /* WifiTransferService.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 */,
|
||||
D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */,
|
||||
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */,
|
||||
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
|
||||
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */,
|
||||
D567E2B528B81B730009F768 /* Queue.swift */,
|
||||
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */,
|
||||
|
|
@ -370,7 +412,6 @@
|
|||
D59DFDB2268CCEAC001737F6 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D567E2DD28C8D3E20009F768 /* SettingsView */,
|
||||
D58E1C8628A2B0DE00AB683E /* Wifi View */,
|
||||
D5507ACE26C668BC00512BAA /* UI Components */,
|
||||
D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
|
||||
|
|
@ -428,6 +469,7 @@
|
|||
D5C474C627E39FC8002DD160 /* ReadexPro-Medium.ttf */,
|
||||
D5C474C727E39FC8002DD160 /* ReadexPro-Regular.ttf */,
|
||||
D5C474C527E39FC8002DD160 /* ReadexPro-Light.ttf */,
|
||||
D5A3D4ED292575F000ECCEC9 /* ReadexPro-SemiBold.ttf */,
|
||||
D5C474C227E39FAD002DD160 /* ReadexPro-Bold.ttf */,
|
||||
);
|
||||
path = ReadexPro;
|
||||
|
|
@ -448,9 +490,11 @@
|
|||
children = (
|
||||
D505B99B2755323C00386E9F /* NetworkMonitor.swift */,
|
||||
D544A24A281F92840038D483 /* ImageCaching.swift */,
|
||||
D5267410292E902700D4C79E /* Networking.swift */,
|
||||
D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */,
|
||||
D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */,
|
||||
D595FC2D2812C23D00569D8C /* Image Extension.swift */,
|
||||
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */,
|
||||
);
|
||||
path = Networking;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -459,6 +503,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
|
||||
D5361099296FB2BB00228E15 /* DataStore.swift */,
|
||||
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
|
||||
);
|
||||
path = Model;
|
||||
|
|
@ -479,6 +524,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D5640215271B54BF00AE1519 /* MainSelectionView.swift */,
|
||||
D52A926C29071DF400973B6B /* SelectionView.swift */,
|
||||
D5482F4828E63DB7000B0C8E /* MainSelectionViewModel.swift */,
|
||||
);
|
||||
path = "Unpaired View";
|
||||
|
|
@ -617,6 +663,7 @@
|
|||
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */,
|
||||
D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */,
|
||||
D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */,
|
||||
D5267411292E902700D4C79E /* Networking.swift in Sources */,
|
||||
D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */,
|
||||
D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */,
|
||||
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */,
|
||||
|
|
@ -631,6 +678,7 @@
|
|||
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */,
|
||||
D5F53CED2694B7A9007634C2 /* OnboardingBackgroundView.swift in Sources */,
|
||||
D5D1F4B227ECFF760040E2BF /* ProjectsModel.swift in Sources */,
|
||||
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */,
|
||||
D5BA1F7A28B52A490012FC62 /* WifiListDetailView.swift in Sources */,
|
||||
D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */,
|
||||
D567E2B628B81B730009F768 /* Queue.swift in Sources */,
|
||||
|
|
@ -644,13 +692,17 @@
|
|||
D59DFDB6268CD052001737F6 /* AppEnvironment.swift in Sources */,
|
||||
D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */,
|
||||
D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */,
|
||||
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */,
|
||||
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */,
|
||||
D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */,
|
||||
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */,
|
||||
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */,
|
||||
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */,
|
||||
D517F68126C5771D002996E8 /* FillerView.swift in Sources */,
|
||||
D5DD39A728D11817000FAEB8 /* WifiFileTransfer.swift in Sources */,
|
||||
D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */,
|
||||
D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */,
|
||||
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */,
|
||||
D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */,
|
||||
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */,
|
||||
D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */,
|
||||
|
|
@ -659,13 +711,16 @@
|
|||
D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */,
|
||||
D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */,
|
||||
D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */,
|
||||
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */,
|
||||
D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */,
|
||||
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */,
|
||||
D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */,
|
||||
D59DFDBC268CE0EB001737F6 /* RootView.swift in Sources */,
|
||||
D595FC2E2812C23D00569D8C /* Image Extension.swift in Sources */,
|
||||
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */,
|
||||
D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */,
|
||||
D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */,
|
||||
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */,
|
||||
D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */,
|
||||
D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */,
|
||||
D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */,
|
||||
|
|
@ -673,6 +728,7 @@
|
|||
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */,
|
||||
D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */,
|
||||
D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */,
|
||||
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */,
|
||||
D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */,
|
||||
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */,
|
||||
D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */,
|
||||
|
|
@ -684,6 +740,7 @@
|
|||
D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */,
|
||||
D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */,
|
||||
D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */,
|
||||
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */,
|
||||
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */,
|
||||
D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */,
|
||||
D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */,
|
||||
|
|
@ -692,6 +749,7 @@
|
|||
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */,
|
||||
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */,
|
||||
D5640216271B54BF00AE1519 /* MainSelectionView.swift in Sources */,
|
||||
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */,
|
||||
D505B99C2755323C00386E9F /* NetworkMonitor.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -823,17 +881,17 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
CURRENT_PROJECT_VERSION = 0;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = PyLeap/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 2.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
|
|
@ -853,17 +911,17 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 14;
|
||||
CURRENT_PROJECT_VERSION = 0;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = PyLeap/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 2.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -9,8 +9,6 @@ import UIKit
|
|||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
|
||||
// UI
|
||||
|
|
@ -20,21 +18,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.769",
|
||||
"green" : "0.561",
|
||||
"red" : "0.380"
|
||||
"blue" : "150",
|
||||
"green" : "100",
|
||||
"red" : "74"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.773",
|
||||
"green" : "0.561",
|
||||
"red" : "0.380"
|
||||
"blue" : "150",
|
||||
"green" : "100",
|
||||
"red" : "74"
|
||||
}
|
||||
},
|
||||
"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 {
|
||||
|
||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
let cachesPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
|
||||
static let shared = DownloadViewModel()
|
||||
@StateObject var globalString = GlobalString()
|
||||
|
||||
var bundleURL = String()
|
||||
var bundleTitle = String()
|
||||
|
||||
// Alert
|
||||
@Published var alertMsg = ""
|
||||
@Published var showAlert = false
|
||||
|
||||
// Saving Download task reference for cancelling...
|
||||
@Published var downloadTaskSession: URLSessionDownloadTask!
|
||||
var manager = FileManager.default
|
||||
|
||||
// Show Progress View
|
||||
@Published var downloadProgress: CGFloat = 0
|
||||
|
|
@ -31,12 +31,232 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
|
|||
@Published var isDownloading = false
|
||||
|
||||
// Saving Download task refernce for cancelling...
|
||||
@Published var downloadtaskSession : URLSessionDownloadTask!
|
||||
// @Published var downloadtaskSession : URLSessionDownloadTask!
|
||||
|
||||
@Published var attemptToSendBunle = false
|
||||
|
||||
@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
|
||||
func startDownload(urlString: String, projectTitle: String) {
|
||||
print("Starting Download...")
|
||||
|
|
@ -54,86 +274,19 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
|
|||
downloadTaskSession = session.downloadTask(with: validURL)
|
||||
downloadTaskSession.resume()
|
||||
|
||||
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
|
||||
}
|
||||
|
||||
func startDownload(urlString: String, projectTitle: String, compeletion: () -> ()) {
|
||||
print("Starting Download...")
|
||||
isDownloading = true
|
||||
// Check for valid URL
|
||||
guard let validURL = URL(string: urlString) else {
|
||||
self.reportError(error: "Invalid URL!")
|
||||
return
|
||||
}
|
||||
downloadProgress = 0
|
||||
|
||||
func testCallback(completion: ()->()) {
|
||||
print("Do something")
|
||||
|
||||
// Download Task...
|
||||
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
|
||||
|
||||
downloadTaskSession = session.downloadTask(with: validURL)
|
||||
downloadTaskSession.resume()
|
||||
|
||||
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
|
||||
|
||||
}
|
||||
|
||||
func makeFileDirectory() {
|
||||
// Creating a File Manager Object
|
||||
|
||||
// Creating a folder
|
||||
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
|
||||
|
||||
do {
|
||||
|
||||
try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [:])
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func unzipProjectFile(urlString: String, projectTitle: String) {
|
||||
|
||||
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
|
||||
|
||||
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
|
||||
|
||||
if let zipFileUrl = URL(string: urlString) {
|
||||
// Download from this site
|
||||
URLSession.shared.downloadTask(with: zipFileUrl) { (tempFileUrl, response, error) in
|
||||
|
||||
/*
|
||||
if let...
|
||||
if you can let the new variable name equal the non-optional version of optionalName, do the following with it"
|
||||
*/
|
||||
|
||||
if let zipTempFileUrl = tempFileUrl {
|
||||
do {
|
||||
|
||||
let zipData = try Data(contentsOf: zipTempFileUrl)
|
||||
|
||||
try zipData.write(to: CPZipName)
|
||||
|
||||
let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
|
||||
|
||||
try FileManager.default.removeItem(at: CPZipName)
|
||||
|
||||
} catch {
|
||||
print("Zip ERROR")
|
||||
print("Error: \(error)")
|
||||
|
||||
self.state = .failed
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
self.state = .idle
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
extension DownloadViewModel {
|
||||
|
||||
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 urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
// Getting Progress
|
||||
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
|
||||
print("Progress: \(numeralProgress)")
|
||||
func makeFileDirectory() {
|
||||
// Creating a File Manager Object
|
||||
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells the delegate that a download task has finished downloading.
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
// Creating a folder
|
||||
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
|
||||
|
||||
guard let url = downloadTask.originalRequest?.url else {
|
||||
self.reportError(error: "An error has occurred...")
|
||||
return
|
||||
}
|
||||
|
||||
// Creating a destination for storing files with a destination URL
|
||||
let destinationURL = directoryPath.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
//if that file already exists, replace it.
|
||||
|
||||
try? FileManager.default.removeItem(at: destinationURL)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
print(#function)
|
||||
do {
|
||||
|
||||
// Copy temp file to directory.
|
||||
try FileManager.default.copyItem(at: location, to: destinationURL)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// If Successful...
|
||||
print("Successful Download")
|
||||
self.isDownloading = false
|
||||
print("Download Location: \(location)")
|
||||
self.downloadProgress = 1.0
|
||||
print("\(self.didDownloadBundle) CURRENT STATE")
|
||||
self.didDownloadBundle = true
|
||||
print("\(self.didDownloadBundle) CURRENT STATE")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
self.attemptToSendBunle.toggle()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [:])
|
||||
} catch {
|
||||
print(error)
|
||||
self.reportError(error: "Try again later")
|
||||
isDownloading = false
|
||||
self.didDownloadBundle = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Report Error Function...
|
||||
func reportError(error: String){
|
||||
alertMsg = error
|
||||
showAlert.toggle()
|
||||
}
|
||||
|
||||
// cancel Task...
|
||||
func cancelTask(){
|
||||
if let task = downloadtaskSession,task.state == .running{
|
||||
// cancelling...
|
||||
downloadtaskSession.cancel()
|
||||
// closing view...
|
||||
// withAnimation{self.showDownlodProgress = false}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ struct ExampleView: View {
|
|||
//
|
||||
// PageView(title: "Choose your Adventure!", subtitle: "Choose a project you would like to send over to your PyLeap compatible device.", imageName: "slide3", showDismissButton: false, shouldShowOnboarding: $shouldShowOnboarding)
|
||||
//
|
||||
PageView(title: "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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@
|
|||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_circuitpython._tcp</string>
|
||||
<string>_bonjour._tcp</string>
|
||||
</array>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
@ -33,6 +28,11 @@
|
|||
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<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>
|
||||
<string>PyLeap uses the local network to communicate with your Adafruit device</string>
|
||||
<key>UIAppFonts</key>
|
||||
|
|
@ -64,10 +64,6 @@
|
|||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</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 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
|
||||
|
||||
class NetworkService: ObservableObject {
|
||||
let dataStore = DataStore()
|
||||
|
||||
static let shared = NetworkService()
|
||||
|
||||
@Published var pdemos : [ResultItem] = []
|
||||
@State var storedURL = ""
|
||||
|
||||
let userDefaults = UserDefaults.standard
|
||||
|
||||
init(){
|
||||
load()
|
||||
loadCustProjects()
|
||||
}
|
||||
|
||||
func loadCustProjects() {
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
print("++++Custom Projects++++")
|
||||
for i in loadedProjects {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func save(content: [ResultItem]) {
|
||||
print("Saving JSON response...")
|
||||
let encoder = JSONEncoder()
|
||||
if let encoded = try? encoder.encode(content) {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.set(encoded, forKey: "SavedProjects")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func save(customProjects: [ResultItem]) {
|
||||
|
||||
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
|
||||
if let encoded = try? encoder.encode(customProjects) {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.set(encoded, forKey: "CustomProjects")
|
||||
}
|
||||
}
|
||||
|
||||
func saveCustomProjects(content: [ResultItem]) {
|
||||
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
|
||||
// if let newIncoming = content.contains()
|
||||
|
||||
var customList: [ResultItem] = []
|
||||
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
print("Loading previous list")
|
||||
|
||||
for i in loadedProjects {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
|
||||
customList = loadedProjects
|
||||
print("Appending new content")
|
||||
|
||||
customList.append(contentsOf: content)
|
||||
|
||||
for i in customList {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
if let encoded = try? encoder.encode(customList) {
|
||||
let defaults = UserDefaults.standard
|
||||
print("Saved new custom list.")
|
||||
defaults.set(encoded, forKey: "CustomProjects")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func verifyIncomingProject(json response: [ResultItem]){
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
/// Does incoming project exist already?
|
||||
/// Check with object's property URL
|
||||
|
||||
|
||||
if loadedProjects.contains(where: { $0.bundleLink == response[0].bundleLink
|
||||
}) {
|
||||
print("does exist")
|
||||
|
||||
} else {
|
||||
|
||||
print("does not exist")
|
||||
}
|
||||
|
||||
print(loadedProjects)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func loadCustomProjects() -> [ResultItem]{
|
||||
print(#function)
|
||||
var customList: [ResultItem] = []
|
||||
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
print("Load custom projectsxo")
|
||||
// print(loadedProjects)
|
||||
|
||||
for i in loadedProjects {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
|
||||
customList = loadedProjects
|
||||
|
||||
}
|
||||
}
|
||||
return customList
|
||||
}
|
||||
|
||||
func mergeProjects() {
|
||||
print(#function)
|
||||
|
||||
var standardList: [ResultItem] = []
|
||||
var customList: [ResultItem] = []
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
print("Load saved projects")
|
||||
|
||||
// let check = loadedProjects.map { $0.bundleLink == "" }
|
||||
|
||||
pdemos = loadedProjects
|
||||
|
||||
print(loadedProjects)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
print("Load saved projects")
|
||||
|
||||
print(loadedProjects)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func load() {
|
||||
|
||||
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
|
||||
let mergedList = loadedProjects + loadCustomProjects()
|
||||
|
||||
// save(content: <#T##[ResultItem]#>)
|
||||
|
||||
pdemos = mergedList
|
||||
|
||||
|
||||
print("----Standard Projects----")
|
||||
for i in loadedProjects {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
|
||||
print("++++Custom Projects++++")
|
||||
for i in loadCustomProjects() {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent)
|
||||
|
||||
private var dataTask: URLSessionDataTask?
|
||||
|
||||
|
|
@ -238,24 +26,16 @@ class NetworkService: ObservableObject {
|
|||
|
||||
// Session Configuration & Caching Policy
|
||||
let configuration = URLSessionConfiguration.default
|
||||
// configuration.requestCachePolicy = .useProtocolCachePolicy
|
||||
configuration.requestCachePolicy = .returnCacheDataElseLoad
|
||||
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
|
||||
@Published var projectInfo = Data()
|
||||
|
||||
func fetch() {
|
||||
|
||||
let cache = URLCache.shared
|
||||
|
||||
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
|
||||
func fetch(completion: @escaping() -> Void) {
|
||||
print("Attempting Network Request")
|
||||
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
|
||||
|
||||
if let error = error {
|
||||
print("error: \(error)")
|
||||
|
|
@ -267,78 +47,82 @@ class NetworkService: ObservableObject {
|
|||
if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.save(content: projectData.projects)
|
||||
self.load()
|
||||
|
||||
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList)
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
print("No data found")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
print("Updating UIList with Cached data...")
|
||||
DispatchQueue.main.async {
|
||||
self.load()
|
||||
self.dataStore.loadDefaultProjectList()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func fetchThirdParyProject(urlString: String?) {
|
||||
let cache = URLCache.shared
|
||||
|
||||
guard let urlString = urlString else {
|
||||
print("Error")
|
||||
return
|
||||
}
|
||||
|
||||
if urlString.contains(" ") {
|
||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let request = URLRequest(url: URL(string: urlString)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
|
||||
|
||||
|
||||
print("Making Network Request for Custom Project.")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
|
||||
func fetchThirdPartyProject(urlString: String?) {
|
||||
|
||||
thirdPartyBackgroundQueue.async {
|
||||
|
||||
if let error = error {
|
||||
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
|
||||
|
||||
guard let urlString = urlString else {
|
||||
print("\(#function) @Line: \(#line)")
|
||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||
|
||||
print("Error urlString")
|
||||
return
|
||||
}
|
||||
|
||||
if let data = data {
|
||||
|
||||
print("Updating UIList with new data...")
|
||||
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
|
||||
|
||||
if let projects = projectData?.projects {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
print(#function)
|
||||
for i in projects {
|
||||
print("\(i.projectName)")
|
||||
}
|
||||
// self.pdemos.append(contentsOf: projects)
|
||||
self.saveCustomProjects(content: projects)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if urlString.contains(" ") {
|
||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||
return
|
||||
}
|
||||
|
||||
guard let requestURL = URL(string: urlString) else {
|
||||
print("\(#function) @Line: \(#line)")
|
||||
print("Error requestURL")
|
||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let request = URLRequest(url: requestURL, cachePolicy: URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 0.0)
|
||||
|
||||
print("Making Network Request for Custom Project.")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
|
||||
if let error = error {
|
||||
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
|
||||
|
||||
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if let data = data {
|
||||
|
||||
let projectData = JSONDecoderHelper.decode(data: data) as RootResults?
|
||||
|
||||
if let projects = projectData?.projects {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.dataStore.save(customProjects: projects, completion: self.dataStore.loadThirdPartyProjectsFromFileManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
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(.horizontal, 20)
|
||||
|
||||
BlinkaAnimationView()
|
||||
BlinkaAnimationView(height: 250, width: 250)
|
||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||
|
||||
.onAppear(){
|
||||
|
|
|
|||
|
|
@ -29,44 +29,37 @@ struct BTConnectionView: View {
|
|||
VStack{
|
||||
|
||||
HStack {
|
||||
|
||||
Button {
|
||||
|
||||
self.rootViewModel.goToMain()
|
||||
rootViewModel.goToSelection()
|
||||
|
||||
} label: {
|
||||
Image(systemName: "arrow.backward")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.foregroundColor(Color("pyleap_gray"))
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.foregroundColor(.black)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.padding()
|
||||
|
||||
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, 50)
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.top, 15)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Image("pyleapLogo")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.minimumScaleFactor(0.1)
|
||||
.padding(.top, 50)
|
||||
.padding(.horizontal, 60)
|
||||
|
||||
|
||||
.sheet(isPresented: $showConnectionErrorView) {
|
||||
TroubleshootView()
|
||||
}
|
||||
|
|
@ -76,19 +69,29 @@ struct BTConnectionView: View {
|
|||
|
||||
|
||||
|
||||
Text("Searching for PyLeap compatible device...")
|
||||
.padding(.horizontal, 30)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||
.minimumScaleFactor(0.1)
|
||||
Text("Bluetooth Connect")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 36))
|
||||
.multilineTextAlignment(.center)
|
||||
.minimumScaleFactor(0.01)
|
||||
.lineLimit(1)
|
||||
.padding()
|
||||
.padding(.horizontal, 30)
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
BlinkaAnimationView()
|
||||
.minimumScaleFactor(0.1)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
.onAppear() {
|
||||
Animation.linear(duration: 1.0)
|
||||
|
|
@ -98,6 +101,9 @@ struct BTConnectionView: View {
|
|||
|
||||
Text(detailText)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||
.multilineTextAlignment(.center)
|
||||
.minimumScaleFactor(0.1)
|
||||
.lineLimit(2)
|
||||
|
||||
Button(action: {
|
||||
nextText = 1
|
||||
|
|
@ -222,10 +228,7 @@ struct BTConnectionView: View {
|
|||
Text("Pair Device")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||
.foregroundColor(Color.white)
|
||||
|
||||
|
||||
.padding(.horizontal, 60)
|
||||
|
||||
.frame(height: 50)
|
||||
.background(Color("pyleap_pink"))
|
||||
.clipShape(Capsule())
|
||||
|
|
@ -275,7 +278,7 @@ struct BTConnectionView: View {
|
|||
let text: String
|
||||
switch model.connectionStatus {
|
||||
case .scanning:
|
||||
text = "Scanning..."
|
||||
text = "Scanning for PyLeap compatible devices..."
|
||||
case .restoringConnection:
|
||||
text = "Restoring connection..."
|
||||
case .connecting:
|
||||
|
|
@ -292,11 +295,8 @@ struct BTConnectionView: View {
|
|||
case .disconnected(let error):
|
||||
if let error = error {
|
||||
text = "Disconnected: \(error.localizedDescription)"
|
||||
//self.showSheetView.toggle()
|
||||
} else {
|
||||
text = "Disconnected"
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ struct TroubleshootView: View {
|
|||
|
||||
Button {
|
||||
|
||||
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
|
||||
UIApplication.shared.open(URL(string: "App-Prefs:root=Bluetooth")!)
|
||||
} label: {
|
||||
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -18,17 +18,15 @@ struct FillerView: View {
|
|||
Image("pyleapLogo")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.offset(y: -20)
|
||||
.offset(y: -30)
|
||||
|
||||
ProgressView()
|
||||
|
||||
}
|
||||
.preferredColorScheme(.light)
|
||||
.padding(.horizontal, 20)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.padding(.horizontal, 30)
|
||||
.modifier(Alerts(activeAlert: $model.activeAlert, model: model))
|
||||
.onAppear {
|
||||
print("Filler View")
|
||||
model.setupBluetooth()
|
||||
}
|
||||
.onChange(of: model.isStartupFinished) { isStartupFinished in
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ import SwiftUI
|
|||
import FileTransferClient
|
||||
|
||||
|
||||
class BleContentCommands {
|
||||
class BleContentCommands: ObservableObject {
|
||||
|
||||
private weak var fileTransferClient: FileTransferClient?
|
||||
@Published var transmissionProgress: TransmissionProgress?
|
||||
@Published var isTransmiting = false
|
||||
@Published var bootUpInfo = String()
|
||||
|
||||
@Published var counter = 0
|
||||
|
||||
enum ProjectViewError: LocalizedError {
|
||||
case fileTransferUndefined
|
||||
|
|
@ -103,6 +104,8 @@ class BleContentCommands {
|
|||
let str = String(decoding: data, as: UTF8.self)
|
||||
print("Read: \(str)")
|
||||
self.bootUpInfo = str
|
||||
sharedBootinfo = str
|
||||
|
||||
|
||||
case .failure(let error):
|
||||
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)?) {
|
||||
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
||||
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.counter += 1
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -252,9 +257,11 @@ class BleContentCommands {
|
|||
switch result {
|
||||
case .success:
|
||||
DLog("writeFile \(path) success. Size: \(data.count)")
|
||||
|
||||
print("\(#function) @Line: \(#line)")
|
||||
case .failure(let 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 FileTransferClient
|
||||
|
||||
class SpotlightCounter: ObservableObject {
|
||||
@Published var counter = 0
|
||||
class ExpandedBLECellState: ObservableObject {
|
||||
@Published var currentCell = ""
|
||||
}
|
||||
|
||||
struct BleModuleView: View {
|
||||
|
|
@ -25,52 +25,43 @@ struct BleModuleView: View {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
|
||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
||||
|
||||
|
||||
|
||||
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
||||
|
||||
|
||||
|
||||
@StateObject var viewModel = BleModuleViewModel()
|
||||
@ObservedObject var networkServiceModel = NetworkService()
|
||||
@StateObject var globalString = GlobalString()
|
||||
@StateObject var btConnectionViewModel = BTConnectionViewModel()
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
@StateObject var downloadModel = DownloadViewModel()
|
||||
@StateObject var spotlight = SpotlightCounter()
|
||||
|
||||
//clearKnownPeripheralUUIDs
|
||||
|
||||
@State private var isConnected = false
|
||||
//@State private var switchedView = 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 var currentHightlight: Int = 0
|
||||
|
||||
|
||||
|
||||
|
||||
@State private var activeAlert: ActiveAlert?
|
||||
@State private var internetAlert = false
|
||||
@State private var showAlert1 = false
|
||||
|
||||
|
||||
@State private var boardBootInfo = ""
|
||||
|
||||
|
||||
@State private var inConnectedInSelectionView = true
|
||||
|
||||
@AppStorage("shouldShowOnboarding123") var switchedView: Bool = false
|
||||
|
||||
@State var subviewHeight : CGFloat = 0
|
||||
|
||||
func showConfirmationPrompt() {
|
||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
||||
connectionManager.isDisconnectingFromCurrent = true
|
||||
} cancel: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
var connectedPeripherals = connectionManager.peripherals.filter{$0.state == .connected }
|
||||
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
|
||||
|
||||
VStack {
|
||||
//Start
|
||||
|
||||
|
|
@ -78,6 +69,7 @@ struct BleModuleView: View {
|
|||
HStack {
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("bluetooth")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
|
|
@ -178,68 +170,139 @@ struct BleModuleView: View {
|
|||
|
||||
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 {
|
||||
|
||||
|
||||
|
||||
if boardBootInfo == "circuitplayground_bluefruit" {
|
||||
Text("Connected to Circuit Playground Bluefruit")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
HStack {
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxHeight: 40)
|
||||
.background(Color("pyleap_green"))
|
||||
.background(Color("adafruit_blue"))
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
|
||||
ScrollView(.vertical, showsIndicators: true) {
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
|
||||
ScrollViewReader { scroll in
|
||||
|
||||
MainSubHeaderView()
|
||||
// .spotlight(enabled: spotlight.counter == 1, title: "1")
|
||||
|
||||
let check = networkServiceModel.pdemos.filter {
|
||||
$0.compatibility[0] == boardBootInfo
|
||||
if boardBootInfo == "clue_nrf52840_express" {
|
||||
MainSubHeaderView(device: "Adafruit CLUE")
|
||||
}
|
||||
|
||||
if boardBootInfo == "circuitplayground_bluefruit" {
|
||||
MainSubHeaderView(device: "Circuit Playground")
|
||||
|
||||
}
|
||||
|
||||
|
||||
let check = viewModel.pdemos.filter {
|
||||
$0.compatibility.contains(boardBootInfo)
|
||||
}
|
||||
|
||||
|
||||
ForEach(check) { demo in
|
||||
|
||||
|
||||
DemoViewCell(result: demo, isConnected: $inConnectedInSelectionView, bootOne: $boardBootInfo, onViewGeometryChanged: {
|
||||
withAnimation {
|
||||
scroll.scrollTo(demo.id)
|
||||
if demo.bundleLink == expandedState.currentCell {
|
||||
|
||||
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||
})
|
||||
.onAppear(){
|
||||
print("Cell Appeared")
|
||||
withAnimation {
|
||||
scroll.scrollTo(demo.id)
|
||||
}
|
||||
|
||||
}
|
||||
}, stateBinder: $downloadState)
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -247,143 +310,38 @@ struct BleModuleView: View {
|
|||
|
||||
.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)
|
||||
.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
|
||||
viewModel.readMyStatus()
|
||||
|
||||
print("newValue \(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
|
||||
viewModel.setup(fileTransferClient: selectedClient)
|
||||
}
|
||||
|
||||
|
||||
.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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
@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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,656 +10,29 @@ import Zip
|
|||
import FileTransferClient
|
||||
|
||||
class BleModuleViewModel: ObservableObject {
|
||||
|
||||
@StateObject var globalString = GlobalString()
|
||||
|
||||
|
||||
private weak var fileTransferClient: FileTransferClient?
|
||||
@StateObject var contentTransfer = BleContentTransfer()
|
||||
|
||||
@Published var entries = [BlePeripheral.DirectoryEntry]()
|
||||
@Published var isTransmiting = false
|
||||
@Published var bootUpInfo = ""
|
||||
|
||||
let dataStore = DataStore()
|
||||
|
||||
var projectDirectories: [URL] = []
|
||||
@Published var sendingBundle = false
|
||||
@Published var didCompleteTranfer = false
|
||||
@Published var writeError = false
|
||||
@Published var pdemos : [ResultItem] = []
|
||||
|
||||
|
||||
@Published var counter = 0
|
||||
@Published var numOfFiles = 0
|
||||
init() {
|
||||
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 {
|
||||
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() {
|
||||
// model.readFile(filename: "boot_out.txt")
|
||||
print(#function)
|
||||
|
||||
print("BOOT INFO: \(bootUpInfo)")
|
||||
|
||||
|
|
@ -668,27 +41,19 @@ class BleModuleViewModel: ObservableObject {
|
|||
case let str where str.contains("circuitplayground_bluefruit"):
|
||||
print("Circuit Playground Bluefruit device")
|
||||
bootUpInfo = "circuitplayground_bluefruit"
|
||||
// DispatchQueue.main.async { [self] in
|
||||
// self.globalString.compatibilityString = "circuitplayground_bluefruit"
|
||||
// }
|
||||
|
||||
case let str where str.contains("clue_nrf52840_express"):
|
||||
print("Clue device")
|
||||
bootUpInfo = "clue_nrf52840_express"
|
||||
// DispatchQueue.main.async { [self] in
|
||||
// globalString.compatibilityString = "clue_nrf52840_express"
|
||||
//
|
||||
// }
|
||||
|
||||
default:
|
||||
print("Unknown Device")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func readBoardForCircuitPythonVersion() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: System
|
||||
|
|
@ -705,6 +70,10 @@ class BleModuleViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
@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 {
|
||||
enum TransmissionType: Equatable {
|
||||
|
|
@ -731,16 +100,7 @@ class BleModuleViewModel: ObservableObject {
|
|||
return modeText
|
||||
}
|
||||
}
|
||||
@Published var lastTransmit: TransmissionLog? = TransmissionLog(type: .write(size: 334))
|
||||
|
||||
|
||||
@Published var activeAlert: ActiveAlert?
|
||||
|
||||
// Data
|
||||
private let bleManager = BleManager.shared
|
||||
|
||||
|
||||
|
||||
// MARK: - Setup
|
||||
func onAppear() {
|
||||
//registerNotifications(enabled: true)
|
||||
|
|
@ -773,8 +133,10 @@ class BleModuleViewModel: ObservableObject {
|
|||
case .success(let data):
|
||||
self.lastTransmit = TransmissionLog(type: .read(data: data))
|
||||
let str = String(decoding: data, as: UTF8.self)
|
||||
|
||||
print("Read: \(str)")
|
||||
self.bootUpInfo = str
|
||||
sharedBootinfo = str
|
||||
|
||||
case .failure(let error):
|
||||
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
|
||||
|
|
@ -906,11 +268,7 @@ class BleModuleViewModel: ObservableObject {
|
|||
private func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
|
||||
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.counter += 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
DLog("start writeFile \(path)")
|
||||
fileTransferClient.writeFile(path: path, data: data, progress: { [weak self] written, total in
|
||||
DLog("writing progress: \( String(format: "%.1f%%", Float(written) * 100 / Float(total)) )")
|
||||
|
|
@ -990,6 +348,8 @@ class BleModuleViewModel: ObservableObject {
|
|||
|
||||
}
|
||||
|
||||
public var sharedBootinfo = ""
|
||||
|
||||
enum ActiveAlert: Identifiable {
|
||||
case error(error: Error)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,76 +8,83 @@
|
|||
import SwiftUI
|
||||
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 {
|
||||
@State var transferInProgress = false
|
||||
@State var isDownloaded = false
|
||||
|
||||
@EnvironmentObject var globalString : GlobalString
|
||||
|
||||
@Binding var bindingString: String
|
||||
|
||||
@Binding var downloadStateBinder: DownloadState
|
||||
|
||||
@State private var toggleView: Bool = false
|
||||
|
||||
let title: String
|
||||
let image: String
|
||||
let description: String
|
||||
let learnGuideLink: URLRequest
|
||||
let downloadLink: String
|
||||
let compatibility: [String]
|
||||
let result: ResultItem
|
||||
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
@StateObject var downloadModel = DownloadViewModel()
|
||||
@StateObject var viewModel = SubCellViewModel()
|
||||
@StateObject var selectionModel = BleModuleViewModel()
|
||||
@StateObject var contentTransfer = BleContentTransfer()
|
||||
|
||||
|
||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
||||
|
||||
@Binding var isConnected : Bool
|
||||
|
||||
@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 {
|
||||
|
||||
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: {
|
||||
|
||||
ImageWithURL(image)
|
||||
ImageWithURL(result.projectImage)
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity)
|
||||
.cornerRadius(14)
|
||||
.padding(.top, 30)
|
||||
|
||||
|
||||
Text(description)
|
||||
|
||||
Text(result.description)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
||||
.multilineTextAlignment(.leading)
|
||||
.minimumScaleFactor(0.1)
|
||||
|
|
@ -85,9 +92,9 @@ struct DemoSubview: View {
|
|||
Text("Compatible with:")
|
||||
.font(Font.custom("ReadexPro-Bold", size: 18))
|
||||
.padding(.top, 5)
|
||||
|
||||
|
||||
ForEach(compatibility, id: \.self) { string in
|
||||
|
||||
ForEach(result.compatibility, id: \.self) { string in
|
||||
if string == "circuitplayground_bluefruit" {
|
||||
|
||||
HStack {
|
||||
|
|
@ -102,7 +109,7 @@ struct DemoSubview: View {
|
|||
.padding(.top, 10)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if string == "clue_nrf52840_express" {
|
||||
|
||||
|
|
@ -123,14 +130,19 @@ struct DemoSubview: View {
|
|||
.padding(.horizontal, 30)
|
||||
|
||||
Button(action: {
|
||||
showWebViewPopover = true
|
||||
if !viewModel.isConnectedToInternet {
|
||||
showAlertMessage()
|
||||
} else {
|
||||
showWebViewPopover = true
|
||||
|
||||
}
|
||||
|
||||
}) {
|
||||
LearnGuideButton()
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.sheet(isPresented: $showWebViewPopover, content: {
|
||||
WebView(URLRequest(url: learnGuideLink.url!))
|
||||
SwiftUIWebView(webAddress: result.learnGuideLink)
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -138,123 +150,72 @@ struct DemoSubview: View {
|
|||
|
||||
if isConnected {
|
||||
|
||||
if compatibility.contains(bindingString) {
|
||||
|
||||
|
||||
Button {
|
||||
contentTransfer.getProjectURL(nameOf: title)
|
||||
} label: {
|
||||
Text("XXX")
|
||||
}
|
||||
|
||||
if result.compatibility.contains(bindingString) {
|
||||
|
||||
// Button {
|
||||
// viewModel.deleteStoredFilesInFM()
|
||||
// } label: {
|
||||
// Text("Delete File Manager Contents")
|
||||
// .bold()
|
||||
// .padding(12)
|
||||
// }
|
||||
|
||||
if downloadStateBinder == .idle {
|
||||
|
||||
|
||||
if contentTransfer.downloadState == .idle {
|
||||
|
||||
Button(action: {
|
||||
|
||||
downloadStateBinder = .transferring
|
||||
globalString.isSendingG = true
|
||||
globalString.counterG = 0
|
||||
globalString.numberOfFilesG = 1
|
||||
|
||||
/// Condition: Connected to the internet
|
||||
///- If you're not connected to the internet, but you've downloaded the project...
|
||||
/// - If you're not connected to the internet, and you're project is not downloaded...
|
||||
/// *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()
|
||||
.padding(.top, 20)
|
||||
|
||||
.padding(.top, 20)
|
||||
}
|
||||
}
|
||||
|
||||
if downloadStateBinder == .failed {
|
||||
|
||||
if contentTransfer.downloadState == .failed {
|
||||
FailedButton()
|
||||
.padding(.top, 20)
|
||||
}
|
||||
|
||||
|
||||
if downloadStateBinder == .transferring {
|
||||
if contentTransfer.downloadState == .transferring {
|
||||
DownloadingButton()
|
||||
.padding(.top, 20)
|
||||
.disabled(true)
|
||||
|
||||
Button(action: {
|
||||
|
||||
print("Project Selected: \(title) - DemoSubView")
|
||||
|
||||
globalString.projectString = title
|
||||
globalString.numberOfTimesDownloaded += 1
|
||||
|
||||
}) {
|
||||
|
||||
DownloadingButton()
|
||||
.padding(.top, 20)
|
||||
VStack(alignment: .center, spacing: 5) {
|
||||
ProgressView("", value: CGFloat(contentTransfer.counter), total: CGFloat(contentTransfer.numOfFiles) )
|
||||
.padding(.horizontal, 90)
|
||||
.padding(.top, -8)
|
||||
.padding(.bottom, 10)
|
||||
.accentColor(Color.gray)
|
||||
.scaleEffect(x: 1, y: 2, anchor: .center)
|
||||
.cornerRadius(10)
|
||||
.frame(height: 10)
|
||||
|
||||
ProgressView()
|
||||
}
|
||||
.disabled(true)
|
||||
|
||||
|
||||
|
||||
if globalString.isSendingG {
|
||||
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
ProgressView("", value: CGFloat(globalString.counterG), total: CGFloat(globalString.numberOfFilesG) )
|
||||
.padding(.horizontal, 90)
|
||||
.padding(.top, -8)
|
||||
.padding(.bottom, 10)
|
||||
.accentColor(Color.gray)
|
||||
.scaleEffect(x: 1, y: 2, anchor: .center)
|
||||
.cornerRadius(10)
|
||||
.frame(height: 10)
|
||||
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if downloadStateBinder == .complete {
|
||||
|
||||
if contentTransfer.downloadState == .complete {
|
||||
CompleteButton()
|
||||
.padding(.top, 20)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
Button {
|
||||
rootViewModel.goTobluetoothPairing()
|
||||
rootViewModel.goToSelection()
|
||||
} label: {
|
||||
ConnectButton()
|
||||
.padding(.top, 20)
|
||||
|
|
@ -264,58 +225,48 @@ struct DemoSubview: View {
|
|||
}
|
||||
Spacer()
|
||||
.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
|
||||
viewModel.getProjectForSubClass(nameOf: title)
|
||||
})
|
||||
|
||||
.onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
|
||||
print("For project: \(title), project download is \(newValue)")
|
||||
.onAppear(){
|
||||
|
||||
print("On Appear")
|
||||
contentTransfer.contentCommands.setup(fileTransferClient: connectionManager.selectedClient)
|
||||
|
||||
globalString.projectString = title
|
||||
|
||||
if newValue {
|
||||
DispatchQueue.main.async {
|
||||
print("Getting project from Subclass \(title)")
|
||||
viewModel.getProjectForSubClass(nameOf: title)
|
||||
isDownloaded = true
|
||||
// viewModel.readFile(filename: "boot_out.txt")
|
||||
}
|
||||
|
||||
.onChange(of: contentTransfer.transferError, perform: { newValue in
|
||||
if newValue {
|
||||
showTransferErrorMessage()
|
||||
}
|
||||
}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: {
|
||||
|
||||
contentTransfer.readMyStatus()
|
||||
viewModel.searchPathForProject(nameOf: result.projectName)
|
||||
|
||||
if viewModel.projectDownloaded {
|
||||
viewModel.projectDownloaded = true
|
||||
} else {
|
||||
viewModel.projectDownloaded = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
.onAppear(perform: {
|
||||
viewModel.getProjectForSubClass(nameOf: title)
|
||||
if viewModel.projectDownloaded {
|
||||
isDownloaded = true
|
||||
} else {
|
||||
isDownloaded = false
|
||||
}
|
||||
print("is downloaded? \(isDownloaded)")
|
||||
})
|
||||
.padding(.top, 8)
|
||||
|
||||
)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,23 +9,21 @@ import Foundation
|
|||
|
||||
struct DemoViewCell: View {
|
||||
|
||||
|
||||
|
||||
@StateObject var spotlight = SpotlightCounter()
|
||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
||||
|
||||
let result : ResultItem
|
||||
@State private var isExpanded: Bool = false {
|
||||
|
||||
@State var isExpanded: Bool = false {
|
||||
didSet {
|
||||
onViewGeometryChanged()
|
||||
onViewGeometryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@Binding var isConnected: Bool
|
||||
@Binding var bootOne: String
|
||||
@Binding var deviceInfo: String
|
||||
|
||||
let onViewGeometryChanged: ()->Void
|
||||
|
||||
@Binding var stateBinder: DownloadState
|
||||
|
||||
var body: some View {
|
||||
content
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -35,19 +33,10 @@ struct DemoViewCell: View {
|
|||
VStack(alignment: .leading, spacing: 8) {
|
||||
header
|
||||
|
||||
|
||||
|
||||
|
||||
if isExpanded {
|
||||
|
||||
Group {
|
||||
DemoSubview(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
|
||||
image: result.projectImage,
|
||||
description: result.description,
|
||||
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
|
||||
downloadLink: result.bundleLink,
|
||||
compatibility: result.compatibility,
|
||||
isConnected: $isConnected)
|
||||
DemoSubview(bindingString: $deviceInfo, result: result, isConnected: $isConnected)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -75,7 +64,10 @@ struct DemoViewCell: View {
|
|||
.padding(.leading)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color("pyleap_purple"))
|
||||
.onTapGesture { isExpanded.toggle() }
|
||||
.onTapGesture {
|
||||
expandedState.currentCell = result.bundleLink
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ struct ScrollRefreshableView<Content: View>: View {
|
|||
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.onRefresh = onRefresh
|
||||
|
||||
|
|
|
|||
|
|
@ -11,37 +11,74 @@ class SubCellViewModel: ObservableObject {
|
|||
|
||||
@Published var projectDownloaded = false
|
||||
@Published var failedProjectLaunch = false
|
||||
@Published var isConnectedToInternet = false
|
||||
|
||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
var manager = FileManager.default
|
||||
|
||||
func getProjectForSubClass(nameOf project: String) {
|
||||
|
||||
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||
// for case condition: Only process URLs
|
||||
for case let fileURL as URL in enumerator {
|
||||
|
||||
if fileURL.lastPathComponent == project {
|
||||
failedProjectLaunch = false
|
||||
projectDownloaded = true
|
||||
print(#function)
|
||||
print("Searching for... \(project)")
|
||||
print("URL Path: \(fileURL.path)")
|
||||
print("URL : \(fileURL)")
|
||||
|
||||
return
|
||||
|
||||
} else {
|
||||
failedProjectLaunch = true
|
||||
projectDownloaded = false
|
||||
print("Project was not found...")
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
var networkMonitor = NetworkMonitor()
|
||||
|
||||
init() {
|
||||
internetMonitoring()
|
||||
}
|
||||
|
||||
func deleteStoredFilesInFM () {
|
||||
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
|
||||
} else {
|
||||
print("\(title) - Does not exist.")
|
||||
projectDownloaded = false
|
||||
}
|
||||
}
|
||||
|
||||
func internetMonitoring() {
|
||||
|
||||
networkMonitor.startMonitoring()
|
||||
networkMonitor.monitor.pathUpdateHandler = { path in
|
||||
if path.status == .satisfied {
|
||||
print("Connected to internet.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// self.showAlert = false
|
||||
self.isConnectedToInternet = true
|
||||
}
|
||||
} else {
|
||||
print("No connection.")
|
||||
DispatchQueue.main.async {
|
||||
// self.showAlert = true
|
||||
self.isConnectedToInternet = false
|
||||
}
|
||||
}
|
||||
print("isExpensive: \(path.isExpensive)")
|
||||
}
|
||||
}
|
||||
|
||||
func searchPathForProject(nameOf project: String) {
|
||||
var manager = FileManager.default
|
||||
|
||||
let nestedFolderURL = directoryPath.appendingPathComponent(project)
|
||||
|
||||
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
|
||||
print("\(project) - Exist")
|
||||
projectDownloaded = true
|
||||
} else {
|
||||
print("Does not exist - \(project)")
|
||||
projectDownloaded = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import FileTransferClient
|
|||
struct RootView: View {
|
||||
|
||||
@StateObject private var model = RootViewModel()
|
||||
@StateObject var currentCellID = ExpandedState()
|
||||
@StateObject var currentBLECellID = ExpandedBLECellState()
|
||||
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
|
||||
@AppStorage("onboarding") var onboardingSeen = true
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ struct RootView: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
Group{
|
||||
Group {
|
||||
switch model.destination {
|
||||
|
||||
case .onboard :
|
||||
|
|
@ -43,15 +45,35 @@ struct RootView: View {
|
|||
case .fileTransfer:
|
||||
BleModuleView()
|
||||
|
||||
case .wifiServiceSelection:
|
||||
WifiServiceSelectionView()
|
||||
|
||||
case .wifi:
|
||||
WifiView()
|
||||
|
||||
case .selection:
|
||||
SelectionView()
|
||||
|
||||
case .wifiSelection:
|
||||
WifiSelection()
|
||||
|
||||
case .wifiPairingTutorial:
|
||||
WifiPairingView()
|
||||
|
||||
case .settings:
|
||||
SettingsView()
|
||||
|
||||
case .bleSettings:
|
||||
BLESettingsView()
|
||||
|
||||
default:
|
||||
FillerView()
|
||||
}
|
||||
|
||||
}
|
||||
.environmentObject(currentCellID)
|
||||
.environmentObject(currentBLECellID)
|
||||
|
||||
|
||||
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
|
||||
if !Config.isSimulatingBluetooth {
|
||||
|
|
@ -71,10 +93,24 @@ struct RootView: View {
|
|||
if isConnectedOrReconnecting, model.destination == .fileTransfer {
|
||||
model.destination = .fileTransfer
|
||||
isReconnecting = true
|
||||
|
||||
|
||||
} else {
|
||||
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")
|
||||
FileTransferConnectionManager.shared.reconnect()
|
||||
}
|
||||
|
||||
.environmentObject(model)
|
||||
.environmentObject(connectionManager)
|
||||
.background(Color.white)
|
||||
|
|
@ -89,6 +126,7 @@ struct RootView: View {
|
|||
.edgesIgnoringSafeArea(.all)
|
||||
.ignoresSafeArea(.all)
|
||||
.preferredColorScheme(.light)
|
||||
.statusBar(hidden: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
import Foundation
|
||||
import FileTransferClient
|
||||
|
||||
class RootViewModel: ObservableObject {
|
||||
public class RootViewModel: ObservableObject {
|
||||
|
||||
// public var shared = RootViewModel()
|
||||
|
||||
enum Destination {
|
||||
//case splash
|
||||
|
|
@ -20,7 +22,13 @@ class RootViewModel: ObservableObject {
|
|||
case fileTransfer
|
||||
case wifi
|
||||
case settings
|
||||
case bleSettings
|
||||
case mainSelection
|
||||
case wifiSelection
|
||||
case wifiPairingTutorial
|
||||
case wifiServiceSelection
|
||||
case selection
|
||||
|
||||
}
|
||||
|
||||
@Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup
|
||||
|
|
@ -30,6 +38,18 @@ class RootViewModel: ObservableObject {
|
|||
//destination = .test
|
||||
}
|
||||
|
||||
func goToWiFiServiceSelection() {
|
||||
destination = .wifiServiceSelection
|
||||
}
|
||||
|
||||
func goToWifiPairingTutorial() {
|
||||
destination = .wifiPairingTutorial
|
||||
}
|
||||
|
||||
func goToWiFiSelection() {
|
||||
destination = .wifiSelection
|
||||
}
|
||||
|
||||
func goToWifiView() {
|
||||
destination = .wifi
|
||||
}
|
||||
|
|
@ -38,6 +58,10 @@ class RootViewModel: ObservableObject {
|
|||
destination = .bluetoothPairing
|
||||
}
|
||||
|
||||
func goToSelection(){
|
||||
destination = .selection
|
||||
}
|
||||
|
||||
func goToMainSelection(){
|
||||
destination = .mainSelection
|
||||
}
|
||||
|
|
@ -52,6 +76,10 @@ class RootViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func backToMain() {
|
||||
destination = .main
|
||||
}
|
||||
|
||||
func goToStartup(){
|
||||
destination = .startup
|
||||
}
|
||||
|
|
@ -64,10 +92,14 @@ class RootViewModel: ObservableObject {
|
|||
destination = .fileTransfer
|
||||
}
|
||||
|
||||
func goToSettings(){
|
||||
func goToSettings(content: SettingState){
|
||||
destination = .settings
|
||||
}
|
||||
|
||||
func goToBLESettings(){
|
||||
destination = .bleSettings
|
||||
}
|
||||
|
||||
func showWarningIfBluetoothStateIsNotReady() {
|
||||
let bluetoothState = BleManager.shared.state
|
||||
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)
|
||||
}
|
||||
|
||||
var height: CGFloat
|
||||
var width: CGFloat
|
||||
|
||||
var body: some View {
|
||||
|
||||
ZStack {
|
||||
Image("BlinkaLoading")
|
||||
.resizable(resizingMode: .stretch)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 300, height: 300)
|
||||
.frame(width: width, height: height)
|
||||
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
|
||||
.animation(self.isAnimating ? foreverAnimation : .default)
|
||||
|
||||
|
|
@ -34,6 +37,6 @@ struct BlinkaAnimationView: View {
|
|||
|
||||
struct ScanningView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
BlinkaAnimationView()
|
||||
BlinkaAnimationView(height: 300, width: 300)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct RunItButton: View {
|
|||
.cornerRadius(25)
|
||||
.foregroundColor(Color("pyleap_pink"))
|
||||
|
||||
Text("Run it!")
|
||||
Text("Run")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||
.foregroundColor(Color.white)
|
||||
.frame(height: 50)
|
||||
|
|
@ -35,8 +35,8 @@ struct PairingTutorialButton: View {
|
|||
Rectangle()
|
||||
.frame(width: 270, height: 50, alignment: .center)
|
||||
.cornerRadius(25)
|
||||
.foregroundColor(Color("pyleap_pink"))
|
||||
|
||||
.foregroundColor(Color("bluetooth_button_color"))
|
||||
|
||||
Text("Pairing Tutorial")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||
.foregroundColor(Color.white)
|
||||
|
|
|
|||
|
|
@ -10,50 +10,44 @@ import SwiftUI
|
|||
struct HeaderView: View {
|
||||
@State var showSheetView = false
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
|
||||
Button {
|
||||
rootViewModel.goToWifiView()
|
||||
} label: {
|
||||
Image(systemName: "wifi.circle")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.foregroundColor(.white)
|
||||
|
||||
}
|
||||
.padding()
|
||||
|
||||
|
||||
Spacer()
|
||||
Image("pyleap_logo_white")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 125, height: 125)
|
||||
.offset(y: 12)
|
||||
// .padding(.leading, 60)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
self.showSheetView.toggle()
|
||||
} label: {
|
||||
Image(systemName: "info.circle")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.foregroundColor(.white)
|
||||
}.sheet(isPresented: $showSheetView) {
|
||||
CreditView(isPresented: $showSheetView)
|
||||
}
|
||||
|
||||
.padding()
|
||||
}
|
||||
HStack (alignment: .center, spacing: 0) {
|
||||
|
||||
Image(systemName: "gearshape")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.padding(.leading, CGFloat(20))
|
||||
.foregroundColor(.clear)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("pyleap_logo_white")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 125, height: 125)
|
||||
.offset(y: 12)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
rootViewModel.goToBLESettings()
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.padding(.trailing, CGFloat(20))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxHeight: 120)
|
||||
.background(Color("pyleap_gray"))
|
||||
|
|
@ -107,6 +101,7 @@ struct MainHeaderView: View {
|
|||
|
||||
|
||||
struct HeaderView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
HeaderView()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ struct SubHeaderView: View {
|
|||
var body: some View {
|
||||
HStack {
|
||||
|
||||
Text("Browse available Wi-Fi PyLeap Projects")
|
||||
Text("Browse available WiFi PyLeap Projects")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||
|
|
@ -22,10 +22,14 @@ struct SubHeaderView: View {
|
|||
}
|
||||
|
||||
struct MainSubHeaderView: View {
|
||||
let device: String
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
HStack {
|
||||
|
||||
Text("Browse available PyLeap Projects")
|
||||
Text("Pick a project to run on your \(device)")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 25))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct WifiHeaderView: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@State var showSheetView = false
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
|
||||
|
|
@ -15,41 +16,38 @@ struct WifiHeaderView: View {
|
|||
|
||||
VStack {
|
||||
|
||||
HStack {
|
||||
|
||||
Button {
|
||||
rootViewModel.goToMain()
|
||||
} label: {
|
||||
Image(systemName: "arrow.backward")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
.padding()
|
||||
|
||||
|
||||
HStack (alignment: .center, spacing: 0) {
|
||||
|
||||
Image(systemName: "gearshape")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.padding(.leading, CGFloat(20))
|
||||
.foregroundColor(.clear)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("pyleap_logo_white")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 125, height: 125)
|
||||
.offset(y: 12)
|
||||
// .padding(.leading, 60)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
rootViewModel.goToSettings()
|
||||
rootViewModel.goToSettings(content: .wifi)
|
||||
} label: {
|
||||
Image(systemName: "gearshape")
|
||||
Image(systemName: "plus")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.offset(y: 15)
|
||||
.padding(.trailing, CGFloat(20))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
.padding()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// PyLeap
|
||||
//
|
||||
// Created by Trevor Beaton on 10/16/21.
|
||||
//
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import FileTransferClient
|
||||
|
|
@ -14,78 +14,54 @@ enum AdafruitDevices {
|
|||
case esp32s2
|
||||
}
|
||||
|
||||
struct MainSelectionView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@EnvironmentObject private var connectionManager: FileTransferConnectionManager
|
||||
|
||||
struct MainSelectionView: View {
|
||||
|
||||
@State private var showWebViewPopover: Bool = false
|
||||
|
||||
|
||||
@ObservedObject var networkModel = NetworkService()
|
||||
|
||||
@State private var inConnectedInSelectionView = true
|
||||
@State private var boardBootInfo = ""
|
||||
@EnvironmentObject var expandedState : ExpandedBLECellState
|
||||
|
||||
@ObservedObject var viewModel = MainSelectionViewModel()
|
||||
|
||||
|
||||
|
||||
|
||||
@State private var isConnected = false
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
|
||||
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: {
|
||||
Text("Not Connected to a Device.")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
Button {
|
||||
rootViewModel.goTobluetoothPairing()
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ScrollView {
|
||||
|
||||
MainSubHeaderView()
|
||||
|
||||
if networkModel.pdemos.isEmpty {
|
||||
|
||||
MainSubHeaderView(device: "Adafruit device")
|
||||
|
||||
if viewModel.pdemos.isEmpty {
|
||||
HStack{
|
||||
Spacer()
|
||||
ProgressView()
|
||||
|
|
@ -93,24 +69,48 @@ struct MainSelectionView: View {
|
|||
Spacer()
|
||||
}
|
||||
.padding(0)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
ScrollViewReader { scroll in
|
||||
|
||||
ForEach(networkModel.pdemos) { demo in
|
||||
DemoViewCell(result: demo, isConnected: $isConnected, bootOne: $test, onViewGeometryChanged: {
|
||||
withAnimation {
|
||||
scroll.scrollTo(demo.id)
|
||||
|
||||
|
||||
|
||||
|
||||
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 {
|
||||
scroll.scrollTo(demo.id)
|
||||
}
|
||||
|
||||
}
|
||||
}, stateBinder: $nilBinder)
|
||||
|
||||
} else {
|
||||
|
||||
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
|
||||
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.onDisappear() {
|
||||
|
||||
}
|
||||
|
||||
/// **Pull down to Refresh feature**
|
||||
// ScrollRefreshableView(title: "Refresh", tintColor: .purple) {
|
||||
// HStack{
|
||||
|
|
@ -136,17 +136,19 @@ struct MainSelectionView: View {
|
|||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
.onChange(of: viewModel.pdemos, perform: { newValue in
|
||||
print("Update")
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
.onAppear() {
|
||||
networkModel.fetch()
|
||||
|
||||
|
||||
print("Opened MainSelectionView")
|
||||
}
|
||||
|
||||
|
||||
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
|
||||
ExampleView(shouldShowOnboarding: $shouldShowOnboarding)
|
||||
})
|
||||
|
|
@ -154,14 +156,91 @@ struct MainSelectionView: View {
|
|||
.background(Color.white)
|
||||
.navigationBarColor(UIColor(named: "pyleap_gray"))
|
||||
.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 SwiftUI
|
||||
import Network
|
||||
import Combine
|
||||
|
||||
class MainSelectionViewModel: ObservableObject {
|
||||
|
||||
@ObservedObject var networkModel = NetworkService()
|
||||
|
||||
let userDefaults = UserDefaults.standard
|
||||
class InternetConnectionManager: ObservableObject {
|
||||
|
||||
@Published var pdemos : [ResultItem] = []
|
||||
private let monitor = NWPathMonitor()
|
||||
private let queue = DispatchQueue(label: "InternetConnectionMonitor")
|
||||
@Published var isConnected = false
|
||||
|
||||
init() {
|
||||
// load()
|
||||
// self.pdemos = load()
|
||||
}
|
||||
|
||||
func makeNetworkCall(){
|
||||
networkModel.fetch()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func load() -> [ResultItem] {
|
||||
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
|
||||
|
||||
startMonitoring(completion: {
|
||||
monitor.pathUpdateHandler = { path in
|
||||
|
||||
print("Load saved projects")
|
||||
print(loadedProjects)
|
||||
// pdemos = loadedProjects
|
||||
return loadedProjects
|
||||
DispatchQueue.main.async {
|
||||
let newIsConnected = path.status == .satisfied
|
||||
if self.isConnected != newIsConnected {
|
||||
self.isConnected = newIsConnected
|
||||
print("net: \(path.status) \(self.isConnected)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
print("Returned Empty pdemos")
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
func startMonitoring(completion:()->Void) {
|
||||
print("Start Monitoring Network")
|
||||
monitor.start(queue: queue)
|
||||
completion()
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("Network Deinit")
|
||||
monitor.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
class MainSelectionViewModel: ObservableObject {
|
||||
|
||||
@ObservedObject var networkModel = NetworkService()
|
||||
@ObservedObject var networkMonitor = InternetConnectionManager()
|
||||
|
||||
|
||||
let fileManager = FileManager.default
|
||||
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
|
||||
let dataStore = DataStore()
|
||||
|
||||
@Published var pdemos : [ResultItem] = []
|
||||
var networkMonitorCancellable: AnyCancellable?
|
||||
|
||||
init() {
|
||||
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
|
||||
|
||||
|
||||
networkMonitorCancellable = networkMonitor.$isConnected.sink { isConnected in
|
||||
if isConnected {
|
||||
print("The device is currently connected to the internet.")
|
||||
// Perform some action when the device is connected to the internet.
|
||||
self.networkModel.fetch {
|
||||
self.pdemos = self.dataStore.loadDefaultList()
|
||||
}
|
||||
|
||||
} else {
|
||||
print("The device is not currently connected to the internet.")
|
||||
// Perform some action when the device is not connected to the internet.
|
||||
print("Loading cached remote data.")
|
||||
self.pdemos = self.dataStore.loadDefaultList()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
class SettingsViewModel: ObservableObject {
|
||||
|
||||
|
||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||
let userDefaults = UserDefaults.standard
|
||||
@Published var hostName = ""
|
||||
|
|
@ -19,6 +19,7 @@ class SettingsViewModel: ObservableObject {
|
|||
@Published var invalidURL = false
|
||||
@Published var confirmDownload = false
|
||||
|
||||
|
||||
init() {
|
||||
check()
|
||||
registerNotifications(enabled: true)
|
||||
|
|
@ -56,10 +57,9 @@ errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkReq
|
|||
|
||||
func check() {
|
||||
print(#function)
|
||||
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
||||
connectedToDevice = false
|
||||
} else {
|
||||
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||
|
||||
connectedToDevice = true
|
||||
|
||||
|
|
@ -8,22 +8,37 @@
|
|||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
|
||||
class ExpandedState: ObservableObject {
|
||||
@Published var currentCell = ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct WifiCell: View {
|
||||
|
||||
@EnvironmentObject var expandedState : ExpandedState
|
||||
|
||||
let result : ResultItem
|
||||
|
||||
@State private var isExpanded: Bool = false {
|
||||
@State var isExpanded: Bool = false {
|
||||
didSet {
|
||||
onViewGeometryChanged()
|
||||
onViewGeometryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@State var isExpandedTest: String = ""
|
||||
|
||||
|
||||
@ObservedObject var viewModel = WifiCellViewModel()
|
||||
@Binding var isConnected: Bool
|
||||
@Binding var bootOne: String
|
||||
@Binding var stateBinder: DownloadState
|
||||
|
||||
var showRunItButton = false
|
||||
|
||||
var projectName = String()
|
||||
|
||||
let onViewGeometryChanged: ()->Void
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -31,48 +46,53 @@ struct WifiCell: View {
|
|||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
var header: some View {
|
||||
|
||||
HStack {
|
||||
Text(result.projectName)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||
.padding(8)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.down")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 30, height: 15, alignment: .center)
|
||||
.foregroundColor(.white)
|
||||
.padding(.trailing, 30)
|
||||
}
|
||||
|
||||
|
||||
.padding(.vertical, 5)
|
||||
.padding(.leading)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color("pyleap_purple"))
|
||||
.onTapGesture {
|
||||
expandedState.currentCell = result.bundleLink
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
var content: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
header
|
||||
|
||||
if isExpanded {
|
||||
|
||||
|
||||
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)
|
||||
WifiSubViewCell(result: result, bindingString: $bootOne, downloadStateBinder: $stateBinder,isConnected: $isConnected)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
|
||||
HStack {
|
||||
Text(result.projectName)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 24))
|
||||
.padding(8)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.down")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 15, alignment: .center)
|
||||
.foregroundColor(.white)
|
||||
.padding(.trailing, 30)
|
||||
}
|
||||
|
||||
.padding(.vertical, 5)
|
||||
.padding(.leading)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color("pyleap_purple"))
|
||||
.onTapGesture { isExpanded.toggle() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
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 SwiftUI
|
||||
|
||||
struct ResolvedService {
|
||||
struct ResolvedService: Identifiable, Equatable {
|
||||
var id = UUID()
|
||||
var ipAddress: String
|
||||
var hostName: String
|
||||
var device: String
|
||||
|
|
@ -37,36 +38,58 @@ class WifiServiceManager: NSObject, ObservableObject {
|
|||
|
||||
override init() {
|
||||
super.init()
|
||||
print("Wifi Module Used")
|
||||
serviceManagerBrowser.delegate = self
|
||||
findService()
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("Wifi Module Removed")
|
||||
self.serviceManagerBrowser.stop()
|
||||
}
|
||||
|
||||
|
||||
func findService() {
|
||||
print("Start Scan")
|
||||
// self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
||||
resolvedServices.removeAll()
|
||||
|
||||
print("Current state of isSearching: \(isSearching)")
|
||||
|
||||
if isSearching == false {
|
||||
print("Start Scanning")
|
||||
startDiscovery()
|
||||
}
|
||||
}
|
||||
|
||||
func startDiscovery() {
|
||||
// connectionStatus = .connected
|
||||
print("Start Scan")
|
||||
isSearching = true
|
||||
print("Start Discovery")
|
||||
DispatchQueue.main.async {
|
||||
self.isSearching = true
|
||||
}
|
||||
|
||||
|
||||
print("\(#function) @Line: \(#line)")
|
||||
print("Current state of isSearching: \(isSearching)")
|
||||
self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
|
||||
|
||||
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 12, repeats: false) { timer in
|
||||
|
||||
self.stopDiscoveryScan()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func stopDiscoveryScan() {
|
||||
if isSearching {
|
||||
isSearching = false
|
||||
self.serviceManagerBrowser.stop()
|
||||
print(resolvedServices)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
||||
|
||||
|
|
@ -88,11 +111,26 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
|||
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) {
|
||||
print(#function)
|
||||
discoveredService = service
|
||||
discoveredService?.delegate = self
|
||||
discoveredService?.resolve(withTimeout: 10)
|
||||
discoveredService?.resolve(withTimeout: 7)
|
||||
|
||||
if services.contains(service) {
|
||||
print("All ready in service array")
|
||||
|
|
@ -101,13 +139,22 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
|||
services.append(service)
|
||||
}
|
||||
|
||||
if moreComing == false {
|
||||
browser.stop()
|
||||
|
||||
|
||||
if !moreComing {
|
||||
serviceManagerBrowser.stop()
|
||||
isSearching = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
print("Service: \(service)")
|
||||
|
||||
print("Service count: \(services.count)")
|
||||
self.serviceManagerBrowser.remove(from: .main, forMode: .common)
|
||||
}
|
||||
|
||||
|
||||
func netServiceDidStop(_ sender: NetService) {
|
||||
isSearching = false
|
||||
print("isSearching: \(isSearching)")
|
||||
|
|
@ -136,7 +183,16 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
|||
|
||||
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
|
||||
|
||||
resolvedServices.append(resolvedService)
|
||||
|
||||
if resolvedServices.contains(where: {$0.ipAddress == resolvedService.ipAddress}) {
|
||||
// it exists, do nothing
|
||||
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) exists in network")
|
||||
} else {
|
||||
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) Added to Network List")
|
||||
resolvedServices.append(resolvedService)
|
||||
}
|
||||
|
||||
|
||||
print("resolvedServices count: \(resolvedServices.count)")
|
||||
|
||||
}
|
||||
|
|
@ -148,17 +204,7 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
|
|||
|
||||
|
||||
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
||||
|
||||
//self.discovered.removeAll { $0.name == service.name }
|
||||
print("didRemove")
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
|
||||
@Binding var hostName: String
|
||||
|
||||
func showConfirmationPrompt() {
|
||||
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
|
||||
rootViewModel.goToSelection()
|
||||
} cancel: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 8, content: {
|
||||
HStack(alignment: .center, spacing: 0, content: {
|
||||
|
||||
Text("Connected To \(hostName)")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
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)
|
||||
}
|
||||
|
||||
})
|
||||
.padding(.all, 0.0)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -34,7 +59,7 @@ struct WifiStatusNoConnectionView: View {
|
|||
|
||||
HStack(alignment: .center, spacing: 8, content: {
|
||||
Text("No Device Detected")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
.font(Font.custom("ReadexPro-SemiBold", size: 14))
|
||||
|
||||
})
|
||||
.padding(.all, 0.0)
|
||||
|
|
@ -50,7 +75,7 @@ struct WifiStatusConnectingView: View {
|
|||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 8, content: {
|
||||
Text("Searching for Adafruit Devices...")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
||||
})
|
||||
.padding(.all, 0.0)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
|
@ -68,7 +93,7 @@ struct NetworkConnectionBanner: View {
|
|||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 8, content: {
|
||||
Text("Searching local network...")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
.font(Font.custom("ReadexPro-Regular", size: 14))
|
||||
|
||||
// ProgressView()
|
||||
//.resizable()
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@
|
|||
import SwiftUI
|
||||
|
||||
struct WifiSubViewCell: View {
|
||||
|
||||
@State var transferInProgress = false
|
||||
|
||||
@State var isDownloaded = false
|
||||
|
||||
@EnvironmentObject var globalString : GlobalString
|
||||
|
||||
@StateObject var wifiFileTransfer = WifiFileTransfer()
|
||||
|
||||
|
||||
@StateObject var wifiTransferService = WifiTransferService()
|
||||
let result : ResultItem
|
||||
|
||||
@Binding var bindingString: String
|
||||
|
||||
|
|
@ -23,18 +23,17 @@ struct WifiSubViewCell: View {
|
|||
|
||||
@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
|
||||
@StateObject var downloadModel = DownloadViewModel()
|
||||
@ObservedObject var viewModel = WifiSubViewCellModel()
|
||||
|
||||
@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 var errorOccured = false
|
||||
@State private var presentAlert = false
|
||||
|
|
@ -42,6 +41,100 @@ struct WifiSubViewCell: View {
|
|||
@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 {
|
||||
|
||||
|
|
@ -49,14 +142,14 @@ struct WifiSubViewCell: View {
|
|||
|
||||
VStack(alignment: .leading, spacing: 0, content: {
|
||||
|
||||
ImageWithURL(image)
|
||||
ImageWithURL(result.projectImage)
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity)
|
||||
.cornerRadius(14)
|
||||
.padding(.top, 30)
|
||||
|
||||
|
||||
Text(description)
|
||||
|
||||
Text(result.description)
|
||||
.font(Font.custom("ReadexPro-Regular", size: 18))
|
||||
.multilineTextAlignment(.leading)
|
||||
.minimumScaleFactor(0.1)
|
||||
|
|
@ -64,9 +157,20 @@ struct WifiSubViewCell: View {
|
|||
Text("Compatible with:")
|
||||
.font(Font.custom("ReadexPro-Bold", size: 18))
|
||||
.padding(.top, 5)
|
||||
|
||||
|
||||
ForEach(compatibility, id: \.self) { string in
|
||||
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(result.compatibility, id: \.self) { string in
|
||||
if string == "circuitplayground_bluefruit" {
|
||||
|
||||
HStack {
|
||||
|
|
@ -81,7 +185,7 @@ struct WifiSubViewCell: View {
|
|||
.padding(.top, 10)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if string == "clue_nrf52840_express" {
|
||||
|
||||
|
|
@ -109,7 +213,7 @@ struct WifiSubViewCell: View {
|
|||
.padding(.top, 20)
|
||||
}
|
||||
.sheet(isPresented: $showWebViewPopover, content: {
|
||||
WebView(URLRequest(url: learnGuideLink.url!))
|
||||
SwiftUIWebView(webAddress: result.learnGuideLink)
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -117,18 +221,16 @@ struct WifiSubViewCell: View {
|
|||
|
||||
if isConnected {
|
||||
|
||||
if compatibility.contains(bindingString) {
|
||||
|
||||
if result.compatibility.contains(bindingString) {
|
||||
|
||||
|
||||
if wifiFileTransfer.testIndex.downloadState == .idle {
|
||||
|
||||
|
||||
Button {
|
||||
print("Wifi Project Attempt \(title)")
|
||||
|
||||
if wifiFileTransfer.projectDownloaded {
|
||||
wifiFileTransfer.projectValidation(nameOf: title)
|
||||
} else {
|
||||
downloadModel.startDownload(urlString: downloadLink, projectTitle: title) {
|
||||
print("DONE")
|
||||
}
|
||||
}
|
||||
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
|
||||
|
||||
testOperation()
|
||||
|
||||
} label: {
|
||||
RunItButton()
|
||||
|
|
@ -137,8 +239,40 @@ 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 {
|
||||
|
||||
|
|
@ -151,61 +285,87 @@ struct WifiSubViewCell: View {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
.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)
|
||||
|
||||
|
||||
.onAppear(perform: {
|
||||
|
||||
wifiFileTransfer.registerWifiNotification(enabled: true)
|
||||
|
||||
viewModel.searchPathForProject(nameOf: result.projectName)
|
||||
|
||||
|
||||
if viewModel.projectDownloaded {
|
||||
isDownloaded = true
|
||||
} else {
|
||||
isDownloaded = false
|
||||
}
|
||||
print("is downloaded? \(viewModel.projectDownloaded)")
|
||||
|
||||
})
|
||||
|
||||
|
||||
.padding(.top, 8)
|
||||
|
||||
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
|
||||
if newValue {
|
||||
showTransferErrorMessage()
|
||||
}
|
||||
})
|
||||
|
||||
.onChange(of: viewModel.showUsbInUseError) { newValue in
|
||||
if newValue {
|
||||
usbInUseErrorMessage()
|
||||
}
|
||||
}else {
|
||||
print("Is not downloaded")
|
||||
isDownloaded = false
|
||||
}
|
||||
|
||||
.onChange(of: wifiFileTransfer.counter) { newValue in
|
||||
print("New counter : \(newValue)")
|
||||
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)")
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
.onAppear(perform: {
|
||||
wifiFileTransfer.getProjectForSubClass(nameOf: title)
|
||||
if wifiFileTransfer.projectDownloaded {
|
||||
isDownloaded = true
|
||||
} else {
|
||||
isDownloaded = false
|
||||
|
||||
.onChange(of: wifiFileTransfer.downloadState) { newValue in
|
||||
switch newValue {
|
||||
case .idle:
|
||||
downloadState = .idle
|
||||
print("idle")
|
||||
case .transferring:
|
||||
downloadState = .transferring
|
||||
print("transferring")
|
||||
case .complete:
|
||||
downloadState = .complete
|
||||
print("complete")
|
||||
case .downloading:
|
||||
downloadState = .downloading
|
||||
print("downloading")
|
||||
case .failed:
|
||||
downloadState = .failed
|
||||
print("failed")
|
||||
}
|
||||
}
|
||||
print("is downloaded? \(isDownloaded)")
|
||||
})
|
||||
.padding(.top, 8)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,40 +6,119 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class WifiSubViewCellModel: ObservableObject {
|
||||
|
||||
@ObservedObject var wifiTransferService = WifiTransferService()
|
||||
|
||||
@ObservedObject var wifiFileTransfer = WifiFileTransfer()
|
||||
|
||||
@Published var downloadState: DownloadState = .idle
|
||||
|
||||
@Published var projectDownloaded = 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) {
|
||||
|
||||
init() {
|
||||
registerForUSBInUseErrorNotification(enabled: true)
|
||||
}
|
||||
|
||||
private weak var usbInUseErrorNotification: NSObjectProtocol?
|
||||
|
||||
private func registerForUSBInUseErrorNotification(enabled: Bool) {
|
||||
print("\(#function) @Line: \(#line)")
|
||||
|
||||
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 {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
|
||||
if enabled {
|
||||
|
||||
if fileURL.lastPathComponent == project {
|
||||
failedProjectLaunch = false
|
||||
projectDownloaded = true
|
||||
print(#function)
|
||||
print("Searching for... \(project)")
|
||||
print("URL Path: \(fileURL.path)")
|
||||
print("URL : \(fileURL)")
|
||||
|
||||
return
|
||||
|
||||
} else {
|
||||
failedProjectLaunch = true
|
||||
projectDownloaded = false
|
||||
print("Project was not found...")
|
||||
|
||||
}
|
||||
}
|
||||
// NotificationCenter.default.addObserver(self, selector: #selector(zipSuccess(_:)), name: .usbInUseErrorNotification,object: nil)
|
||||
|
||||
usbInUseErrorNotification = notificationCenter.addObserver(forName: .usbInUseErrorNotification, object: nil, queue: .main, using: {[weak self] _ in self?.zipSuccess()})
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func zipSuccess() {
|
||||
showUsbInUseError = true
|
||||
}
|
||||
|
||||
func checkIfUSBInUse() {
|
||||
|
||||
wifiTransferService.optionRequest(handler: { result in
|
||||
switch result {
|
||||
|
||||
case .success:
|
||||
print("Success")
|
||||
|
||||
self.wifiTransferService.getRequest(read: "boot_out.txt") { result in
|
||||
print(result)
|
||||
}
|
||||
|
||||
|
||||
case .failure:
|
||||
print("Failure")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
|
||||
//
|
||||
// print("USB not in use.")
|
||||
// DispatchQueue.main.async {
|
||||
// self.usbInUse = false
|
||||
// }
|
||||
//
|
||||
// // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
// // if wifiFileTransfer.projectDownloaded {
|
||||
// //
|
||||
// // wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
|
||||
// //
|
||||
// // } else {
|
||||
// // downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// } else {
|
||||
// DispatchQueue.main.async {
|
||||
// self.usbInUse = true
|
||||
// }
|
||||
// print("USB in use - files cannot be tranferred or moved while USB is in use. Show Error")
|
||||
// }
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ protocol WifiTransferServiceDelegate: AnyObject {
|
|||
|
||||
class WifiTransferService: ObservableObject {
|
||||
|
||||
weak var delegate: WifiTransferServiceDelegate?
|
||||
// weak var delegate: WifiTransferServiceDelegate?
|
||||
|
||||
let userDefaults = UserDefaults.standard
|
||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||
|
||||
@Published var counter = 0
|
||||
|
||||
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
||||
|
||||
@Published var hostName = ""
|
||||
|
||||
func startup() {
|
||||
print(#function)
|
||||
print("Startup")
|
||||
print("\(#function) @Line: \(#line)")
|
||||
if (userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName")) != nil {
|
||||
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String)
|
||||
|
||||
|
|
@ -43,6 +43,7 @@ class WifiTransferService: ObservableObject {
|
|||
startup()
|
||||
}
|
||||
|
||||
//*
|
||||
func sendPutRequest(fileName: String,
|
||||
body: Data,
|
||||
then handler: @escaping(Result<Data, Error>) -> Void) {
|
||||
|
|
@ -76,30 +77,88 @@ class WifiTransferService: ObservableObject {
|
|||
print("Print curl:")
|
||||
print(request.cURL(pretty: true))
|
||||
|
||||
let task = urlSession.dataTask(
|
||||
with: request,
|
||||
completionHandler: { data, response, error in
|
||||
// Validate response and call handler
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
|
||||
if let error = error {
|
||||
print("File write error")
|
||||
|
||||
if let error = error {
|
||||
print("File write error")
|
||||
|
||||
handler(.failure(error))
|
||||
|
||||
}
|
||||
|
||||
if let data = data {
|
||||
print("File write success!")
|
||||
handler(.success(data))
|
||||
}
|
||||
handler(.failure(error))
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
if let data = data {
|
||||
print("File write success!")
|
||||
handler(.success(data))
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
@ -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://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
|
||||
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||
|
||||
request.httpMethod = "GET"
|
||||
|
||||
print("Print curl:")
|
||||
|
|
@ -147,8 +209,13 @@ class WifiTransferService: ObservableObject {
|
|||
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)
|
||||
|
||||
let username = ""
|
||||
|
|
@ -157,13 +224,14 @@ class WifiTransferService: ObservableObject {
|
|||
|
||||
var outgoingString = String()
|
||||
|
||||
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
|
||||
return "Error"
|
||||
}
|
||||
let base64LoginString = loginData.base64EncodedString()
|
||||
let loginData = loginString.data(using: String.Encoding.utf8)
|
||||
|
||||
|
||||
// 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("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||
request.httpMethod = "GET"
|
||||
|
|
@ -175,13 +243,11 @@ class WifiTransferService: ObservableObject {
|
|||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
guard let data = data else {
|
||||
print(String(describing: "Error Found: \(error)"))
|
||||
semaphore.signal()
|
||||
return
|
||||
}
|
||||
// print(String(data: data, encoding: .utf8)!)
|
||||
semaphore.signal()
|
||||
|
||||
do {
|
||||
print("In do-catch loop of getRequest")
|
||||
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -192,18 +258,124 @@ class WifiTransferService: ObservableObject {
|
|||
}
|
||||
|
||||
if let str = String(data: data, encoding: .utf8) {
|
||||
print(str)
|
||||
print("Out-going getRequest data: \(str)")
|
||||
outgoingString = str
|
||||
print("\(#function) @Line: \(#line)")
|
||||
completionHandler(outgoingString)
|
||||
|
||||
} else {
|
||||
print("\(#function) @Line: \(#line)")
|
||||
print("Error")
|
||||
}
|
||||
|
||||
}
|
||||
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 putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||
print("Test Transfer")
|
||||
let parameters = fileContent
|
||||
let postData = parameters
|
||||
|
||||
|
|
@ -240,15 +412,16 @@ class WifiTransferService: ObservableObject {
|
|||
print("File write success!")
|
||||
completion(.success(data))
|
||||
}
|
||||
|
||||
// print(String(data: data, encoding: .utf8)!)
|
||||
|
||||
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
// Make
|
||||
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||
print("\(#function) @Line: \(#line)")
|
||||
|
||||
|
||||
|
||||
let username = ""
|
||||
let password = "passw0rd"
|
||||
|
|
@ -271,12 +444,20 @@ class WifiTransferService: ObservableObject {
|
|||
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 {
|
||||
print("Write Directory Failure")
|
||||
completion(.failure(error))
|
||||
|
||||
}
|
||||
|
||||
if let data = data {
|
||||
print("Write Directory Success")
|
||||
completion(.success(data))
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +482,7 @@ class WifiTransferService: ObservableObject {
|
|||
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/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("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
|
||||
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
// Created by Trevor Beaton on 8/9/22.
|
||||
//
|
||||
|
||||
// My IP Address - 192.168.1.111
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
|
@ -14,32 +13,70 @@ struct WifiView: View {
|
|||
|
||||
@StateObject var viewModel = WifiViewModel()
|
||||
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||
@StateObject var wifiviewModel = WifiServiceManager()
|
||||
|
||||
// User Defaults
|
||||
let userDefaults = UserDefaults.standard
|
||||
|
||||
@ObservedObject var networkModel = NetworkService()
|
||||
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
|
||||
|
||||
@State private var downloadState = DownloadState.idle
|
||||
@State private var scrollViewID = UUID()
|
||||
@State private var inConnectedInWifiView = true
|
||||
@State private var boardBootInfo = "esp32-s2"
|
||||
@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() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
viewModel.checkServices(ip: text)
|
||||
|
||||
} secondaryAction: {
|
||||
print("Cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func showAlertMessage() {
|
||||
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
|
||||
|
|
@ -47,106 +84,61 @@ 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 {
|
||||
|
||||
|
||||
VStack(spacing: 0) {
|
||||
WifiHeaderView()
|
||||
|
||||
if wifiviewModel.isSearching {
|
||||
NetworkConnectionBanner()
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
Group{
|
||||
switch viewModel.connectionStatus {
|
||||
case .connected:
|
||||
case .connected:
|
||||
WifiStatusConnectedView(hostName: $hostName)
|
||||
case .noConnection:
|
||||
WifiStatusNoConnectionView()
|
||||
case .connecting:
|
||||
WifiStatusConnectingView()
|
||||
}
|
||||
}
|
||||
|
||||
if !viewModel.ipAddressStored {
|
||||
HStack(alignment: .center, content: {
|
||||
|
||||
Button {
|
||||
showValidationPrompt()
|
||||
|
||||
} label: {
|
||||
Text("Enter IP address")
|
||||
.font(Font.custom("ReadexPro-Regular", size: 16))
|
||||
.foregroundColor(.white)
|
||||
.background(.indigo)
|
||||
// .cornerRadius(15)
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
Button {
|
||||
wifiviewModel.findService()
|
||||
} label: {
|
||||
Text("Scan Network")
|
||||
.foregroundColor(.white)
|
||||
.background(.indigo)
|
||||
// .cornerRadius(15)
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
Button {
|
||||
rootViewModel.goTobluetoothPairing()
|
||||
} label: {
|
||||
Text("BLE Mode")
|
||||
.foregroundColor(.white)
|
||||
.background(.indigo)
|
||||
// .cornerRadius(15)
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
.padding(.all, 0.0)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(maxHeight: 40)
|
||||
.background(Color.clear)
|
||||
.foregroundColor(.black)
|
||||
} else {
|
||||
|
||||
case .noConnection:
|
||||
WifiStatusNoConnectionView()
|
||||
case .connecting:
|
||||
WifiStatusConnectingView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ScrollView(.vertical, showsIndicators: true) {
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
|
||||
ScrollViewReader { scroll in
|
||||
|
||||
SubHeaderView()
|
||||
|
||||
let check = networkModel.pdemos.filter {
|
||||
|
||||
|
||||
let check = viewModel.pdemos.filter {
|
||||
$0.compatibility.contains(boardBootInfo)
|
||||
}
|
||||
|
||||
ForEach(check) { demo in
|
||||
|
||||
WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||
withAnimation {
|
||||
scroll.scrollTo(demo.id)
|
||||
if demo.bundleLink == test.currentCell {
|
||||
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||
|
||||
|
||||
})
|
||||
.onAppear(){
|
||||
|
||||
withAnimation {
|
||||
scroll.scrollTo(demo.id)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
|
||||
withAnimation {
|
||||
// scroll.scrollTo(demo.id)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -154,52 +146,73 @@ struct WifiView: View {
|
|||
.id(self.scrollViewID)
|
||||
}
|
||||
.foregroundColor(.black)
|
||||
.environmentObject(test)
|
||||
}
|
||||
|
||||
|
||||
|
||||
.onChange(of: viewModel.connectionStatus, perform: { newValue in
|
||||
if newValue == .connected {
|
||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
.onChange(of: viewModel.wifiServiceManager.resolvedServices, perform: { newValue in
|
||||
print("Credential Check!")
|
||||
print(newValue)
|
||||
|
||||
if newValue.contains(where: { result in
|
||||
result.hostName == userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||
}) {
|
||||
print("Matched")
|
||||
|
||||
} else {
|
||||
print("Un-Matched")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
|
||||
print("viewModel.isInvalidIP .onChange")
|
||||
if newValue {
|
||||
showAlertMessage()
|
||||
viewModel.isInvalidIP.toggle()
|
||||
toggleViewModelIP()
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
.onAppear(){
|
||||
print("On Appear")
|
||||
networkModel.fetch()
|
||||
|
||||
// viewModel.checkStoredIP()
|
||||
initialIPStoreCheck()
|
||||
|
||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
|
||||
print("Nothing stored.")
|
||||
} else {
|
||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||
}
|
||||
|
||||
checkForStoredIPAddress()
|
||||
viewModel.printStoredInfo()
|
||||
viewModel.read()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct WifiView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WifiView()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
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 invalidIPNotif = Notification.Name(kPrefix+".invalidIPNotif")
|
||||
public static let invalidCustomNetworkRequest = Notification.Name(kPrefix+".invalidCustomNetworkRequest")
|
||||
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 UIKit
|
||||
import CoreLocation
|
||||
import Network
|
||||
|
||||
|
|
@ -23,21 +22,33 @@ class WifiViewModel: ObservableObject {
|
|||
private let kPrefix = Bundle.main.bundleIdentifier!
|
||||
|
||||
@Published var connectionStatus: ConnectionStatus = .noConnection
|
||||
@Published var isInvalidIP = false
|
||||
|
||||
@Published var isInvalidIP = false
|
||||
@Published var ipInputValidation = false
|
||||
//Dependencies
|
||||
var networkMonitor = NetworkMonitor()
|
||||
var networkAuth = LocalNetworkAuthorization()
|
||||
|
||||
public var wifiNetworkService = WifiNetworkService()
|
||||
|
||||
@Published var wifiTransferService = WifiTransferService()
|
||||
|
||||
@Published var wifiServiceManager = WifiServiceManager()
|
||||
|
||||
|
||||
|
||||
var circuitPythonVersion = Int()
|
||||
|
||||
@Published var webDirectoryInfo = [WebDirectoryModel]()
|
||||
|
||||
@Published var hostName = ""
|
||||
|
||||
@Published var downloadState: DownloadState = .idle
|
||||
|
||||
let dataStore = DataStore()
|
||||
|
||||
@Published var pdemos : [ResultItem] = []
|
||||
|
||||
|
||||
|
||||
// File Manager Data
|
||||
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
@Published var fileArray: [ContentFile] = []
|
||||
|
|
@ -45,21 +56,47 @@ class WifiViewModel: ObservableObject {
|
|||
|
||||
var projectDirectories: [URL] = []
|
||||
|
||||
var returnedArray = [[String]]()
|
||||
|
||||
var ipAddressStored = false
|
||||
|
||||
init() {
|
||||
|
||||
pdemos = dataStore.loadDefaultList()
|
||||
checkIP()
|
||||
registerNotifications(enabled: true)
|
||||
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 testObserver: NSObjectProtocol?
|
||||
private func registerNotifications(enabled: Bool) {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
|
||||
|
|
@ -76,6 +113,9 @@ class WifiViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func checkIP() {
|
||||
|
||||
print("Tiggered checkIP")
|
||||
|
|
@ -84,9 +124,7 @@ class WifiViewModel: ObservableObject {
|
|||
ipAddressStored = true
|
||||
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
|
||||
} else {
|
||||
|
||||
print(userDefaults.object(forKey: kPrefix+".storedIP"))
|
||||
ipAddressStored = false
|
||||
ipAddressStored = false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -110,15 +148,17 @@ class WifiViewModel: ObservableObject {
|
|||
|
||||
// @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) {
|
||||
|
|
@ -135,7 +175,6 @@ class WifiViewModel: ObservableObject {
|
|||
|
||||
func clearKnownIPAddress() {
|
||||
userDefaults.set(nil, forKey: kPrefix+".storedIP")
|
||||
printIPStorageAtLocation()
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -149,14 +188,11 @@ class WifiViewModel: ObservableObject {
|
|||
|
||||
let resolvedService = wifiServiceManager.resolvedServices.filter { $0.ipAddress == ip }
|
||||
|
||||
|
||||
// To store in UserDefaults
|
||||
|
||||
|
||||
storeResolvedAddress(service: resolvedService[0])
|
||||
storeIPAddress(ipAddress: ip)
|
||||
connectionStatus = .connected
|
||||
|
||||
ipInputValidation = true
|
||||
} else {
|
||||
isInvalidIP = true
|
||||
print("1 does not exists in the array")
|
||||
|
|
@ -164,16 +200,19 @@ class WifiViewModel: ObservableObject {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func checkStoredIP() {
|
||||
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
|
||||
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
|
||||
print("Nothing stored.")
|
||||
} else {
|
||||
NotificationCenter.default.post(name: .invalidIPNotif, object: nil, userInfo: nil)
|
||||
|
||||
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func internetMonitoring() {
|
||||
|
||||
networkMonitor.monitor.pathUpdateHandler = { path in
|
||||
|
|
|
|||
Loading…
Reference in a new issue