Compare commits

..

No commits in common. "master" and "Custom-project-fix" have entirely different histories.

62 changed files with 3197 additions and 5259 deletions

View file

@ -27,8 +27,6 @@ public class FileTransferConnectionManager: ObservableObject {
@Published public var isConnectedOrReconnecting = false // Is any peripheral connected or trying to connect @Published public var isConnectedOrReconnecting = false // Is any peripheral connected or trying to connect
@Published public var isAnyPeripheralConnecting = false @Published public var isAnyPeripheralConnecting = false
@Published public var isDisconnectingFromCurrent = false
// Parameters // Parameters
public var userDefaults = UserDefaults.standard // Can be replaced if data saved needs to be shared public var userDefaults = UserDefaults.standard // Can be replaced if data saved needs to be shared

View file

@ -1,25 +0,0 @@
MIT License
Copyright (c) 2023 Adafruit Industries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -11,14 +11,6 @@
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D505B99D2756894300386E9F /* ViewModifier.swift */; }; D505B99E2756894300386E9F /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D505B99D2756894300386E9F /* ViewModifier.swift */; };
D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.swift */; }; D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.swift */; };
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */; }; D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */; };
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */; };
D5267411292E902700D4C79E /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5267410292E902700D4C79E /* Networking.swift */; };
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269BFF291960A300C0CE4B /* WifiSelection.swift */; };
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */; };
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */; };
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C07291AB75800C0CE4B /* WifiPairingView.swift */; };
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A926C29071DF400973B6B /* SelectionView.swift */; };
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */; };
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */; }; D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */; };
D52BE7F3269DF62100630900 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = D52BE7F2269DF62100630900 /* Zip */; }; D52BE7F3269DF62100630900 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = D52BE7F2269DF62100630900 /* Zip */; };
D52BE82A26A0660200630900 /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54D38B82691E0D000FBFE47 /* KeyboardUtils.swift */; }; D52BE82A26A0660200630900 /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54D38B82691E0D000FBFE47 /* KeyboardUtils.swift */; };
@ -30,8 +22,6 @@
D52F7E7B2672F4C500911D43 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */; }; D52F7E7B2672F4C500911D43 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */; };
D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.swift */; }; D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.swift */; };
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */; }; D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */; };
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */; };
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361099296FB2BB00228E15 /* DataStore.swift */; };
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A1D1281B9BB70038D483 /* Buttons.swift */; }; D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A1D1281B9BB70038D483 /* Buttons.swift */; };
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.swift */; }; D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.swift */; };
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24E282046840038D483 /* OnAnimationComplete.swift */; }; D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24E282046840038D483 /* OnAnimationComplete.swift */; };
@ -53,8 +43,6 @@
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */; }; D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */; };
D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */; }; D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */; };
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2DE28C8D40C0009F768 /* SettingsView.swift */; }; D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2DE28C8D40C0009F768 /* SettingsView.swift */; };
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */; };
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */; };
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */; }; D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */; };
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */; }; D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */; };
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */; }; D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */; };
@ -118,14 +106,6 @@
D505B99D2756894300386E9F /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; }; D505B99D2756894300386E9F /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; };
D517F68026C5771D002996E8 /* FillerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillerView.swift; sourceTree = "<group>"; }; D517F68026C5771D002996E8 /* FillerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillerView.swift; sourceTree = "<group>"; };
D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleContentTransfer.swift; sourceTree = "<group>"; }; D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleContentTransfer.swift; sourceTree = "<group>"; };
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiCellViewModel.swift; sourceTree = "<group>"; };
D5267410292E902700D4C79E /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
D5269BFF291960A300C0CE4B /* WifiSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiSelection.swift; path = "PyLeap/Views/Unpaired View/WifiSelection.swift"; sourceTree = SOURCE_ROOT; };
D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellView.swift; path = ../../../../../../Desktop/WifiServiceCellView.swift; sourceTree = "<group>"; };
D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellSubView.swift; path = ../../../../../../Desktop/WifiServiceCellSubView.swift; sourceTree = "<group>"; };
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiPairingView.swift; sourceTree = "<group>"; };
D52A926C29071DF400973B6B /* SelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionView.swift; sourceTree = "<group>"; };
D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiServiceSelectionView.swift; sourceTree = "<group>"; };
D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadViewModel.swift; sourceTree = "<group>"; }; D52BE7EA269DF36E00630900 /* DownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadViewModel.swift; sourceTree = "<group>"; };
D52BE85326A0E39100630900 /* BTConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTConnectionViewModel.swift; sourceTree = "<group>"; }; D52BE85326A0E39100630900 /* BTConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTConnectionViewModel.swift; sourceTree = "<group>"; };
D52F7E702672F4C400911D43 /* PyLeap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PyLeap.app; sourceTree = BUILT_PRODUCTS_DIR; }; D52F7E702672F4C400911D43 /* PyLeap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PyLeap.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -135,8 +115,6 @@
D52F7E7C2672F4C500911D43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; D52F7E7C2672F4C500911D43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D534F3FB280B59090053699C /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = "<group>"; }; D534F3FB280B59090053699C /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = "<group>"; };
D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollRefreshableView.swift; sourceTree = "<group>"; }; D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollRefreshableView.swift; sourceTree = "<group>"; };
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONDecoderHelper.swift; sourceTree = "<group>"; };
D5361099296FB2BB00228E15 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
D544A1D1281B9BB70038D483 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; }; D544A1D1281B9BB70038D483 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
D544A24A281F92840038D483 /* ImageCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCaching.swift; sourceTree = "<group>"; }; D544A24A281F92840038D483 /* ImageCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCaching.swift; sourceTree = "<group>"; };
D544A24E282046840038D483 /* OnAnimationComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAnimationComplete.swift; sourceTree = "<group>"; }; D544A24E282046840038D483 /* OnAnimationComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAnimationComplete.swift; sourceTree = "<group>"; };
@ -162,8 +140,6 @@
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCell.swift; sourceTree = "<group>"; }; D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCell.swift; sourceTree = "<group>"; };
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCellModel.swift; sourceTree = "<group>"; }; D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCellModel.swift; sourceTree = "<group>"; };
D567E2DE28C8D40C0009F768 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; D567E2DE28C8D40C0009F768 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsView.swift; sourceTree = "<group>"; };
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsViewModel.swift; sourceTree = "<group>"; };
D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; }; D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DeletingPrefix.swift"; sourceTree = "<group>"; }; D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DeletingPrefix.swift"; sourceTree = "<group>"; };
D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferPathUtils.swift; sourceTree = "<group>"; }; D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferPathUtils.swift; sourceTree = "<group>"; };
@ -187,7 +163,6 @@
D59DFDC1268CFA36001737F6 /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; }; D59DFDC1268CFA36001737F6 /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; };
D59DFDC3268CFAB4001737F6 /* OnboardingDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDataModel.swift; sourceTree = "<group>"; }; D59DFDC3268CFAB4001737F6 /* OnboardingDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDataModel.swift; sourceTree = "<group>"; };
D59E31A9281B8DD300D24211 /* DownloadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadState.swift; sourceTree = "<group>"; }; D59E31A9281B8DD300D24211 /* DownloadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadState.swift; sourceTree = "<group>"; };
D5A3D4ED292575F000ECCEC9 /* ReadexPro-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ReadexPro-SemiBold.ttf"; sourceTree = "<group>"; };
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPythonType.swift; sourceTree = "<group>"; }; D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPythonType.swift; sourceTree = "<group>"; };
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiStatusHeaderBarView.swift; sourceTree = "<group>"; }; D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiStatusHeaderBarView.swift; sourceTree = "<group>"; };
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiListDetailView.swift; sourceTree = "<group>"; }; D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiListDetailView.swift; sourceTree = "<group>"; };
@ -360,42 +335,25 @@
D567E2DD28C8D3E20009F768 /* SettingsView */ = { D567E2DD28C8D3E20009F768 /* SettingsView */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D56B75D2294BAA8900D008E7 /* BLESetttings */,
D567E2DE28C8D40C0009F768 /* SettingsView.swift */, D567E2DE28C8D40C0009F768 /* SettingsView.swift */,
D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */, D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */,
); );
path = SettingsView; path = SettingsView;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D56B75D2294BAA8900D008E7 /* BLESetttings */ = {
isa = PBXGroup;
children = (
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */,
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */,
);
path = BLESetttings;
sourceTree = "<group>";
};
D58E1C8628A2B0DE00AB683E /* Wifi View */ = { D58E1C8628A2B0DE00AB683E /* Wifi View */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D567E2DD28C8D3E20009F768 /* SettingsView */,
D58E1C8728A2B10B00AB683E /* WifiView.swift */, D58E1C8728A2B10B00AB683E /* WifiView.swift */,
D567E2B728C137880009F768 /* WifiCell.swift */,
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */,
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */, D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */,
D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */,
D5269BFF291960A300C0CE4B /* WifiSelection.swift */,
D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */,
D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */,
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */, D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */, D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */,
D567E2B728C137880009F768 /* WifiCell.swift */,
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */, D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */,
D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */, D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */,
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */, D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */,
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */, D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */,
D567E2B528B81B730009F768 /* Queue.swift */, D567E2B528B81B730009F768 /* Queue.swift */,
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */, D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */,
@ -412,6 +370,7 @@
D59DFDB2268CCEAC001737F6 /* Views */ = { D59DFDB2268CCEAC001737F6 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D567E2DD28C8D3E20009F768 /* SettingsView */,
D58E1C8628A2B0DE00AB683E /* Wifi View */, D58E1C8628A2B0DE00AB683E /* Wifi View */,
D5507ACE26C668BC00512BAA /* UI Components */, D5507ACE26C668BC00512BAA /* UI Components */,
D59DFDB3268CCEB9001737F6 /* Onboarding Views */, D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
@ -469,7 +428,6 @@
D5C474C627E39FC8002DD160 /* ReadexPro-Medium.ttf */, D5C474C627E39FC8002DD160 /* ReadexPro-Medium.ttf */,
D5C474C727E39FC8002DD160 /* ReadexPro-Regular.ttf */, D5C474C727E39FC8002DD160 /* ReadexPro-Regular.ttf */,
D5C474C527E39FC8002DD160 /* ReadexPro-Light.ttf */, D5C474C527E39FC8002DD160 /* ReadexPro-Light.ttf */,
D5A3D4ED292575F000ECCEC9 /* ReadexPro-SemiBold.ttf */,
D5C474C227E39FAD002DD160 /* ReadexPro-Bold.ttf */, D5C474C227E39FAD002DD160 /* ReadexPro-Bold.ttf */,
); );
path = ReadexPro; path = ReadexPro;
@ -490,11 +448,9 @@
children = ( children = (
D505B99B2755323C00386E9F /* NetworkMonitor.swift */, D505B99B2755323C00386E9F /* NetworkMonitor.swift */,
D544A24A281F92840038D483 /* ImageCaching.swift */, D544A24A281F92840038D483 /* ImageCaching.swift */,
D5267410292E902700D4C79E /* Networking.swift */,
D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */, D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */,
D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */, D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */,
D595FC2D2812C23D00569D8C /* Image Extension.swift */, D595FC2D2812C23D00569D8C /* Image Extension.swift */,
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */,
); );
path = Networking; path = Networking;
sourceTree = "<group>"; sourceTree = "<group>";
@ -503,7 +459,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */, D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
D5361099296FB2BB00228E15 /* DataStore.swift */,
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */, D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
); );
path = Model; path = Model;
@ -524,7 +479,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D5640215271B54BF00AE1519 /* MainSelectionView.swift */, D5640215271B54BF00AE1519 /* MainSelectionView.swift */,
D52A926C29071DF400973B6B /* SelectionView.swift */,
D5482F4828E63DB7000B0C8E /* MainSelectionViewModel.swift */, D5482F4828E63DB7000B0C8E /* MainSelectionViewModel.swift */,
); );
path = "Unpaired View"; path = "Unpaired View";
@ -663,7 +617,6 @@
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */, D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */,
D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */, D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */,
D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */, D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */,
D5267411292E902700D4C79E /* Networking.swift in Sources */,
D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */, D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */,
D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */, D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */,
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */, D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */,
@ -678,7 +631,6 @@
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */, D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */,
D5F53CED2694B7A9007634C2 /* OnboardingBackgroundView.swift in Sources */, D5F53CED2694B7A9007634C2 /* OnboardingBackgroundView.swift in Sources */,
D5D1F4B227ECFF760040E2BF /* ProjectsModel.swift in Sources */, D5D1F4B227ECFF760040E2BF /* ProjectsModel.swift in Sources */,
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */,
D5BA1F7A28B52A490012FC62 /* WifiListDetailView.swift in Sources */, D5BA1F7A28B52A490012FC62 /* WifiListDetailView.swift in Sources */,
D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */, D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */,
D567E2B628B81B730009F768 /* Queue.swift in Sources */, D567E2B628B81B730009F768 /* Queue.swift in Sources */,
@ -692,17 +644,13 @@
D59DFDB6268CD052001737F6 /* AppEnvironment.swift in Sources */, D59DFDB6268CD052001737F6 /* AppEnvironment.swift in Sources */,
D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */, D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */,
D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */, D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */,
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */,
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */, D505B99E2756894300386E9F /* ViewModifier.swift in Sources */,
D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */, D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */,
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */,
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */,
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */, D544A24B281F92840038D483 /* ImageCaching.swift in Sources */,
D517F68126C5771D002996E8 /* FillerView.swift in Sources */, D517F68126C5771D002996E8 /* FillerView.swift in Sources */,
D5DD39A728D11817000FAEB8 /* WifiFileTransfer.swift in Sources */, D5DD39A728D11817000FAEB8 /* WifiFileTransfer.swift in Sources */,
D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */, D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */,
D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */, D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */,
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */,
D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */, D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */,
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */, D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */,
D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */, D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */,
@ -711,16 +659,13 @@
D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */, D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */,
D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */, D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */,
D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */, D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */,
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */,
D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */, D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */,
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */,
D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */, D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */,
D59DFDBC268CE0EB001737F6 /* RootView.swift in Sources */, D59DFDBC268CE0EB001737F6 /* RootView.swift in Sources */,
D595FC2E2812C23D00569D8C /* Image Extension.swift in Sources */, D595FC2E2812C23D00569D8C /* Image Extension.swift in Sources */,
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */, D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */,
D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */, D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */,
D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */, D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */,
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */,
D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */, D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */,
D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */, D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */,
D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */, D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */,
@ -728,7 +673,6 @@
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */, D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */,
D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */, D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */,
D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */, D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */,
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */,
D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */, D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */,
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */, D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */,
D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */, D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */,
@ -740,7 +684,6 @@
D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */, D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */,
D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */, D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */,
D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */, D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */,
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */,
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */, D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */,
D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */, D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */,
D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */, D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */,
@ -749,7 +692,6 @@
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */, D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */,
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */, D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */,
D5640216271B54BF00AE1519 /* MainSelectionView.swift in Sources */, D5640216271B54BF00AE1519 /* MainSelectionView.swift in Sources */,
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */,
D505B99C2755323C00386E9F /* NetworkMonitor.swift in Sources */, D505B99C2755323C00386E9F /* NetworkMonitor.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -881,17 +823,17 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements; CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 0; CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457; DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist; INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.5; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.1.1; MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap; PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@ -911,17 +853,17 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements; CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 0; CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457; DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist; INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.5; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.1.1; MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap; PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;

View file

@ -9,6 +9,8 @@ import UIKit
class AppDelegate: NSObject, UIApplicationDelegate { class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// UI // UI
@ -18,7 +20,21 @@ class AppDelegate: NSObject, UIApplicationDelegate {
} }
private func setupAppearances() { private func setupAppearances() {
// Alerts print("Set Appearance")
// Navigation bar title
// UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
// UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white]
// Navigation bar background
// UINavigationBar.appearance().barTintColor = .clear
// UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
// List background
// UITableView.appearance().backgroundColor = UIColor.clear
// UITableView.appearance().separatorStyle = .none
// UITableViewCell.appearance().backgroundColor = .clear
//
// Alerts
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .blue UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .blue
} }
} }

Binary file not shown.

View file

@ -5,9 +5,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "150", "blue" : "0.769",
"green" : "100", "green" : "0.561",
"red" : "74" "red" : "0.380"
} }
}, },
"idiom" : "universal" "idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "150", "blue" : "0.773",
"green" : "100", "green" : "0.561",
"red" : "74" "red" : "0.380"
} }
}, },
"idiom" : "universal" "idiom" : "universal"

View file

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "97",
"green" : "97",
"red" : "97"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "97",
"green" : "97",
"red" : "97"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "bluetoothLogo.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "196",
"green" : "143",
"red" : "97"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -11,17 +11,17 @@ import Zip
class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate { class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate {
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let cachesPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
static let shared = DownloadViewModel() static let shared = DownloadViewModel()
@StateObject var globalString = GlobalString()
var bundleURL = String()
var bundleTitle = String()
// Alert // Alert
@Published var alertMsg = "" @Published var alertMsg = ""
@Published var showAlert = false @Published var showAlert = false
var manager = FileManager.default // Saving Download task reference for cancelling...
@Published var downloadTaskSession: URLSessionDownloadTask!
// Show Progress View // Show Progress View
@Published var downloadProgress: CGFloat = 0 @Published var downloadProgress: CGFloat = 0
@ -31,232 +31,12 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
@Published var isDownloading = false @Published var isDownloading = false
// Saving Download task refernce for cancelling... // Saving Download task refernce for cancelling...
// @Published var downloadtaskSession : URLSessionDownloadTask! @Published var downloadtaskSession : URLSessionDownloadTask!
@Published var attemptToSendBunle = false @Published var attemptToSendBunle = false
@Published var state: DownloadState = .idle @Published var state: DownloadState = .idle
private lazy var session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForResource = 5
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
func trueDownload(useProject link: String, projectName: String) {
let CPZipName = directoryPath.appendingPathComponent("\(projectName).zip")
let request = URLRequest(url: URL(string: link)!)
session.downloadTask(with: request).resume()
bundleURL = link
bundleTitle = projectName
}
// Saving Download task reference for cancelling...
@Published var downloadTaskSession: URLSessionDownloadTask!
func newZip(projectTitle: String, location: URL) {
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
print("\(#function) @Line: \(#line)")
print("Location 1: \(location)")
do {
let zipData = try Data(contentsOf: location)
try zipData.write(to: CPZipName)
// let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
try FileManager.default.removeItem(at: CPZipName)
} catch {
print("newZip - Zip ERROR")
print("Error: \(error)")
print("Location 2: \(location)")
self.state = .failed
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .idle
}
}
}
func unzipProjectFile(urlString: String, projectTitle: String) {
print("Times unzipProjectFile was called")
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
if let zipFileUrl = URL(string: urlString) {
// Download from this site
URLSession.shared.downloadTask(with: zipFileUrl) { (tempFileUrl, response, error) in
if let zipTempFileUrl = tempFileUrl {
do {
print("Times do looped in unzipProjectFile")
let zipData = try Data(contentsOf: zipTempFileUrl)
try zipData.write(to: CPZipName)
let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
try FileManager.default.removeItem(at: CPZipName)
var projectResponse = [String: String]()
projectResponse["projectTitle"] = self.bundleTitle
projectResponse["projectLink"] = self.bundleURL
NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
print("times wifiDownloadComplete was triggered")
NotificationCenter.default.post(name: .wifiDownloadComplete, object: nil, userInfo: projectResponse)
} catch {
print("unzipProjectFile - Zip ERROR")
print("Error: \(error)")
NotificationCenter.default.post(name: .downloadErrorDidOccur, object: nil, userInfo: nil)
return
DispatchQueue.main.async {
self.state = .failed
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .idle
}
}
} else {
self.state = .failed
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .idle
}
}
}.resume()
}
}
// func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// print("Download succeeded")
// }
/// Periodically informs the delegate about the downloads progress - Used for progress UI
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// Getting Progress
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
print("Progress: \(numeralProgress)")
// Since URL Session will be running in the background thread
// UI will be done on the main thread
DispatchQueue.main.async {
self.downloadProgress = numeralProgress
print("Recorded downloadProgress Progress: \(self.downloadProgress)")
print("Recorded numeralProgress Progress: \(numeralProgress)")
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
print(error)
guard let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data else {
print("Download failed")
return
}
session.downloadTask(withResumeData: resumeData).resume()
}
/// Tells the delegate that a download task has finished downloading.
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("urlSession completed")
guard let httpResponse = downloadTask.response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print ("server error")
NotificationCenter.default.post(name: .downloadErrorDidOccur, object: nil, userInfo: nil)
return
}
print("Location: \(location)")
guard let url = downloadTask.originalRequest?.url else {
self.reportError(error: "An error has occurred...")
return
}
// Creating a destination for storing files with a destination URL
let destinationURL = directoryPath.appendingPathComponent(url.lastPathComponent)
//if that file already exists, replace it.
try? FileManager.default.removeItem(at: destinationURL)
print(#function)
do {
// Copy temp file to directory.
try FileManager.default.copyItem(at: location, to: destinationURL)
DispatchQueue.main.async {
print("unzipProjectFile loop")
self.unzipProjectFile(urlString: self.bundleURL, projectTitle: self.bundleTitle)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.attemptToSendBunle.toggle()
}
NotificationCenter.default.post(name: .didCompleteTransfer, object: nil, userInfo: nil)
}
} catch {
print(error)
self.reportError(error: "Try again later")
isDownloading = false
self.didDownloadBundle = false
}
}
// Report Error Function...
func reportError(error: String){
alertMsg = error
showAlert.toggle()
}
// MARK:- Download // MARK:- Download
func startDownload(urlString: String, projectTitle: String) { func startDownload(urlString: String, projectTitle: String) {
print("Starting Download...") print("Starting Download...")
@ -274,19 +54,86 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
downloadTaskSession = session.downloadTask(with: validURL) downloadTaskSession = session.downloadTask(with: validURL)
downloadTaskSession.resume() downloadTaskSession.resume()
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
} }
func startDownload(urlString: String, projectTitle: String, compeletion: () -> ()) {
print("Starting Download...")
isDownloading = true
// Check for valid URL
guard let validURL = URL(string: urlString) else {
self.reportError(error: "Invalid URL!")
return
}
downloadProgress = 0
func testCallback(completion: ()->()) { // Download Task...
print("Do something") let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
downloadTaskSession = session.downloadTask(with: validURL)
downloadTaskSession.resume()
unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
} }
func makeFileDirectory() {
// Creating a File Manager Object
// Creating a folder
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder")
do {
} try FileManager.default.createDirectory(at: pyleapProjectFolderURL,
extension DownloadViewModel { withIntermediateDirectories: true,
attributes: [:])
} catch {
print(error)
}
}
func unzipProjectFile(urlString: String, projectTitle: String) {
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
if let zipFileUrl = URL(string: urlString) {
// Download from this site
URLSession.shared.downloadTask(with: zipFileUrl) { (tempFileUrl, response, error) in
/*
if let...
if you can let the new variable name equal the non-optional version of optionalName, do the following with it"
*/
if let zipTempFileUrl = tempFileUrl {
do {
let zipData = try Data(contentsOf: zipTempFileUrl)
try zipData.write(to: CPZipName)
let unzipDirectory = try Zip.quickUnzipFile(CPZipName) // Unzip
try FileManager.default.removeItem(at: CPZipName)
} catch {
print("Zip ERROR")
print("Error: \(error)")
self.state = .failed
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .idle
}
}
}
}.resume()
}
}
func createNewTextFile() { func createNewTextFile() {
@ -335,20 +182,89 @@ extension DownloadViewModel {
} }
} }
func makeFileDirectory() { /// Periodically informs the delegate about the downloads progress - Used for progress UI
// Creating a File Manager Object func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// Getting Progress
let numeralProgress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
print("Progress: \(numeralProgress)")
// Creating a folder // Since URL Session will be running in the background thread
let pyleapProjectFolderURL = directoryPath.appendingPathComponent("PyLeap Project Folder") // UI will be done on the main thread
DispatchQueue.main.async {
do { self.downloadProgress = numeralProgress
print("Recorded downloadProgress Progress: \(self.downloadProgress)")
try FileManager.default.createDirectory(at: pyleapProjectFolderURL, print("Recorded numeralProgress Progress: \(numeralProgress)")
withIntermediateDirectories: true,
attributes: [:])
} catch {
print(error)
} }
} }
/// Tells the delegate that a download task has finished downloading.
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let url = downloadTask.originalRequest?.url else {
self.reportError(error: "An error has occurred...")
return
}
// Creating a destination for storing files with a destination URL
let destinationURL = directoryPath.appendingPathComponent(url.lastPathComponent)
//if that file already exists, replace it.
try? FileManager.default.removeItem(at: destinationURL)
print(#function)
do {
// Copy temp file to directory.
try FileManager.default.copyItem(at: location, to: destinationURL)
DispatchQueue.main.async {
// If Successful...
print("Successful Download")
self.isDownloading = false
print("Download Location: \(location)")
self.downloadProgress = 1.0
print("\(self.didDownloadBundle) CURRENT STATE")
self.didDownloadBundle = true
print("\(self.didDownloadBundle) CURRENT STATE")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.attemptToSendBunle.toggle()
}
}
} catch {
print(error)
self.reportError(error: "Try again later")
isDownloading = false
self.didDownloadBundle = false
}
}
// Report Error Function...
func reportError(error: String){
alertMsg = error
showAlert.toggle()
}
// cancel Task...
func cancelTask(){
if let task = downloadtaskSession,task.state == .running{
// cancelling...
downloadtaskSession.cancel()
// closing view...
// withAnimation{self.showDownlodProgress = false}
}
}
} }

View file

@ -19,7 +19,7 @@ struct ExampleView: View {
// //
// PageView(title: "Choose your Adventure!", subtitle: "Choose a project you would like to send over to your PyLeap compatible device.", imageName: "slide3", showDismissButton: false, shouldShowOnboarding: $shouldShowOnboarding) // PageView(title: "Choose your Adventure!", subtitle: "Choose a project you would like to send over to your PyLeap compatible device.", imageName: "slide3", showDismissButton: false, shouldShowOnboarding: $shouldShowOnboarding)
// //
PageView(title: "Send projects directly from the Adafruit Learning System to your Adafruit Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding) PageView(title: "Send projects directly from the Adafruit Learning System to your Bluefruit Compatible Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
} }
.tabViewStyle(PageTabViewStyle()) .tabViewStyle(PageTabViewStyle())
} }

View file

@ -20,6 +20,11 @@
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSBonjourServices</key>
<array>
<string>_circuitpython._tcp</string>
<string>_bonjour._tcp</string>
</array>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string></string> <string></string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
@ -28,11 +33,6 @@
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string> <string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
<key>NSBluetoothPeripheralUsageDescription</key> <key>NSBluetoothPeripheralUsageDescription</key>
<string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string> <string>PyLeap requires access to Bluetooth to connect to Adafruit devices</string>
<key>NSBonjourServices</key>
<array>
<string>_circuitpython._tcp</string>
<string>_bonjour._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key> <key>NSLocalNetworkUsageDescription</key>
<string>PyLeap uses the local network to communicate with your Adafruit device</string> <string>PyLeap uses the local network to communicate with your Adafruit device</string>
<key>UIAppFonts</key> <key>UIAppFonts</key>
@ -64,6 +64,10 @@
<key>UIStatusBarHidden</key> <key>UIStatusBarHidden</key>
<false/> <false/>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>

View file

@ -1,159 +0,0 @@
//
// DataStore.swift
// PyLeap
//
// Created by Trevor Beaton on 1/11/23.
//
import Foundation
/**
/// This is a DataStore class that is used for saving and loading data to/from the file system using the FileManager class.
*/
public class DataStore {
let fileManager = FileManager.default
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
init() {}
/**
- parameter content: This method takes an array of ResultItem objects.
- returns: completion
/// This method writes it to a file named "StandardPyLeapProjects.json" in the documents directory.
*/
func save(content: [ResultItem], completion: @escaping () -> Void) {
let encoder = JSONEncoder()
if let encodedProjectData = try? encoder.encode(content) {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsURL.appendingPathComponent("StandardPyLeapProjects.json")
try? encodedProjectData.write(to: fileURL)
completion()
}
}
/**
- parameter content: This method takes an array of ResultItem objects.
- returns: completion
/// This method reads the "StandardPyLeapProjects.json" file in the documents directory and decodes it as an array of ResultItem objects, and then appends the customProjects array to it and saves it back to the file.
*/
func save(customProjects: [ResultItem], completion: @escaping () -> Void) {
var temp = customProjects
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
let savedData = try? Data(contentsOf: fileURL)
if let savedData = savedData,
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
temp.append(contentsOf: savedProjects)
save(content: temp) {
self.removeDuplicates(projectList: temp)
}
completion()
}
}
/**
/// This method reads the "StandardPyLeapProjects.json" file in the documents directory and decodes it as an array of ResultItem objects, and then calls the loadCustomProjectList(contents:) method with the decoded array as an argument
*/
func loadDefaultProjectList() {
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
let savedData = try? Data(contentsOf: fileURL)
if let savedData = savedData,
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
loadCustomProjectList(contents: savedProjects)
}
}
/**
/// : This method reads the "StandardPyLeapProjects.json" file in the documents directory, decodes it as an array of ResultItem objects, and returns it.
*/
func loadDefaultList() -> [ResultItem] {
var result = [ResultItem]()
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
let savedData = try? Data(contentsOf: fileURL)
if let savedData = savedData,
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
result = savedProjects
}
return result
}
/**
- parameter: This method takes an array of ResultItem objects
- returns: completion
/// This method reads the "CustomProjects.json" file in the documents directory, decodes it as an array of ResultItem objects, appends it to the input array, and then calls
*/
func loadCustomProjectList(contents: [ResultItem]) {
var temp = contents
let fileURL = documentsDirectory.appendingPathComponent("CustomProjects.json")
let savedData = try? Data(contentsOf: fileURL)
if let savedData = savedData,
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
temp.append(contentsOf: savedProjects)
removeDuplicates(projectList: temp)
}
}
/**
- parameter: This method takes an array of ResultItem objects
- returns: completion
/// This method uses the reduce(into:_:) method to iterate over the array, and it builds a new array that only contains unique ResultItem objects based on their bundleLink property. It then calls the save(content:completion:) method to save the new array to "StandardPyLeapProjects.json" file.
*/
func removeDuplicates(projectList: [ResultItem]) {
let combinedLists = projectList.reduce(into: [ResultItem]()) { (result, projectList) in
if !result.contains(where: { $0.bundleLink == projectList.bundleLink }) {
result.append(projectList)
}
}
save(content: combinedLists) {}
}
func loadThirdPartyProjectsFromFileManager() {
let fileURL = documentsDirectory.appendingPathComponent("CustomProjects.json")
let savedData = try? Data(contentsOf: fileURL)
if let savedData = savedData,
let savedProjects = try? JSONDecoder().decode([ResultItem].self, from: savedData) {
for project in savedProjects {
print("CustomProjects name: \(project.projectName)")
}
}
}
}

View file

@ -30,5 +30,3 @@ struct ResultItem: Codable, Identifiable, Equatable {
let learnGuideLink: String let learnGuideLink: String
let compatibility: [String] let compatibility: [String]
} }

View file

@ -1,15 +0,0 @@
//
// JSONDecoderHelper.swift
// PyLeap
//
// Created by Trevor Beaton on 1/11/23.
//
import Foundation
class JSONDecoderHelper {
static func decode<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
return try? decoder.decode(T.self, from: data)
}
}

View file

@ -14,9 +14,221 @@ import Foundation
import SwiftUI import SwiftUI
class NetworkService: ObservableObject { class NetworkService: ObservableObject {
let dataStore = DataStore()
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent) static let shared = NetworkService()
@Published var pdemos : [ResultItem] = []
@State var storedURL = ""
let userDefaults = UserDefaults.standard
init(){
load()
loadCustProjects()
}
func loadCustProjects() {
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("++++Custom Projects++++")
for i in loadedProjects {
print("\(i.projectName)")
}
}
}
}
func save(content: [ResultItem]) {
print("Saving JSON response...")
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(content) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "SavedProjects")
}
}
func save(customProjects: [ResultItem]) {
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(customProjects) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "CustomProjects")
}
}
func saveCustomProjects(content: [ResultItem]) {
NotificationCenter.default.post(name: .didCollectCustomProject, object: nil, userInfo: nil)
// if let newIncoming = content.contains()
var customList: [ResultItem] = []
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Loading previous list")
for i in loadedProjects {
print("\(i.projectName)")
}
customList = loadedProjects
print("Appending new content")
customList.append(contentsOf: content)
for i in customList {
print("\(i.projectName)")
}
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(customList) {
let defaults = UserDefaults.standard
print("Saved new custom list.")
defaults.set(encoded, forKey: "CustomProjects")
}
}
}
}
func verifyIncomingProject(json response: [ResultItem]){
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
/// Does incoming project exist already?
/// Check with object's property URL
if loadedProjects.contains(where: { $0.bundleLink == response[0].bundleLink
}) {
print("does exist")
} else {
print("does not exist")
}
print(loadedProjects)
}
}
}
func loadCustomProjects() -> [ResultItem]{
print(#function)
var customList: [ResultItem] = []
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load custom projectsxo")
// print(loadedProjects)
for i in loadedProjects {
print("\(i.projectName)")
}
customList = loadedProjects
}
}
return customList
}
func mergeProjects() {
print(#function)
var standardList: [ResultItem] = []
var customList: [ResultItem] = []
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
// let check = loadedProjects.map { $0.bundleLink == "" }
pdemos = loadedProjects
print(loadedProjects)
}
}
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
print(loadedProjects)
}
}
}
func load() {
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
let mergedList = loadedProjects + loadCustomProjects()
// save(content: <#T##[ResultItem]#>)
pdemos = mergedList
print("----Standard Projects----")
for i in loadedProjects {
print("\(i.projectName)")
}
print("++++Custom Projects++++")
for i in loadCustomProjects() {
print("\(i.projectName)")
}
}
}
}
private var dataTask: URLSessionDataTask? private var dataTask: URLSessionDataTask?
@ -26,16 +238,24 @@ class NetworkService: ObservableObject {
// Session Configuration & Caching Policy // Session Configuration & Caching Policy
let configuration = URLSessionConfiguration.default let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad // configuration.requestCachePolicy = .useProtocolCachePolicy
return URLSession(configuration: configuration) return URLSession(configuration: configuration)
}() }()
@Published var projectInfo = Data()
func fetch(completion: @escaping() -> Void) { func fetch() {
print("Attempting Network Request")
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 60.0) let cache = URLCache.shared
let task = session.dataTask(with: request) { data, response, error in
let requestForCache = URLRequest(url: URL(string: AdafruitInfo.baseURL)!)
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
print("Making Network Request.")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error { if let error = error {
print("error: \(error)") print("error: \(error)")
@ -47,82 +267,78 @@ class NetworkService: ObservableObject {
if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) { if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.save(content: projectData.projects)
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList) self.load()
completion()
} }
} else { } else {
print("No data found") print("No data found")
} }
} else { } else {
print("Updating UIList with Cached data...") print("Updating UIList with Cached data...")
DispatchQueue.main.async { DispatchQueue.main.async {
self.dataStore.loadDefaultProjectList() self.load()
completion()
} }
} }
} }
task.resume() task.resume()
} }
func fetchThirdParyProject(urlString: String?) {
let cache = URLCache.shared
func fetchThirdPartyProject(urlString: String?) { guard let urlString = urlString else {
print("Error")
thirdPartyBackgroundQueue.async { return
guard let urlString = urlString else {
print("\(#function) @Line: \(#line)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
print("Error urlString")
return
}
if urlString.contains(" ") {
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
guard let requestURL = URL(string: urlString) else {
print("\(#function) @Line: \(#line)")
print("Error requestURL")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
let request = URLRequest(url: requestURL, cachePolicy: URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 0.0)
print("Making Network Request for Custom Project.")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
if let data = data {
let projectData = JSONDecoderHelper.decode(data: data) as RootResults?
if let projects = projectData?.projects {
DispatchQueue.main.async {
self.dataStore.save(customProjects: projects, completion: self.dataStore.loadThirdPartyProjectsFromFileManager)
}
}
}
}
task.resume()
} }
if urlString.contains(" ") {
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
let request = URLRequest(url: URL(string: urlString)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
print("Making Network Request for Custom Project.")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
if let data = data {
print("Updating UIList with new data...")
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
if let projects = projectData?.projects {
DispatchQueue.main.async {
print(#function)
for i in projects {
print("\(i.projectName)")
}
// self.pdemos.append(contentsOf: projects)
self.saveCustomProjects(content: projects)
}
}
}
}
task.resume()
} }
} }

View file

@ -1,41 +0,0 @@
//
// Networking.swift
// PyLeap
//
// Created by Trevor Beaton on 11/23/22.
//
import Foundation
enum HTTPMethod: String {
case delete = "DELETE"
case get = "GET"
case patch = "PATCH"
case post = "POST"
case put = "PUT"
case options = "OPTIONS"
}
enum HTTPScheme: String {
case http
case https
}
/// The API protocol allows us to separate the task of constructing a URL,
/// its parameters, and HTTP method from the act of executing the URL request
/// and parsing the response.
///
protocol API {
var scheme: HTTPScheme { get }
var baseURL: String { get }
var path: String { get }
// [URLQueryItem(name: "api_key", value: API_KEY)]
var parameters: [URLQueryItem] { get }
var method: HTTPMethod { get }
}

BIN
PyLeap/Views/.DS_Store vendored

Binary file not shown.

View file

@ -60,7 +60,7 @@ struct BLEPairingView: View {
//.padding(.top, 100) //.padding(.top, 100)
.padding(.horizontal, 20) .padding(.horizontal, 20)
BlinkaAnimationView(height: 250, width: 250) BlinkaAnimationView()
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0)) .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.onAppear(){ .onAppear(){

View file

@ -29,34 +29,41 @@ struct BTConnectionView: View {
VStack{ VStack{
HStack { HStack {
Button {
rootViewModel.goToSelection()
Button {
self.rootViewModel.goToMain()
} label: { } label: {
Image(systemName: "arrow.backward") Image(systemName: "arrow.backward")
.resizable() .resizable()
.frame(width: 25, height: 25, alignment: .center) .aspectRatio(contentMode: .fit)
.offset(y: 15) .frame(width: 30, height: 30, alignment: .center)
.foregroundColor(.black) .foregroundColor(Color("pyleap_gray"))
} }
.padding()
Spacer() Spacer()
Image("bluetooth")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center)
.offset(y: -5)
.onReceive(timer) { _ in
nextText = 1
timer.upstream.connect().cancel()
}
} }
.padding(.top, 15) .padding(.top, 50)
.padding(.horizontal, 30)
Image("pyleapLogo") Image("pyleapLogo")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.minimumScaleFactor(0.1)
.padding(.top, 50) .padding(.top, 50)
.padding(.horizontal, 60) .padding(.horizontal, 60)
@ -69,29 +76,19 @@ struct BTConnectionView: View {
Text("Bluetooth Connect") Text("Searching for PyLeap compatible device...")
.font(Font.custom("ReadexPro-Regular", size: 36)) .padding(.horizontal, 30)
.multilineTextAlignment(.center) .font(Font.custom("ReadexPro-Regular", size: 24))
.minimumScaleFactor(0.01) .minimumScaleFactor(0.1)
.lineLimit(1)
.padding()
.padding(.horizontal, 30) .padding(.horizontal, 30)
Spacer() Spacer()
BlinkaAnimationView()
BlinkaAnimationView(height: 150, width: 145) .minimumScaleFactor(0.1)
.padding(.bottom, 20)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0)) .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.onAppear() {
Animation.linear(duration: 1.0)
.repeatForever(autoreverses: false)
isAnimating = true
}
Spacer()
.onAppear() { .onAppear() {
Animation.linear(duration: 1.0) Animation.linear(duration: 1.0)
@ -101,9 +98,6 @@ struct BTConnectionView: View {
Text(detailText) Text(detailText)
.font(Font.custom("ReadexPro-Regular", size: 24)) .font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.minimumScaleFactor(0.1)
.lineLimit(2)
Button(action: { Button(action: {
nextText = 1 nextText = 1
@ -228,7 +222,10 @@ struct BTConnectionView: View {
Text("Pair Device") Text("Pair Device")
.font(Font.custom("ReadexPro-Regular", size: 25)) .font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 60) .padding(.horizontal, 60)
.frame(height: 50) .frame(height: 50)
.background(Color("pyleap_pink")) .background(Color("pyleap_pink"))
.clipShape(Capsule()) .clipShape(Capsule())
@ -278,7 +275,7 @@ struct BTConnectionView: View {
let text: String let text: String
switch model.connectionStatus { switch model.connectionStatus {
case .scanning: case .scanning:
text = "Scanning for PyLeap compatible devices..." text = "Scanning..."
case .restoringConnection: case .restoringConnection:
text = "Restoring connection..." text = "Restoring connection..."
case .connecting: case .connecting:
@ -295,8 +292,11 @@ struct BTConnectionView: View {
case .disconnected(let error): case .disconnected(let error):
if let error = error { if let error = error {
text = "Disconnected: \(error.localizedDescription)" text = "Disconnected: \(error.localizedDescription)"
//self.showSheetView.toggle()
} else { } else {
text = "Disconnected" text = "Disconnected"
} }
} }
return text return text

View file

@ -86,7 +86,7 @@ struct TroubleshootView: View {
Button { Button {
UIApplication.shared.open(URL(string: "App-Prefs:root=Bluetooth")!) UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
} label: { } label: {
ZStack { ZStack {

View file

@ -18,15 +18,17 @@ struct FillerView: View {
Image("pyleapLogo") Image("pyleapLogo")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.offset(y: -30) .offset(y: -20)
ProgressView() ProgressView()
} }
.preferredColorScheme(.light) .preferredColorScheme(.light)
.padding(.horizontal, 30) .padding(.horizontal, 20)
.edgesIgnoringSafeArea(.all)
.modifier(Alerts(activeAlert: $model.activeAlert, model: model)) .modifier(Alerts(activeAlert: $model.activeAlert, model: model))
.onAppear { .onAppear {
print("Filler View")
model.setupBluetooth() model.setupBluetooth()
} }
.onChange(of: model.isStartupFinished) { isStartupFinished in .onChange(of: model.isStartupFinished) { isStartupFinished in

View file

@ -9,13 +9,12 @@ import SwiftUI
import FileTransferClient import FileTransferClient
class BleContentCommands: ObservableObject { class BleContentCommands {
private weak var fileTransferClient: FileTransferClient? private weak var fileTransferClient: FileTransferClient?
@Published var transmissionProgress: TransmissionProgress? @Published var transmissionProgress: TransmissionProgress?
@Published var isTransmiting = false @Published var isTransmiting = false
@Published var bootUpInfo = String() @Published var bootUpInfo = String()
@Published var counter = 0
enum ProjectViewError: LocalizedError { enum ProjectViewError: LocalizedError {
case fileTransferUndefined case fileTransferUndefined
@ -104,8 +103,6 @@ class BleContentCommands: ObservableObject {
let str = String(decoding: data, as: UTF8.self) let str = String(decoding: data, as: UTF8.self)
print("Read: \(str)") print("Read: \(str)")
self.bootUpInfo = str self.bootUpInfo = str
sharedBootinfo = str
case .failure(let error): case .failure(let error):
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription)) self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
@ -237,10 +234,8 @@ class BleContentCommands: ObservableObject {
func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) { func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return } guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
DispatchQueue.main.async { DispatchQueue.main.async {
self.counter += 1
} }
@ -257,11 +252,9 @@ class BleContentCommands: ObservableObject {
switch result { switch result {
case .success: case .success:
DLog("writeFile \(path) success. Size: \(data.count)") DLog("writeFile \(path) success. Size: \(data.count)")
print("\(#function) @Line: \(#line)")
case .failure(let error): case .failure(let error):
DLog("writeFile \(path) error: \(error)") DLog("writeFile \(path) error: \(error)")
print("Deep Error")
print("\(#function) @Line: \(#line)")
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,8 +8,8 @@
import SwiftUI import SwiftUI
import FileTransferClient import FileTransferClient
class ExpandedBLECellState: ObservableObject { class SpotlightCounter: ObservableObject {
@Published var currentCell = "" @Published var counter = 0
} }
struct BleModuleView: View { struct BleModuleView: View {
@ -25,43 +25,52 @@ struct BleModuleView: View {
} }
} }
@Environment(\.presentationMode) var presentationMode @Environment(\.presentationMode) var presentationMode
@EnvironmentObject var expandedState : ExpandedBLECellState
@ObservedObject var connectionManager = FileTransferConnectionManager.shared @ObservedObject var connectionManager = FileTransferConnectionManager.shared
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
@StateObject var viewModel = BleModuleViewModel()
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = BleModuleViewModel()
@ObservedObject var networkServiceModel = NetworkService()
@StateObject var globalString = GlobalString()
@StateObject var btConnectionViewModel = BTConnectionViewModel()
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var downloadModel = DownloadViewModel()
@StateObject var spotlight = SpotlightCounter()
//clearKnownPeripheralUUIDs
@State private var isConnected = false @State private var isConnected = false
//@State private var switchedView = false
@State private var errorOccured = false @State private var errorOccured = false
@State private var downloadState = DownloadState.idle
@State var notExpanded = false
@State var isExpanded = true
@State private var scrollViewID = UUID() @State private var scrollViewID = UUID()
@State var currentHightlight: Int = 0
@State private var activeAlert: ActiveAlert? @State private var activeAlert: ActiveAlert?
@State private var internetAlert = false
@State private var showAlert1 = false
@State private var boardBootInfo = "" @State private var boardBootInfo = ""
@State private var inConnectedInSelectionView = true @State private var inConnectedInSelectionView = true
@AppStorage("shouldShowOnboarding123") var switchedView: Bool = false @AppStorage("shouldShowOnboarding123") var switchedView: Bool = false
@State var subviewHeight : CGFloat = 0
func showConfirmationPrompt() {
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
connectionManager.isDisconnectingFromCurrent = true
} cancel: {
}
}
var body: some View { var body: some View {
var connectedPeripherals = connectionManager.peripherals.filter{$0.state == .connected }
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
VStack { VStack {
//Start //Start
@ -69,7 +78,6 @@ struct BleModuleView: View {
HStack { HStack {
Spacer() Spacer()
Image("bluetooth") Image("bluetooth")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
@ -170,139 +178,68 @@ struct BleModuleView: View {
HeaderView() HeaderView()
// Button {
// print("Disconnect")
//
// activeAlert = .confirmUnpair(blePeripheral: connectedPeripherals[0])
// connectedPeripherals = []
//
// connectionManager.isConnectedOrReconnecting = false
// FileTransferConnectionManager.shared
// connectionManager.selectedPeripheral = nil
// connectionManager.isAnyPeripheralConnecting = false
// connectionManager.isSelectedPeripheralReconnecting = false
// // connectionManager.clearAllPeripheralInfo()
// rootViewModel.goToMain()
//
// print("Destination: \(rootViewModel.destination)")
// } label: {
// Text("Disconnection")
// }
// Sub-Header
VStack { VStack {
if boardBootInfo == "circuitplayground_bluefruit" { if boardBootInfo == "circuitplayground_bluefruit" {
HStack { Text("Connected to Circuit Playground Bluefruit")
.font(Font.custom("ReadexPro-Regular", size: 16))
Image("bluetoothLogo")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
Text("Circuit Playground Bluefruit.")
.font(Font.custom("ReadexPro-Regular", size: 14))
.minimumScaleFactor(0.1)
Button {
showConfirmationPrompt()
} label: {
Text("Disconnect")
.font(Font.custom("ReadexPro-Bold", size: 14))
.underline()
.minimumScaleFactor(0.1)
}
}
} }
if boardBootInfo == "clue_nrf52840_express" { if boardBootInfo == "clue_nrf52840_express" {
VStack { Text("Connected to Adafruit CLUE")
.font(Font.custom("ReadexPro-Regular", size: 16))
HStack {
Image("bluetoothLogo")
.resizable()
.scaledToFit()
.frame(width: 16, height: 16)
Text("Adafruit CLUE.")
.font(Font.custom("ReadexPro-Regular", size: 14))
Button {
showConfirmationPrompt()
} label: {
Text("Disconnect")
.font(Font.custom("ReadexPro-Bold", size: 14))
.underline()
//.minimumScaleFactor(0.1)
}
}
// Expandable
VStack {
Text("More Info")
}
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
.onPreferenceChange(ViewHeightKey.self) { subviewHeight = $0 }
.frame(height: isExpanded ? subviewHeight : 50, alignment: .top)
.clipped()
.frame(maxWidth: .infinity)
.transition(.move(edge: .bottom))
.onTapGesture {
withAnimation(.easeIn(duration: 0.5)) {
isExpanded.toggle()
}
}
} }
} }
.padding(.all, 0.0) .padding(.all, 0.0)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(maxHeight: 40) .frame(maxHeight: 40)
.background(Color("adafruit_blue")) .background(Color("pyleap_green"))
.foregroundColor(.white) .foregroundColor(.white)
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scroll in ScrollViewReader { scroll in
if boardBootInfo == "clue_nrf52840_express" { MainSubHeaderView()
MainSubHeaderView(device: "Adafruit CLUE") // .spotlight(enabled: spotlight.counter == 1, title: "1")
let check = networkServiceModel.pdemos.filter {
$0.compatibility[0] == boardBootInfo
} }
if boardBootInfo == "circuitplayground_bluefruit" {
MainSubHeaderView(device: "Circuit Playground")
}
let check = viewModel.pdemos.filter {
$0.compatibility.contains(boardBootInfo)
}
ForEach(check) { demo in ForEach(check) { demo in
if demo.bundleLink == expandedState.currentCell {
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
.onAppear(){
print("Cell Appeared")
withAnimation {
scroll.scrollTo(demo.id)
}
DemoViewCell(result: demo, isConnected: $inConnectedInSelectionView, bootOne: $boardBootInfo, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
} }
}, stateBinder: $downloadState)
} else {
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
}
} }
@ -310,38 +247,143 @@ struct BleModuleView: View {
.id(self.scrollViewID) .id(self.scrollViewID)
} }
.environmentObject(expandedState)
}
}
.alert("Cannot Write To Device", isPresented: $errorOccured) {
Button("OK") {
// Handle acknowledgement.
print("OK")
errorOccured = false
}
} message: {
Text("""
Unplug device from computer and use external power source.
Then press RESET on device to continue.
""")
.multilineTextAlignment(.leading)
}
.onTapGesture {
print("\(networkServiceModel.pdemos.count)")
}
} }
} }
.background(Color.white) .background(Color.white)
.environmentObject(globalString)
.onChange(of: viewModel.isConnectedToInternet, perform: { newValue in
if newValue {
internetAlert = false
} else {
internetAlert = true
}
})
.modifier(Alerts(activeAlert: $activeAlert))
.onChange(of: viewModel.state, perform: { newValue in
print("State: \(newValue)")
downloadState = newValue
print("State Change: \(newValue )")
if newValue == .failed {
print("Failed Value")
errorOccured = true
}
})
.onChange(of: viewModel.writeError, perform: { newValue in
print("Change happened! \(newValue)")
globalString.bundleHasBeenDownloaded = newValue
})
.onChange(of: viewModel.sendingBundle, perform: { newValue in
globalString.isSendingG = newValue
if newValue {
print("Is transferring...")
} else {
print("Not transferring...")
}
})
.onChange(of: viewModel.numOfFiles, perform: { newValue in
globalString.numberOfFilesG = newValue
print("NumOfFiles: \(newValue)")
})
.onChange(of: viewModel.counter, perform: { newValue in
globalString.counterG = newValue
print("Change for counterG happened: Value should be \(newValue)")
})
.onChange(of: viewModel.bootUpInfo, perform: { newValue in .onChange(of: viewModel.bootUpInfo, perform: { newValue in
viewModel.readMyStatus() viewModel.readMyStatus()
print("newValue \(newValue)") print("newValue \(newValue)")
boardBootInfo = newValue boardBootInfo = newValue
}) })
.onChange(of: globalString.projectString, perform: { newValue in
print("Start Transfer")
// viewModel.getProjectURL(nameOf: newValue)
})
.onChange(of: globalString.attemptToDownload, perform: { newValue in
print("Start Download Process\(globalString.downloadLinkString) - \(globalString.projectString)")
downloadModel.startDownload(urlString: globalString.downloadLinkString, projectTitle: globalString.projectString)
})
.onChange(of: globalString.attemptToSend, perform: { newValue in
viewModel.getProjectURL(nameOf: globalString.projectString)
})
.onChange(of: downloadModel.attemptToSendBunle, perform: { newValue in
print("Attempting transfer of: \(globalString.projectString)")
viewModel.getProjectURL(nameOf: globalString.projectString)
})
.onChange(of: connectionManager.selectedClient) { selectedClient in .onChange(of: connectionManager.selectedClient) { selectedClient in
viewModel.setup(fileTransferClient: selectedClient) viewModel.setup(fileTransferClient: selectedClient)
} }
.onAppear(){ .onAppear(){
print("Opened BleModuleView")
// networkServiceModel.fetch()
viewModel.setup(fileTransferClient:connectionManager.selectedClient)
connectionManager.isSelectedPeripheralReconnecting = true
print("On Appear")
networkServiceModel.fetch()
viewModel.setup(fileTransferClient: connectionManager.selectedClient)
viewModel.readFile(filename: "boot_out.txt") viewModel.readFile(filename: "boot_out.txt")
} }
} }
enum ButtonStatus: CaseIterable, Identifiable {
case download
case transfer
case complete
var id: String { return title }
var title: String {
switch self {
case .download: return "Download"
case .transfer: return "Transfer"
case .complete: return "Complete"
}
}
}
struct Alerts: ViewModifier { struct Alerts: ViewModifier {
@Binding var activeAlert: ActiveAlert? @Binding var activeAlert: ActiveAlert?
@ -364,9 +406,4 @@ struct BleModuleView: View {
} }
} }
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}

View file

@ -11,28 +11,655 @@ import FileTransferClient
class BleModuleViewModel: ObservableObject { class BleModuleViewModel: ObservableObject {
private weak var fileTransferClient: FileTransferClient? @StateObject var globalString = GlobalString()
@StateObject var contentTransfer = BleContentTransfer()
private weak var fileTransferClient: FileTransferClient?
@Published var entries = [BlePeripheral.DirectoryEntry]() @Published var entries = [BlePeripheral.DirectoryEntry]()
@Published var isTransmiting = false @Published var isTransmiting = false
@Published var bootUpInfo = "" @Published var bootUpInfo = ""
let dataStore = DataStore() var projectDirectories: [URL] = []
@Published var sendingBundle = false
@Published var didCompleteTranfer = false
@Published var writeError = false
@Published var pdemos : [ResultItem] = []
init() { @Published var counter = 0
pdemos = dataStore.loadDefaultList() @Published var numOfFiles = 0
}
@Published var fileArray: [ContentFile] = []
@Published var contentList: [URLData] = []
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
var networkMonitor = NetworkMonitor()
static let shared = BleModuleViewModel()
@Published var isConnectedToInternet = false
@Published var showAlert = false
var downloadPhases: String = ""
@Published var state: DownloadState = .idle
enum ProjectViewError: LocalizedError { enum ProjectViewError: LocalizedError {
case fileTransferUndefined case fileTransferUndefined
} }
func displayErrorMessage() {
DispatchQueue.main.async {
self.writeError = true
self.sendingBundle = false
}
}
func internetMonitoring() {
networkMonitor.startMonitoring()
networkMonitor.monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("Connected to internet.")
DispatchQueue.main.async {
self.showAlert = false
self.isConnectedToInternet = true
}
} else {
print("No connection.")
DispatchQueue.main.async {
self.showAlert = true
self.isConnectedToInternet = false
}
}
print("isExpensive: \(path.isExpensive)")
}
}
init() {
internetMonitoring()
}
/// Deletes all files and dic. on Bluefruit device *Except boot_out.txt*
func removeAllFiles(){
self.listDirectoryCommand(path: "") { result in
switch result {
case .success(let contents):
for i in contents! where i.name != "boot_out.txt" {
self.deleteFileCommand(path: i.name) { deletionResult in
switch deletionResult {
case .success:
print("Successfully Deleted")
case .failure:
print("Failed to delete.")
}
}
}
case .failure:
print("No content listed")
}
}
}
/*
- Find URL by name - send it to filesDownloaded
- Enumerate thru found URL
- Get collection of files and directories - then send URL to startFileTransfer
*/
func getProjectURL(nameOf project: String) {
print("getProjectURL called")
counter = 0
state = .transferring
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
// for case condition: Only process URLs
for case let fileURL as URL in enumerator {
do {
print("Starting a loop...")
if fileURL.lastPathComponent == project {
print("Searching for... \(project)")
do {
print(#function)
print("Found \(project) project at this location...")
print("URL Path: \(fileURL.path)")
print("URL : \(fileURL)")
let newURL = URL(fileURLWithPath: fileURL.path, relativeTo: directoryPath)
print("URL: \(newURL)")
filesDownloaded(url: fileURL)
return
} catch { print(error, fileURL) }
} else {
print("Project was not found for...\(project)")
print("\(state)")
state = .idle
}
}
}
}
}
func filesDownloaded(url: URL) {
print("filesDownloaded was called")
//Cycles through files and directories in File Manager Document Directory
fileArray.removeAll()
var files = [URL]()
// Returns a directory enumerator object that can be used to perform a deep enumeration of the directory at the specified URL.
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
// for case condition: Only process URLs
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey, .addedToDirectoryDateKey,.isDirectoryKey])
print("INCOMING FILE: \(fileURL.path)")
if fileURL.path.contains("adafruit-circuitpython-bundle-7.x-mpy") {
print("Removing adafruit-circuitpython-bundle-7.x-mpy: \(fileURL.path)")
} else {
print("FILTERED INCOMING FILE: \(fileURL.path)")
contentList.append(.init(urlTitle: fileURL))
if fileAttributes.isRegularFile! {
files.append(fileURL)
let resources = try fileURL.resourceValues(forKeys:[.fileSizeKey])
let fileSize = resources.fileSize!
let addedFile = ContentFile(title: fileURL.lastPathComponent, fileSize: fileSize)
fileArray.append(addedFile)
}
let addedFile = ContentFile(title: fileURL.lastPathComponent, fileSize: 0 )
fileArray.append(addedFile)
}
} catch { print(error, fileURL) }
}
startFileTransfer(url: url)
numOfFiles = files.count
print("Contents in URL \(fileArray.count)")
print("Number of Files in URL \(files.count)")
for i in contentList {
print("CL: \(i.urlTitle.pathComponents)")
}
contentList.removeAll()
}
}
func startFileTransfer(url: URL) {
print("Project Location: \(url)")
let localFileManager = FileManager()
let resourceKeys = Set<URLResourceKey>([.nameKey, .isDirectoryKey])
var fileURLs: [URL] = []
let dirEnumerator = localFileManager.enumerator(at: url, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles)!
for case let fileURL as URL in dirEnumerator {
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
let isDirectory = resourceValues.isDirectory,
let name = resourceValues.name
else {
continue
}
if fileURL.path.contains("adafruit-circuitpython-bundle-7.x-mpy") {
print("Removing adafruit-circuitpython-bundle-7.x-mpy: \(fileURL.path)")
} else {
if isDirectory {
print("Directories Found")
print(fileURL.lastPathComponent)
if name == "_extras" {
dirEnumerator.skipDescendants()
}
//adafruit-circuitpython-bundle
if fileURL.lastPathComponent.contains("adafruit-circuitpython-bundle") {
print("We got one!")
print("Bad file - \(fileURL)")
} else {
if fileURL.pathComponents.count > 12 {
print("File Path component count: \(fileURL.pathComponents.count)")
projectDirectories.append(fileURL)
}
}
} else {
print("APPENDED: \(fileURL.path)")
fileURLs.append(fileURL)
}
}
}
print("List of Directories")
for i in projectDirectories {
print("Directory: \(i.path)")
}
print("List of Files")
for i in fileURLs {
print("Files: \(i.path)")
}
DispatchQueue.main.async {
self.sendingBundle = true
}
print("Current projectDirectories: \(projectDirectories[0])")
sortDirectory(dirList: projectDirectories, filesUrls: fileURLs)
}
func sortDirectory(dirList: [URL], filesUrls: [URL]) {
print(#function)
//Creates a sorted list of directories
var tempDirectory = dirList.sorted(by: { $1.pathComponents.count > $0.pathComponents.count} )
print("Evaluating: \(String(describing: tempDirectory.first?.lastPathComponent))")
print("With Path: \(String(describing: tempDirectory.first?.path))")
print("Sorted Directory")
for i in tempDirectory{
print(i.lastPathComponent)
}
// If directories are not found, start transferring files over to directories.
if dirList.isEmpty {
print("No directories left in queue")
projectDirectories.removeAll()
self.transferFiles(files: filesUrls)
} else {
guard let firstDirectory = tempDirectory.first else {
print("No directory exist here")
return
}
// If lib/ directory is found in the project bundle, make a lib directory on client.
if firstDirectory.lastPathComponent == "lib" {
mkLibDir(libDirectory: firstDirectory, copiedDirectory: tempDirectory, filesUrl: filesUrls)
} else {
mkSubLibDir(subdirectory: firstDirectory, copiedDirectory: tempDirectory, filesURL: filesUrls)
}
}
self.projectDirectories.removeAll()
}
//Make lib/ Directory
func mkLibDir(libDirectory: URL, copiedDirectory: [URL], filesUrl: [URL]) {
print(#function)
var temp = copiedDirectory
// print(temp)
print("mkLibDir list")
for i in temp {
print("\(i)")
}
listDirectoryCommand(path: "") { result in
switch result {
// Check that lib/ exist.
case .success(let contents):
print("ListDirCommand: \(String(describing: contents))")
if contents!.contains(where: { name in name.name == libDirectory.lastPathComponent}) {
print("lib directory exist")
temp.removeFirst()
self.sortDirectory(dirList: temp, filesUrls: filesUrl)
} else {
print("lib directory does not exist")
print("XXXX mkLibDir")
var tempURL = libDirectory.pathComponents
tempURL.removeFirst(12)
let joined = tempURL.joined(separator: "/")
print("FIXED PATHxx:\(joined)")
self.makeDirectoryCommand(path: joined) { result in
switch result {
case .success:
print("Success")
temp.removeFirst()
self.sortDirectory(dirList: temp, filesUrls: filesUrl)
case .failure:
print("Failed to create directory \(joined)")
temp.removeAll()
self.projectDirectories.removeAll()
self.displayErrorMessage()
}
}
}
case .failure:
print("Failure - mkLibDir")
temp.removeAll()
self.projectDirectories.removeAll()
self.displayErrorMessage()
}
}
}
func mkSubLibDir(subdirectory: URL, copiedDirectory: [URL], filesURL: [URL]) {
print(#function)
var temp = copiedDirectory
print("List of Directories Currently in mkSubLibDir")
for i in temp {
print("\(i.path)")
}
var tempURL = subdirectory.pathComponents
tempURL.removeFirst(12)
let joined = tempURL.joined(separator: "/")
print("Modified Path top: \(joined)")
var pathDirectoryForListCommand = tempURL
pathDirectoryForListCommand.removeLast()
let pathDirectoryForListCommandJoined = pathDirectoryForListCommand.joined(separator: "/")
print("pathDirectoryForListCommandJoined: \(pathDirectoryForListCommandJoined)")
print("How its taken: \(pathDirectoryForListCommandJoined)/")
listDirectoryCommand(path: "\(pathDirectoryForListCommandJoined)/") { result in
switch result {
case .success(let contents):
if contents!.contains(where: { name in name.name == subdirectory.lastPathComponent}) {
print("FULL PATH OF: \(subdirectory.lastPathComponent)")
print("\(subdirectory.path)")
// Skips the existing directory.
temp.removeFirst()
self.sortDirectory(dirList: temp, filesUrls: filesURL)
} else {
print("\(subdirectory.lastPathComponent) directory does not exist")
print("Here's the full path of \(subdirectory.lastPathComponent): \(subdirectory.path)")
print("XXXX mkSubLibDir")
var tempURL = subdirectory.pathComponents
print("Incoming URL: \(tempURL)")
tempURL.removeFirst(12)
print("Modified Path without seperators: \(tempURL)")
let joined = tempURL.joined(separator: "/")
print("Modified Path: \(joined)")
var pathDirectoryForListCommand = tempURL
pathDirectoryForListCommand.removeLast()
let pathDirectoryForListCommandJoined = pathDirectoryForListCommand.joined(separator: "/")
print("pathDirectoryForListCommandJoined: \(pathDirectoryForListCommandJoined)")
self.makeDirectoryCommand(path: joined) { result in
switch result {
case .success:
print("Success")
temp.removeFirst()
self.sortDirectory(dirList: temp, filesUrls: filesURL)
case .failure:
print("Failed to create directory - 2")
temp.removeAll()
self.projectDirectories.removeAll()
self.displayErrorMessage()
}
}
}
case .failure:
print("Fail in: \(#function)")
temp.removeAll()
self.projectDirectories.removeAll()
self.displayErrorMessage()
}
}
}
func completedTransfer() {
DispatchQueue.main.async {
self.didCompleteTranfer = true
self.numOfFiles = 0
self.counter = 0
self.state = .complete
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.didCompleteTranfer = false
self.state = .idle
}
}
func transferFiles(files: [URL]) {
print(#function)
var copiedFiles = files
print("Number of files in filesArray \(files.count)")
print(files)
if files.isEmpty {
print("Array of contents empty - Check other directories")
self.completedTransfer()
DispatchQueue.main.asyncAfter(deadline: .now() + 2){
self.sendingBundle = false
self.counter = 0
self.numOfFiles = 0
self.contentList.removeAll()
}
} else {
guard let selectedUrl = files.first else {
print("No such file exist here")
return
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: selectedUrl.deletingPathExtension().lastPathComponent, relativeTo: selectedUrl).appendingPathExtension(selectedUrl.pathExtension)) else {
print("File not found")
return
}
if selectedUrl.deletingLastPathComponent().lastPathComponent == "CircuitPython 7.x"{
print("Selected Path: \(selectedUrl.path)")
var tempURL = selectedUrl.pathComponents
tempURL.removeFirst(12)
let joined = tempURL.joined(separator: "/")
var newModPath = tempURL
newModPath.removeLast()
print("Test file path: \(tempURL)")
print("File transfer modified path xx: \(joined)")
self.writeFileCommand(path: joined, data: data) { result in
switch result {
case .success(_):
copiedFiles.removeFirst()
self.transferFiles(files: copiedFiles)
case .failure(_):
DispatchQueue.main.async {
print("Transfer Failure")
print("\(joined)")
self.state = .failed
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .idle
}
}
self.displayErrorMessage()
}
}
}
else if selectedUrl.deletingLastPathComponent().lastPathComponent == "lib" {
var tempURL = selectedUrl.pathComponents
tempURL.removeFirst(12)
let joined = tempURL.joined(separator: "/")
print("File transfer modified path 11:\(joined)")
print("Updated Path:\(joined)")
writeFileCommand(path: joined, data: data) { result in
switch result {
case .success(_):
copiedFiles.removeFirst()
self.transferFiles(files: copiedFiles)
case .failure(_):
print("Transfer Failure - 2")
self.state = .failed
self.displayErrorMessage()
}
}
} else {
if selectedUrl.lastPathComponent == "README.txt" {
print("Got one")
copiedFiles.removeFirst()
self.transferFiles(files: copiedFiles)
} else {
var tempURL = selectedUrl.pathComponents
tempURL.removeFirst(12)
let joined = tempURL.joined(separator: "/")
print("File transfer modified path: \(joined)")
print("Updated Path:\(joined)")
writeFileCommand(path: joined, data: data) { result in
switch result {
case .success(_):
copiedFiles.removeFirst()
self.transferFiles(files: copiedFiles)
case .failure(let error):
print("Failed: \(error): \(result)")
// self.displayErrorMessage()
}
}
}
}
}
DispatchQueue.main.async {
self.sendingBundle = true
}
}
func readMyStatus() { func readMyStatus() {
// model.readFile(filename: "boot_out.txt")
print(#function)
print("BOOT INFO: \(bootUpInfo)") print("BOOT INFO: \(bootUpInfo)")
@ -41,21 +668,29 @@ class BleModuleViewModel: ObservableObject {
case let str where str.contains("circuitplayground_bluefruit"): case let str where str.contains("circuitplayground_bluefruit"):
print("Circuit Playground Bluefruit device") print("Circuit Playground Bluefruit device")
bootUpInfo = "circuitplayground_bluefruit" bootUpInfo = "circuitplayground_bluefruit"
// DispatchQueue.main.async { [self] in
// self.globalString.compatibilityString = "circuitplayground_bluefruit"
// }
case let str where str.contains("clue_nrf52840_express"): case let str where str.contains("clue_nrf52840_express"):
print("Clue device") print("Clue device")
bootUpInfo = "clue_nrf52840_express" bootUpInfo = "clue_nrf52840_express"
// DispatchQueue.main.async { [self] in
// globalString.compatibilityString = "clue_nrf52840_express"
//
// }
default: default:
print("Unknown Device") print("Unknown Device")
} }
}
func readBoardForCircuitPythonVersion() {
} }
// MARK: System // MARK: System
struct TransmissionProgress { struct TransmissionProgress {
@ -70,10 +705,6 @@ class BleModuleViewModel: ObservableObject {
} }
@Published var transmissionProgress: TransmissionProgress? @Published var transmissionProgress: TransmissionProgress?
@Published var lastTransmit: TransmissionLog? = TransmissionLog(type: .write(size: 334))
@Published var activeAlert: ActiveAlert?
// Data
private let bleManager = BleManager.shared
struct TransmissionLog: Equatable { struct TransmissionLog: Equatable {
enum TransmissionType: Equatable { enum TransmissionType: Equatable {
@ -100,6 +731,15 @@ class BleModuleViewModel: ObservableObject {
return modeText return modeText
} }
} }
@Published var lastTransmit: TransmissionLog? = TransmissionLog(type: .write(size: 334))
@Published var activeAlert: ActiveAlert?
// Data
private let bleManager = BleManager.shared
// MARK: - Setup // MARK: - Setup
func onAppear() { func onAppear() {
@ -133,10 +773,8 @@ class BleModuleViewModel: ObservableObject {
case .success(let data): case .success(let data):
self.lastTransmit = TransmissionLog(type: .read(data: data)) self.lastTransmit = TransmissionLog(type: .read(data: data))
let str = String(decoding: data, as: UTF8.self) let str = String(decoding: data, as: UTF8.self)
print("Read: \(str)") print("Read: \(str)")
self.bootUpInfo = str self.bootUpInfo = str
sharedBootinfo = str
case .failure(let error): case .failure(let error):
self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription)) self.lastTransmit = TransmissionLog(type: .error(message: error.localizedDescription))
@ -268,6 +906,10 @@ class BleModuleViewModel: ObservableObject {
private func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) { private func writeFileCommand(path: String, data: Data, completion: ((Result<Date?, Error>) -> Void)?) {
guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return } guard let fileTransferClient = fileTransferClient else { completion?(.failure(ProjectViewError.fileTransferUndefined)); return }
DispatchQueue.main.async {
self.counter += 1
}
DLog("start writeFile \(path)") DLog("start writeFile \(path)")
fileTransferClient.writeFile(path: path, data: data, progress: { [weak self] written, total in fileTransferClient.writeFile(path: path, data: data, progress: { [weak self] written, total in
@ -348,8 +990,6 @@ class BleModuleViewModel: ObservableObject {
} }
public var sharedBootinfo = ""
enum ActiveAlert: Identifiable { enum ActiveAlert: Identifiable {
case error(error: Error) case error(error: Error)

View file

@ -8,83 +8,76 @@
import SwiftUI import SwiftUI
import FileTransferClient import FileTransferClient
class GlobalString: ObservableObject {
@Published var projectString = ""
@Published var downloadLinkString = ""
@Published var compatibilityString = ""
@Published var counterG = 0
@Published var numberOfFilesG = 0
@Published var isSendingG = false
@Published var bundleHasBeenDownloaded = false
@Published var numberOfTimesDownloaded = 0
@Published var attemptToDownload = false
@Published var attemptToSend = false
}
struct DemoSubview: View { struct DemoSubview: View {
@State var transferInProgress = false
@State var isDownloaded = false
@EnvironmentObject var globalString : GlobalString
@Binding var bindingString: String @Binding var bindingString: String
let result: ResultItem @Binding var downloadStateBinder: DownloadState
@State private var toggleView: Bool = false
let title: String
let image: String
let description: String
let learnGuideLink: URLRequest
let downloadLink: String
let compatibility: [String]
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
@StateObject var downloadModel = DownloadViewModel()
@StateObject var viewModel = SubCellViewModel() @StateObject var viewModel = SubCellViewModel()
@StateObject var selectionModel = BleModuleViewModel()
@StateObject var contentTransfer = BleContentTransfer() @StateObject var contentTransfer = BleContentTransfer()
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
@Binding var isConnected : Bool @Binding var isConnected : Bool
@State private var showWebViewPopover: Bool = false @State private var showWebViewPopover: Bool = false
@State var errorOccured = false
@State private var presentAlert = false
func showAlertMessage() { @State var offlineWithoutProject = false
alertMessage(title: """
There's a problem with your internet connection.
Try again later.
""", exitTitle: "Ok") {
}
}
func showTransferErrorMessage() {
alertMessage(title: """
Transfer Failed
Disconnect device from the computer.
Press "Reset" on the device and use a battery source.
""", exitTitle: "Retry") {
contentTransfer.transferError = false
}
}
func showDownloadErrorMessage() {
alertMessage(title: """
Server Error
This project can not be downloaded at this time
Try again later
""", exitTitle: "Ok") {
contentTransfer.downloaderror = false
}
}
var body: some View { var body: some View {
VStack { VStack {
if viewModel.projectDownloaded {
// HStack {
// Spacer()
//
// Text("Downloaded")
// .foregroundColor(.green)
// .padding(.trailing, -15)
// Circle()
// .fill(.green)
// .frame(width: 15, height: 15)
// .padding()
// }
// .padding(.vertical, -8)
}
VStack(alignment: .leading, spacing: 0, content: { VStack(alignment: .leading, spacing: 0, content: {
ImageWithURL(result.projectImage) ImageWithURL(image)
.scaledToFit() .scaledToFit()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.cornerRadius(14) .cornerRadius(14)
.padding(.top, 30) .padding(.top, 30)
Text(result.description) Text(description)
.font(Font.custom("ReadexPro-Regular", size: 18)) .font(Font.custom("ReadexPro-Regular", size: 18))
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.minimumScaleFactor(0.1) .minimumScaleFactor(0.1)
@ -94,7 +87,7 @@ Try again later
.padding(.top, 5) .padding(.top, 5)
ForEach(result.compatibility, id: \.self) { string in ForEach(compatibility, id: \.self) { string in
if string == "circuitplayground_bluefruit" { if string == "circuitplayground_bluefruit" {
HStack { HStack {
@ -130,19 +123,14 @@ Try again later
.padding(.horizontal, 30) .padding(.horizontal, 30)
Button(action: { Button(action: {
if !viewModel.isConnectedToInternet { showWebViewPopover = true
showAlertMessage()
} else {
showWebViewPopover = true
}
}) { }) {
LearnGuideButton() LearnGuideButton()
.padding(.top, 20) .padding(.top, 20)
} }
.sheet(isPresented: $showWebViewPopover, content: { .sheet(isPresented: $showWebViewPopover, content: {
SwiftUIWebView(webAddress: result.learnGuideLink) WebView(URLRequest(url: learnGuideLink.url!))
}) })
@ -150,72 +138,123 @@ Try again later
if isConnected { if isConnected {
if result.compatibility.contains(bindingString) { if compatibility.contains(bindingString) {
Button {
contentTransfer.getProjectURL(nameOf: title)
} label: {
Text("XXX")
}
if downloadStateBinder == .idle {
// Button {
// viewModel.deleteStoredFilesInFM()
// } label: {
// Text("Delete File Manager Contents")
// .bold()
// .padding(12)
// }
if contentTransfer.downloadState == .idle {
Button(action: { Button(action: {
/// Condition: Connected to the internet downloadStateBinder = .transferring
///- If you're not connected to the internet, but you've downloaded the project... globalString.isSendingG = true
/// - If you're not connected to the internet, and you're project is not downloaded... globalString.counterG = 0
/// *Show Alert* globalString.numberOfFilesG = 1
globalString.downloadLinkString = downloadLink
globalString.projectString = title
globalString.attemptToDownload.toggle()
if selectionModel.isConnectedToInternet == false {
print("Going offline...")
downloadStateBinder = .transferring
globalString.projectString = title
globalString.attemptToSend.toggle()
}
if viewModel.projectDownloaded == false && selectionModel.isConnectedToInternet == false {
offlineWithoutProject = true
downloadStateBinder = .idle
}
if viewModel.projectDownloaded == false {
if viewModel.projectDownloaded == false && viewModel.isConnectedToInternet == false {
showAlertMessage()
} else {
contentTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
} }
}) { }) {
RunItButton() RunItButton()
.padding(.top, 20) .padding(.top, 20)
} }
} }
if contentTransfer.downloadState == .failed { if downloadStateBinder == .failed {
FailedButton() FailedButton()
.padding(.top, 20) .padding(.top, 20)
} }
if contentTransfer.downloadState == .transferring {
DownloadingButton()
.padding(.top, 20)
.disabled(true)
VStack(alignment: .center, spacing: 5) { if downloadStateBinder == .transferring {
ProgressView("", value: CGFloat(contentTransfer.counter), total: CGFloat(contentTransfer.numOfFiles) )
.padding(.horizontal, 90)
.padding(.top, -8)
.padding(.bottom, 10)
.accentColor(Color.gray)
.scaleEffect(x: 1, y: 2, anchor: .center)
.cornerRadius(10)
.frame(height: 10)
ProgressView() Button(action: {
print("Project Selected: \(title) - DemoSubView")
globalString.projectString = title
globalString.numberOfTimesDownloaded += 1
}) {
DownloadingButton()
.padding(.top, 20)
} }
.disabled(true)
if globalString.isSendingG {
VStack(alignment: .center, spacing: 0) {
ProgressView("", value: CGFloat(globalString.counterG), total: CGFloat(globalString.numberOfFilesG) )
.padding(.horizontal, 90)
.padding(.top, -8)
.padding(.bottom, 10)
.accentColor(Color.gray)
.scaleEffect(x: 1, y: 2, anchor: .center)
.cornerRadius(10)
.frame(height: 10)
ProgressView()
}
}
} }
if contentTransfer.downloadState == .complete {
if downloadStateBinder == .complete {
CompleteButton() CompleteButton()
.padding(.top, 20) .padding(.top, 20)
} }
} }
} else { } else {
Button { Button {
rootViewModel.goToSelection() rootViewModel.goTobluetoothPairing()
} label: { } label: {
ConnectButton() ConnectButton()
.padding(.top, 20) .padding(.top, 20)
@ -225,48 +264,58 @@ Try again later
} }
Spacer() Spacer()
.frame(height: 30) .frame(height: 30)
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
.onAppear(){
print("On Appear")
contentTransfer.contentCommands.setup(fileTransferClient: connectionManager.selectedClient)
// viewModel.readFile(filename: "boot_out.txt") .alert("Project Not Found", isPresented: $offlineWithoutProject) {
Button("OK") {
// Handle acknowledgement.
print("OK")
offlineWithoutProject = false
downloadStateBinder = .idle
selectionModel.state = .idle
print("\(offlineWithoutProject)")
}
} message: {
Text("""
To use this project, connect to the internet.
""")
.multilineTextAlignment(.leading)
}
.onChange(of: downloadModel.isDownloading, perform: { newValue in
viewModel.getProjectForSubClass(nameOf: title)
})
.onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
print("For project: \(title), project download is \(newValue)")
globalString.projectString = title
if newValue {
DispatchQueue.main.async {
print("Getting project from Subclass \(title)")
viewModel.getProjectForSubClass(nameOf: title)
isDownloaded = true
}
}else {
print("Is not downloaded")
isDownloaded = false
} }
.onChange(of: contentTransfer.transferError, perform: { newValue in })
if newValue { .onAppear(perform: {
showTransferErrorMessage() viewModel.getProjectForSubClass(nameOf: title)
} if viewModel.projectDownloaded {
}) isDownloaded = true
} else {
.onChange(of: contentTransfer.downloaderror, perform: { newValue in isDownloaded = false
if newValue {
showDownloadErrorMessage()
}
})
// .onChange(of: connectionManager.selectedClient) { selectedClient in
// viewModel.setup(fileTransferClient: selectedClient)
// }
.onAppear(perform: {
contentTransfer.readMyStatus()
viewModel.searchPathForProject(nameOf: result.projectName)
if viewModel.projectDownloaded {
viewModel.projectDownloaded = true
} else {
viewModel.projectDownloaded = false
}
} }
print("is downloaded? \(isDownloaded)")
})
.padding(.top, 8)
)
.padding(.top, 8)
} }
} }

View file

@ -9,21 +9,23 @@ import Foundation
struct DemoViewCell: View { struct DemoViewCell: View {
@EnvironmentObject var expandedState : ExpandedBLECellState
@StateObject var spotlight = SpotlightCounter()
let result : ResultItem let result : ResultItem
@State private var isExpanded: Bool = false {
@State var isExpanded: Bool = false {
didSet { didSet {
onViewGeometryChanged() onViewGeometryChanged()
} }
} }
@Binding var isConnected: Bool @Binding var isConnected: Bool
@Binding var deviceInfo: String @Binding var bootOne: String
let onViewGeometryChanged: ()->Void let onViewGeometryChanged: ()->Void
@Binding var stateBinder: DownloadState
var body: some View { var body: some View {
content content
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -33,10 +35,19 @@ struct DemoViewCell: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
header header
if isExpanded { if isExpanded {
Group { Group {
DemoSubview(bindingString: $deviceInfo, result: result, isConnected: $isConnected) DemoSubview(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
image: result.projectImage,
description: result.description,
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
downloadLink: result.bundleLink,
compatibility: result.compatibility,
isConnected: $isConnected)
} }
} }
@ -64,10 +75,7 @@ struct DemoViewCell: View {
.padding(.leading) .padding(.leading)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(Color("pyleap_purple")) .background(Color("pyleap_purple"))
.onTapGesture { .onTapGesture { isExpanded.toggle() }
expandedState.currentCell = result.bundleLink
}
} }
} }

View file

@ -13,7 +13,7 @@ struct ScrollRefreshableView<Content: View>: View {
var onRefresh: ()->() var onRefresh: ()->()
init(title: String, tintColor: Color, @ViewBuilder content: @escaping () -> Content, onRefresh: @escaping () -> ()) { init(title: String, tintColor: Color, @ViewBuilder content: @escaping ()->Content, onRefresh: @escaping ()->()) {
self.content = content() self.content = content()
self.onRefresh = onRefresh self.onRefresh = onRefresh

View file

@ -11,74 +11,37 @@ class SubCellViewModel: ObservableObject {
@Published var projectDownloaded = false @Published var projectDownloaded = false
@Published var failedProjectLaunch = false @Published var failedProjectLaunch = false
@Published var isConnectedToInternet = false
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
var manager = FileManager.default
var networkMonitor = NetworkMonitor() func getProjectForSubClass(nameOf project: String) {
init() { if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
internetMonitoring() // for case condition: Only process URLs
} for case let fileURL as URL in enumerator {
func deleteStoredFilesInFM () { if fileURL.lastPathComponent == project {
print("\(#function) @Line: \(#line)") failedProjectLaunch = false
do { projectDownloaded = true
try manager.removeItem(at: directoryPath) print(#function)
print("Searching for... \(project)")
print("URL Path: \(fileURL.path)")
print("URL : \(fileURL)")
} catch { return
print(error)
}
}
func find(projectWith title: String) { } else {
failedProjectLaunch = true
projectDownloaded = false
print("Project was not found...")
let nestedFolderURL = directoryPath.appendingPathComponent(title) }
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("\(title) - Exists")
projectDownloaded = true
} else {
print("\(title) - Does not exist.")
projectDownloaded = false
}
}
func internetMonitoring() {
networkMonitor.startMonitoring()
networkMonitor.monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("Connected to internet.")
DispatchQueue.main.async {
// self.showAlert = false
self.isConnectedToInternet = true
}
} else {
print("No connection.")
DispatchQueue.main.async {
// self.showAlert = true
self.isConnectedToInternet = false
}
} }
print("isExpensive: \(path.isExpensive)")
} }
}
func searchPathForProject(nameOf project: String) {
var manager = FileManager.default
let nestedFolderURL = directoryPath.appendingPathComponent(project)
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("\(project) - Exist")
projectDownloaded = true
} else {
print("Does not exist - \(project)")
projectDownloaded = false
}
} }
} }

View file

@ -11,8 +11,6 @@ import FileTransferClient
struct RootView: View { struct RootView: View {
@StateObject private var model = RootViewModel() @StateObject private var model = RootViewModel()
@StateObject var currentCellID = ExpandedState()
@StateObject var currentBLECellID = ExpandedBLECellState()
@ObservedObject var connectionManager = FileTransferConnectionManager.shared @ObservedObject var connectionManager = FileTransferConnectionManager.shared
@AppStorage("onboarding") var onboardingSeen = true @AppStorage("onboarding") var onboardingSeen = true
@ -21,7 +19,7 @@ struct RootView: View {
var body: some View { var body: some View {
Group { Group{
switch model.destination { switch model.destination {
case .onboard : case .onboard :
@ -45,35 +43,15 @@ struct RootView: View {
case .fileTransfer: case .fileTransfer:
BleModuleView() BleModuleView()
case .wifiServiceSelection:
WifiServiceSelectionView()
case .wifi: case .wifi:
WifiView() WifiView()
case .selection:
SelectionView()
case .wifiSelection:
WifiSelection()
case .wifiPairingTutorial:
WifiPairingView()
case .settings: case .settings:
SettingsView() SettingsView()
case .bleSettings:
BLESettingsView()
default: default:
FillerView() FillerView()
} }
} }
.environmentObject(currentCellID)
.environmentObject(currentBLECellID)
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in .onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
if !Config.isSimulatingBluetooth { if !Config.isSimulatingBluetooth {
@ -97,20 +75,6 @@ struct RootView: View {
} else { } else {
isReconnecting = false isReconnecting = false
} }
}
.onChange(of: connectionManager.isDisconnectingFromCurrent) { isDisconnected in
if isDisconnected {
print("Is disconnected.")
isReconnecting = false
connectionManager.clearAllPeripheralInfo()
connectionManager.peripherals = []
connectionManager.isDisconnectingFromCurrent = false
model.destination = .selection
}
} }
@ -118,7 +82,6 @@ struct RootView: View {
DLog("App moving to the foreground. Force reconnect") DLog("App moving to the foreground. Force reconnect")
FileTransferConnectionManager.shared.reconnect() FileTransferConnectionManager.shared.reconnect()
} }
.environmentObject(model) .environmentObject(model)
.environmentObject(connectionManager) .environmentObject(connectionManager)
.background(Color.white) .background(Color.white)
@ -126,7 +89,6 @@ struct RootView: View {
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
.preferredColorScheme(.light) .preferredColorScheme(.light)
.statusBar(hidden: true)
} }
} }

View file

@ -8,9 +8,7 @@
import Foundation import Foundation
import FileTransferClient import FileTransferClient
public class RootViewModel: ObservableObject { class RootViewModel: ObservableObject {
// public var shared = RootViewModel()
enum Destination { enum Destination {
//case splash //case splash
@ -22,13 +20,7 @@ public class RootViewModel: ObservableObject {
case fileTransfer case fileTransfer
case wifi case wifi
case settings case settings
case bleSettings
case mainSelection case mainSelection
case wifiSelection
case wifiPairingTutorial
case wifiServiceSelection
case selection
} }
@Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup @Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup
@ -38,18 +30,6 @@ public class RootViewModel: ObservableObject {
//destination = .test //destination = .test
} }
func goToWiFiServiceSelection() {
destination = .wifiServiceSelection
}
func goToWifiPairingTutorial() {
destination = .wifiPairingTutorial
}
func goToWiFiSelection() {
destination = .wifiSelection
}
func goToWifiView() { func goToWifiView() {
destination = .wifi destination = .wifi
} }
@ -58,10 +38,6 @@ public class RootViewModel: ObservableObject {
destination = .bluetoothPairing destination = .bluetoothPairing
} }
func goToSelection(){
destination = .selection
}
func goToMainSelection(){ func goToMainSelection(){
destination = .mainSelection destination = .mainSelection
} }
@ -76,10 +52,6 @@ public class RootViewModel: ObservableObject {
} }
} }
func backToMain() {
destination = .main
}
func goToStartup(){ func goToStartup(){
destination = .startup destination = .startup
} }
@ -92,14 +64,10 @@ public class RootViewModel: ObservableObject {
destination = .fileTransfer destination = .fileTransfer
} }
func goToSettings(content: SettingState){ func goToSettings(){
destination = .settings destination = .settings
} }
func goToBLESettings(){
destination = .bleSettings
}
func showWarningIfBluetoothStateIsNotReady() { func showWarningIfBluetoothStateIsNotReady() {
let bluetoothState = BleManager.shared.state let bluetoothState = BleManager.shared.state
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized

View file

@ -0,0 +1,317 @@
//
// SettingsView.swift
// PyLeap
//
// Created by Trevor Beaton on 9/7/22.
//
import SwiftUI
struct SettingsView: View {
@State private var jsonFileName: String = ""
@State private var pythonFileName: String = ""
@State private var presentJSONAlert = false
@State private var presentPythonAlert = false
@EnvironmentObject var rootViewModel: RootViewModel
@ObservedObject var networkModel = NetworkService()
@StateObject var viewModel = SettingsViewModel()
private let kPrefix = Bundle.main.bundleIdentifier!
let userDefaults = UserDefaults.standard
func showInvalidURLEntryAlert() {
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
}
}
func showDownloadConfirmationAlert() {
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
}
}
func showDisconnectionPrompt() {
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
viewModel.clearKnownIPAddress()
rootViewModel.goToWifiView()
} cancel: {
}
}
var body: some View {
VStack {
HStack {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
}
.padding(.all,20)
Spacer()
}
//.frame(width: .infinity)
.frame(height: UIScreen.main.bounds.height / 19)
.background(Color(.systemGroupedBackground))
HStack {
Text("Settings")
.font(.largeTitle)
.padding(.all,20)
Spacer()
}
.background(Color(.systemGroupedBackground))
Form {
VStack() {
if viewModel.connectedToDevice {
Section() {
VStack(alignment: .leading, spacing: 8) {
Text("Host Name:")
.bold()
Text(viewModel.hostName)
Text("IP Address:")
.bold()
Text(viewModel.ipAddress)
Text("Device:")
.bold()
Text(viewModel.device)
}
.padding(.leading,0)
}
} else {
Section() {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Connect to Adafruit Device")
}
}
}
}
if viewModel.connectedToDevice {
Section() {
Button {
showDisconnectionPrompt()
} label: {
Text("Disconnect")
}
}
}
Section {
Toggle(isOn: .constant(false)) {
Text("Dark Mode")
}
}
header: {
Text("Display")
}
if viewModel.connectedToDevice {
Section {
Text("Enter project URL")
TextField("https://", text: $pythonFileName)
.keyboardType(.URL)
.textContentType(.URL)
.onSubmit {
NetworkService.shared.fetchThirdParyProject(urlString: pythonFileName)
print(pythonFileName)
pythonFileName = ""
}
} header: {
Text("Add Project")
}
.listRowSeparator(.hidden)
Section {
Button("Create Python File"){
presentPythonAlert = true
}
.alert("Create Python File", isPresented: $presentPythonAlert, actions: {
TextField("", text: $pythonFileName)
Button("Add", action: {})
Button("Cancel", role: .cancel, action: {
presentPythonAlert = false
})
}, message: {
Text("Please enter your username and password.")
})
Button("Create JSON File"){
presentJSONAlert = true
}
.alert("Create JSON File", isPresented: $presentJSONAlert, actions: {
TextField("", text: $jsonFileName)
Button("Add", action: {})
Button("Cancel", role: .cancel, action: {
presentJSONAlert = false
})
}, message: {
Text("Please enter your username and password.")
})
}
header: {
Text("Create")
}
}
Section{
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
}
.font(.system(size: 16, weight: .semibold))
}
.onChange(of: viewModel.invalidURL, perform: { newValue in
showInvalidURLEntryAlert()
viewModel.invalidURL = false
})
.onChange(of: viewModel.confirmDownload, perform: { newValue in
showDownloadConfirmationAlert()
viewModel.confirmDownload = false
})
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
// .font(.system(size: 18, weight: .regular, design: .default))
.foregroundColor(.blue)
}
.padding(12)
}
}
}
//.padding(.all, 11.0)
.background(Color(UIColor.systemGroupedBackground))
// .safeAreaInset(edge: .top) {
// HStack {
// Button {
// rootViewModel.goToWifiView()
// } label: {
// Text("Back")
// }
//
// .padding(.all,20)
// Spacer()
// }
// // .background(Color(UIColor.systemGroupedBackground))
//
//
//
//
// }
}
}
extension View {
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(.init(title: primaryTitle, style: .destructive, handler: { _ in
disconnect()
}))
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}
func alertMessage(title: String, exitTitle: String, cancel: @escaping()->()){
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}
func alertTF(title: String, message: String, hintText: String, primaryTitle: String, secondaryTitle: String, primaryAction: @escaping(String)->(), secondaryAction: @escaping()->()) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addTextField { field in
field.keyboardType = .decimalPad
field.placeholder = hintText
}
alert.addAction(.init(title: secondaryTitle, style: .cancel, handler: { _ in
secondaryAction()
}))
alert.addAction(.init(title: primaryTitle, style: .default, handler: { _ in
if let text = alert.textFields?[0].text {
primaryAction(text)
} else {
primaryAction("")
}
}))
rootController().present(alert, animated: true, completion: nil)
}
func rootController()->UIViewController{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
}

View file

@ -19,7 +19,6 @@ class SettingsViewModel: ObservableObject {
@Published var invalidURL = false @Published var invalidURL = false
@Published var confirmDownload = false @Published var confirmDownload = false
init() { init() {
check() check()
registerNotifications(enabled: true) registerNotifications(enabled: true)
@ -57,9 +56,10 @@ errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkReq
func check() { func check() {
print(#function) print(#function)
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil { if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
connectedToDevice = false connectedToDevice = false
} else { } else {
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
connectedToDevice = true connectedToDevice = true

View file

@ -16,16 +16,13 @@ struct BlinkaAnimationView: View {
.repeatForever(autoreverses: false) .repeatForever(autoreverses: false)
} }
var height: CGFloat
var width: CGFloat
var body: some View { var body: some View {
ZStack { ZStack {
Image("BlinkaLoading") Image("BlinkaLoading")
.resizable(resizingMode: .stretch) .resizable(resizingMode: .stretch)
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: width, height: height) .frame(width: 300, height: 300)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0)) .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.animation(self.isAnimating ? foreverAnimation : .default) .animation(self.isAnimating ? foreverAnimation : .default)
@ -37,6 +34,6 @@ struct BlinkaAnimationView: View {
struct ScanningView_Previews: PreviewProvider { struct ScanningView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
BlinkaAnimationView(height: 300, width: 300) BlinkaAnimationView()
} }
} }

View file

@ -17,7 +17,7 @@ struct RunItButton: View {
.cornerRadius(25) .cornerRadius(25)
.foregroundColor(Color("pyleap_pink")) .foregroundColor(Color("pyleap_pink"))
Text("Run") Text("Run it!")
.font(Font.custom("ReadexPro-Regular", size: 25)) .font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white) .foregroundColor(Color.white)
.frame(height: 50) .frame(height: 50)
@ -35,7 +35,7 @@ struct PairingTutorialButton: View {
Rectangle() Rectangle()
.frame(width: 270, height: 50, alignment: .center) .frame(width: 270, height: 50, alignment: .center)
.cornerRadius(25) .cornerRadius(25)
.foregroundColor(Color("bluetooth_button_color")) .foregroundColor(Color("pyleap_pink"))
Text("Pairing Tutorial") Text("Pairing Tutorial")
.font(Font.custom("ReadexPro-Regular", size: 25)) .font(Font.custom("ReadexPro-Regular", size: 25))

View file

@ -10,44 +10,50 @@ import SwiftUI
struct HeaderView: View { struct HeaderView: View {
@State var showSheetView = false @State var showSheetView = false
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
var body: some View { var body: some View {
VStack {
HStack {
HStack (alignment: .center, spacing: 0) { Button {
rootViewModel.goToWifiView()
} label: {
Image(systemName: "wifi.circle")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.offset(y: 15)
.foregroundColor(.white)
Image(systemName: "gearshape") }
.resizable() .padding()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.padding(.leading, CGFloat(20))
.foregroundColor(.clear)
Spacer()
Image("pyleap_logo_white") Spacer()
.resizable() Image("pyleap_logo_white")
.scaledToFit()
.frame(width: 125, height: 125)
.offset(y: 12)
Spacer()
Button {
rootViewModel.goToBLESettings()
} label: {
Image(systemName: "plus")
.resizable() .resizable()
.frame(width: 25, height: 25, alignment: .center) .scaledToFit()
.offset(y: 15) .frame(width: 125, height: 125)
.padding(.trailing, CGFloat(20)) .offset(y: 12)
.foregroundColor(.white) // .padding(.leading, 60)
Spacer()
Button {
self.showSheetView.toggle()
} label: {
Image(systemName: "info.circle")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.offset(y: 15)
.foregroundColor(.white)
}.sheet(isPresented: $showSheetView) {
CreditView(isPresented: $showSheetView)
}
.padding()
} }
Spacer()
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(maxHeight: 120) .frame(maxHeight: 120)
.background(Color("pyleap_gray")) .background(Color("pyleap_gray"))
@ -101,7 +107,6 @@ struct MainHeaderView: View {
struct HeaderView_Previews: PreviewProvider { struct HeaderView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
HeaderView() HeaderView()
} }

View file

@ -11,7 +11,7 @@ struct SubHeaderView: View {
var body: some View { var body: some View {
HStack { HStack {
Text("Browse available WiFi PyLeap Projects") Text("Browse available Wi-Fi PyLeap Projects")
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) .multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.font(Font.custom("ReadexPro-Regular", size: 25)) .font(Font.custom("ReadexPro-Regular", size: 25))
@ -22,14 +22,10 @@ struct SubHeaderView: View {
} }
struct MainSubHeaderView: View { struct MainSubHeaderView: View {
let device: String
var body: some View { var body: some View {
HStack { HStack {
Text("Pick a project to run on your \(device)") Text("Browse available PyLeap Projects")
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) .multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.font(Font.custom("ReadexPro-Regular", size: 25)) .font(Font.custom("ReadexPro-Regular", size: 25))

View file

@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
struct WifiHeaderView: View { struct WifiHeaderView: View {
@Environment(\.presentationMode) var presentationMode
@State var showSheetView = false @State var showSheetView = false
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
@ -16,38 +15,41 @@ struct WifiHeaderView: View {
VStack { VStack {
HStack {
Button {
rootViewModel.goToMain()
} label: {
Image(systemName: "arrow.backward")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.offset(y: 15)
.foregroundColor(.white)
}
HStack (alignment: .center, spacing: 0) { .padding()
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.padding(.leading, CGFloat(20))
.foregroundColor(.clear)
Spacer() Spacer()
Image("pyleap_logo_white") Image("pyleap_logo_white")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 125, height: 125) .frame(width: 125, height: 125)
.offset(y: 12) .offset(y: 12)
// .padding(.leading, 60)
Spacer() Spacer()
Button { Button {
rootViewModel.goToSettings(content: .wifi) rootViewModel.goToSettings()
} label: { } label: {
Image(systemName: "plus") Image(systemName: "gearshape")
.resizable() .resizable()
.frame(width: 25, height: 25, alignment: .center) .frame(width: 30, height: 30, alignment: .center)
.offset(y: 15) .offset(y: 15)
.padding(.trailing, CGFloat(20))
.foregroundColor(.white) .foregroundColor(.white)
} }
.padding()
} }
Spacer() Spacer()
} }

View file

@ -14,14 +14,15 @@ enum AdafruitDevices {
case esp32s2 case esp32s2
} }
struct MainSelectionView: View { struct MainSelectionView: View {
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject private var connectionManager: FileTransferConnectionManager
@State private var showWebViewPopover: Bool = false @State private var showWebViewPopover: Bool = false
@State private var inConnectedInSelectionView = true
@State private var boardBootInfo = ""
@EnvironmentObject var expandedState : ExpandedBLECellState
@ObservedObject var networkModel = NetworkService()
@ObservedObject var viewModel = MainSelectionViewModel() @ObservedObject var viewModel = MainSelectionViewModel()
@ -30,6 +31,7 @@ enum AdafruitDevices {
@State private var test = "" @State private var test = ""
@State private var nilBinder = DownloadState.idle @State private var nilBinder = DownloadState.idle
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
@AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
@ -39,11 +41,30 @@ enum AdafruitDevices {
VStack(alignment: .center, spacing: 0) { VStack(alignment: .center, spacing: 0) {
MainHeaderView() MainHeaderView()
HStack(alignment: .center, spacing: 8, content: {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Connect to Wi-Fi Mode")
.font(Font.custom("ReadexPro-Regular", size: 16))
.underline()
}
})
.padding(.all, 0.0)
.frame(maxWidth: .infinity)
.frame(maxHeight: 40)
.background(Color("pyleap_blue"))
.foregroundColor(.white)
HStack(alignment: .center, spacing: 8, content: { HStack(alignment: .center, spacing: 8, content: {
Text("Not Connected to a Device.") Text("Not Connected to a Device.")
.font(Font.custom("ReadexPro-Regular", size: 16)) .font(Font.custom("ReadexPro-Regular", size: 16))
Button { Button {
rootViewModel.goToSelection() rootViewModel.goTobluetoothPairing()
} label: { } label: {
Text("Connect Now") Text("Connect Now")
.font(Font.custom("ReadexPro-Regular", size: 16)) .font(Font.custom("ReadexPro-Regular", size: 16))
@ -57,11 +78,14 @@ enum AdafruitDevices {
.background(Color("pyleap_burg")) .background(Color("pyleap_burg"))
.foregroundColor(.white) .foregroundColor(.white)
ScrollView { ScrollView {
MainSubHeaderView(device: "Adafruit device") MainSubHeaderView()
if viewModel.pdemos.isEmpty { if networkModel.pdemos.isEmpty {
HStack{ HStack{
Spacer() Spacer()
ProgressView() ProgressView()
@ -74,42 +98,18 @@ enum AdafruitDevices {
ScrollViewReader { scroll in ScrollViewReader { scroll in
ForEach(networkModel.pdemos) { demo in
DemoViewCell(result: demo, isConnected: $isConnected, bootOne: $test, onViewGeometryChanged: {
withAnimation {
ForEach(viewModel.pdemos) { demo in scroll.scrollTo(demo.id)
if demo.bundleLink == expandedState.currentCell {
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
.onAppear(){
print("Cell Appeared")
withAnimation {
scroll.scrollTo(demo.id)
}
} }
}, stateBinder: $nilBinder)
} else {
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
}
} }
} }
} }
} }
.onDisappear() {
}
/// **Pull down to Refresh feature** /// **Pull down to Refresh feature**
// ScrollRefreshableView(title: "Refresh", tintColor: .purple) { // ScrollRefreshableView(title: "Refresh", tintColor: .purple) {
@ -144,9 +144,7 @@ enum AdafruitDevices {
}) })
.onAppear() { .onAppear() {
networkModel.fetch()
print("Opened MainSelectionView")
} }
.fullScreenCover(isPresented: $shouldShowOnboarding, content: { .fullScreenCover(isPresented: $shouldShowOnboarding, content: {
@ -158,89 +156,12 @@ enum AdafruitDevices {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
} }
//struct MainSelectionView_Previews: PreviewProvider {
// static var previews: some View {
// MainSelectionView()
// }
//}
//
//struct MainSelectionView: View {
//
// @State private var showWebViewPopover: Bool = false
// @ObservedObject var networkModel = NetworkService()
// @ObservedObject var viewModel = MainSelectionViewModel()
// @State private var test = ""
// @State private var nilBinder = DownloadState.idle
// @EnvironmentObject var rootViewModel: RootViewModel
// @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
//
// var body: some View {
// VStack(alignment: .center, spacing: 0) {
// MainHeaderView()
// connectionMessageView()
// ScrollView {
// MainSubHeaderView(device: "Adafruit device")
// if networkModel.pdemos.isEmpty {
// loadingIndicatorView()
// }
// ScrollViewReader { scroll in
// ForEach(networkModel.pdemos) { demo in
// demoViewCell(demo: demo, scroll: scroll)
// }
// }
// }
// }
// }
//
// private func connectionMessageView() -> some View {
// HStack(alignment: .center, spacing: 8) {
// Text("Not Connected to a Device.")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// Button {
// rootViewModel.goToSelection()
// } label: {
// Text("Connect Now")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// .underline()
// }
// }
// .padding(.all, 0.0)
// .frame(maxWidth: .infinity)
// .frame(maxHeight: 40)
// .background(Color("pyleap_burg"))
// .foregroundColor(.white)
// }
//
// private func loadingIndicatorView() -> some View {
// HStack{
// Spacer()
// ProgressView()
// .scaleEffect(2)
// Spacer()
// }
// .padding(0)
// }
//
// private func demoViewCell(demo: Demo, scroll: ScrollViewProxy) -> some View {
// if demo.bundleLink == expandedState.currentCell {
// DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
// })
// .onAppear(){
// print("Cell Appeared")
// withAnimation {
// scroll.scrollTo(demo.id)
// }
// }
// } else {
// DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
// })
// }
// }
//}
struct MainSelectionView_Previews: PreviewProvider {
static var previews: some View {
MainSelectionView()
}
}

View file

@ -7,82 +7,44 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import Network
import Combine
class InternetConnectionManager: ObservableObject {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "InternetConnectionMonitor")
@Published var isConnected = false
init() {
startMonitoring(completion: {
monitor.pathUpdateHandler = { path in
DispatchQueue.main.async {
let newIsConnected = path.status == .satisfied
if self.isConnected != newIsConnected {
self.isConnected = newIsConnected
print("net: \(path.status) \(self.isConnected)")
}
}
}
})
}
func startMonitoring(completion:()->Void) {
print("Start Monitoring Network")
monitor.start(queue: queue)
completion()
}
deinit {
print("Network Deinit")
monitor.cancel()
}
}
class MainSelectionViewModel: ObservableObject { class MainSelectionViewModel: ObservableObject {
@ObservedObject var networkModel = NetworkService() @ObservedObject var networkModel = NetworkService()
@ObservedObject var networkMonitor = InternetConnectionManager()
let userDefaults = UserDefaults.standard
let fileManager = FileManager.default
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let dataStore = DataStore()
@Published var pdemos : [ResultItem] = [] @Published var pdemos : [ResultItem] = []
var networkMonitorCancellable: AnyCancellable?
init() { init() {
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json") // load()
// self.pdemos = load()
}
networkMonitorCancellable = networkMonitor.$isConnected.sink { isConnected in
if isConnected {
print("The device is currently connected to the internet.")
// Perform some action when the device is connected to the internet.
self.networkModel.fetch {
self.pdemos = self.dataStore.loadDefaultList()
}
} else {
print("The device is not currently connected to the internet.")
// Perform some action when the device is not connected to the internet.
print("Loading cached remote data.")
self.pdemos = self.dataStore.loadDefaultList()
}
}
func makeNetworkCall(){
networkModel.fetch()
} }
func load() -> [ResultItem] {
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
print(loadedProjects)
// pdemos = loadedProjects
return loadedProjects
}
}
print("Returned Empty pdemos")
return []
}
} }

View file

@ -1,121 +0,0 @@
//
// SelectionView.swift
// PyLeap
//
// Created by Trevor Beaton on 10/24/22.
//
import SwiftUI
import FileTransferClient
struct SelectionView: View {
@EnvironmentObject var rootViewModel: RootViewModel
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
var body: some View {
VStack {
HStack {
Button {
// rootViewModel.goToSelection()
} label: {
Image(systemName: "arrow.backward")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.foregroundColor(.clear)
}
.padding()
Spacer()
}
.padding(.top, 15)
Image("pyleapLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.top, 100)
.padding(.horizontal, 60)
Text("What type of device do you want to connect to?")
.font(Font.custom("ReadexPro-Regular", size: 36))
.minimumScaleFactor(0.01)
.multilineTextAlignment(.center)
.padding(.top, 100)
.padding(.horizontal, 30)
Spacer()
VStack {
Button {
rootViewModel.goToWiFiSelection()
} label: {
Text("Wifi")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_pink"))
.clipShape(Capsule())
.padding(5)
}
Button {
rootViewModel.goTobluetoothPairing()
} label: {
Text("Bluetooth")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("bluetooth_button_color"))
.clipShape(Capsule())
.padding(5)
}
Button {
rootViewModel.backToMain()
} label: {
Text("I Don't Know")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.padding(.horizontal, 60)
.minimumScaleFactor(0.1)
.frame(width: 270, height: 50, alignment: .center)
.background(Color.gray)
.clipShape(Capsule())
.padding(5)
}
Button {
rootViewModel.goTobluetoothPairing()
} label: {
Text("Reconnect to a device")
.font(Font.custom("ReadexPro-Regular", size: 25))
.frame(width: 270, height: 50, alignment: .center)
.foregroundColor(.blue)
}
}
}
.padding(.bottom, 60)
}
}
struct SelectionView_Previews: PreviewProvider {
static var previews: some View {
SelectionView()
}
}

View file

@ -1,182 +0,0 @@
//
// WifiSelection.swift
// PyLeap
//
// Created by Trevor Beaton on 11/7/22.
//
import SwiftUI
struct WifiSelection: View {
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = WifiViewModel()
let userDefaults = UserDefaults.standard
private let kPrefix = Bundle.main.bundleIdentifier!
func toggleViewModelIP() {
viewModel.isInvalidIP.toggle()
}
func storeResolvedAddress(service: ResolvedService) {
print("Storing resolved address")
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
userDefaults.set(service.device, forKey: kPrefix+".storeResolvedAddress.device" )
print("Stored UserDefaults")
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
}
func showConfirmationPrompt(service: ResolvedService, hostName: String) {
comfirmationAlertMessage(title: "Would you like to connect to \(hostName)?", exitTitle: "Cancel", primaryTitle: "Connect") {
storeResolvedAddress(service: service)
viewModel.printStoredInfo()
rootViewModel.goToWifiView()
} cancel: {
}
}
func showValidationPrompt() {
alertTF(title: "Enter Device IP Address",
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
hintText: "IP Address...",
primaryTitle: "Done",
secondaryTitle: "Cancel") { text in
viewModel.checkServices(ip: text)
} secondaryAction: {
print("Cancel")
}
}
func showAlertMessage() {
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
showValidationPrompt()
}
}
var body: some View {
VStack {
HStack {
Button {
rootViewModel.goToSelection()
} label: {
Image(systemName: "arrow.backward")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.foregroundColor(.black)
}
.padding(.leading, 30)
Spacer()
}
.padding(.top, 15)
Image("pyleapLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.top, 100)
.padding(.horizontal, 60)
Text("How do you want to connect to your WiFi enabled device?")
.font(Font.custom("ReadexPro-Regular", size: 36))
.minimumScaleFactor(0.01)
.multilineTextAlignment(.center)
.padding(.top, 100)
.padding(.horizontal, 30)
Spacer()
VStack {
Button {
rootViewModel.goToWiFiServiceSelection()
} label: {
Text("Scan for a Device")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_pink"))
.clipShape(Capsule())
.padding(5)
}
Button {
showValidationPrompt()
} label: {
Text("Manually Connect")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_purple"))
.clipShape(Capsule())
.padding(5)
}
Link("Learn More", destination: URL(string: "https://learn.adafruit.com/pyleap-app")!)
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.minimumScaleFactor(0.1)
.frame(width: 270, height: 50, alignment: .center)
.background(Color.gray)
.clipShape(Capsule())
.padding(5)
Button {
rootViewModel.goTobluetoothPairing()
} label: {
Text("Reconnect to a device")
.font(Font.custom("ReadexPro-Regular", size: 25))
.frame(width: 270, height: 50, alignment: .center)
.foregroundColor(.blue)
}
}
}
.padding(.bottom, 60)
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
if newValue {
rootViewModel.goToWifiView()
viewModel.ipInputValidation.toggle()
}
})
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
print("viewModel.isInvalidIP .onChange")
if newValue {
showAlertMessage()
toggleViewModelIP()
}
})
}
}
struct WifiSelection_Previews: PreviewProvider {
static var previews: some View {
WifiSelection()
}
}

View file

@ -1,175 +0,0 @@
//
// BLESettingsView.swift
// PyLeap
//
// Created by Trevor Beaton on 12/15/22.
//
import SwiftUI
struct BLESettingsView: View {
@State private var thirdPartyLink: String = ""
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = SettingsViewModel()
@ObservedObject var networkModel = NetworkService()
func showInvalidURLEntryAlert() {
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
}
}
func showDownloadConfirmationAlert() {
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
}
}
func showDisconnectionPrompt() {
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
viewModel.clearKnownIPAddress()
rootViewModel.goToWifiView()
} cancel: {
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Add Custom Project")
.font(Font.custom("ReadexPro-SemiBold", size: 24))
.padding([.horizontal, .top])
Text("""
Please enter the URL link to your own project that you would like to add to the current list of projects in PyLeap.
""")
.font(Font.custom("ReadexPro-Regular", size: 18))
.font(.callout)
.padding()
TextField("https://", text: $thirdPartyLink)
.background(Color.white)
.cornerRadius(5)
.keyboardType(.URL)
.textContentType(.URL)
.onSubmit {
networkModel.fetchThirdPartyProject(urlString: thirdPartyLink)
thirdPartyLink = ""
}
.padding(.horizontal)
Form {
Section{
Label("[Go to GitHub](https://github.com/adafruit/pyleap.github.io)", systemImage: "link")
}
header: {
Text("""
Find more information on adding your own project here:
""")
}
Section{
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
}
.font(.system(size: 16, weight: .semibold))
}
.onChange(of: viewModel.invalidURL, perform: { newValue in
showInvalidURLEntryAlert()
viewModel.invalidURL = false
})
.onChange(of: viewModel.confirmDownload, perform: { newValue in
showDownloadConfirmationAlert()
viewModel.confirmDownload = false
})
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
.foregroundColor(.blue)
}
.padding(12)
}
}
}
.background(Color(UIColor.systemGroupedBackground))
.safeAreaInset(edge: .top) {
VStack {
HStack {
Button {
rootViewModel.goToFileTransfer()
} label: {
Text("Back")
}
.padding(.leading, 20)
Spacer()
// switch appState {
//
// case .ble:
// Button {
// rootViewModel.goToFileTransfer()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .wifi:
// Button {
// rootViewModel.goToWifiView()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .none:
// Button {
// rootViewModel.goToMainSelection()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
// }
}
.frame(height: UIScreen.main.bounds.height / 19)
.background(Color(.systemGroupedBackground))
HStack {
Text("Settings")
.font(Font.custom("ReadexPro-Bold", size: 32))
.padding(.leading,20)
Spacer()
}
}
.padding(.top, 25)
}
}
}
struct BLESettingsView_Previews: PreviewProvider {
static var previews: some View {
BLESettingsView()
}
}

View file

@ -1,79 +0,0 @@
//
// BLESettingsViewModel.swift
// PyLeap
//
// Created by Trevor Beaton on 12/15/22.
//
import Foundation
import SwiftUI
class BLESettingsViewModel: ObservableObject {
private let kPrefix = Bundle.main.bundleIdentifier!
let userDefaults = UserDefaults.standard
@Published var hostName = ""
@Published var device = ""
@Published var ipAddress = ""
var connectedToDevice = false
@Published var invalidURL = false
@Published var confirmDownload = false
init() {
check()
registerNotifications(enabled: true)
}
private weak var errorObserver: NSObjectProtocol?
private weak var confirmDownloadObserver: NSObjectProtocol?
private weak var invalidIPObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
errorObserver = notificationCenter.addObserver(forName: .invalidCustomNetworkRequest, object: nil, queue: .main, using: {[weak self] _ in self?.showError()})
confirmDownloadObserver = notificationCenter.addObserver(forName: .didCollectCustomProject, object: nil, queue: .main, using: {[weak self] _ in self?.showConfirmationAlert()})
} else {
if let testObserver = errorObserver {notificationCenter.removeObserver(testObserver)}
}
}
func showError() {
invalidURL = true
}
func showConfirmationAlert() {
confirmDownload = true
}
func check() {
print(#function)
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil {
connectedToDevice = false
} else {
connectedToDevice = true
ipAddress = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") as! String
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
device = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device") as! String
}
}
func clearKnownIPAddress() {
userDefaults.set(nil, forKey: kPrefix+".storedIP")
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.hostName" )
userDefaults.set(nil, forKey: kPrefix+".storeResolvedAddress.device" )
}
}

View file

@ -1,243 +0,0 @@
//
// SettingsView.swift
// PyLeap
//
// Created by Trevor Beaton on 9/7/22.
//
import SwiftUI
enum SettingState {
case ble
case wifi
case none
}
struct SettingsView: View {
// @State public var appState: SettingState = .none
@State private var thirdPartyLink: String = ""
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = SettingsViewModel()
@ObservedObject var networkModel = NetworkService()
func showInvalidURLEntryAlert() {
alertMessage(title: "Invalid URL entry", exitTitle: "Ok") {
}
}
func showDownloadConfirmationAlert() {
alertMessage(title: "Added to Project List", exitTitle: "Ok") {
}
}
func showDisconnectionPrompt() {
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
viewModel.clearKnownIPAddress()
rootViewModel.goToWifiView()
} cancel: {
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Add Custom Project")
.font(Font.custom("ReadexPro-SemiBold", size: 24))
.padding([.horizontal, .top])
Text("""
Please enter the URL link to your own project that you would like to add to the current list of projects in PyLeap.
""")
.font(Font.custom("ReadexPro-Regular", size: 18))
.font(.callout)
.padding()
TextField("https://", text: $thirdPartyLink)
.background(Color.white)
.cornerRadius(5)
.keyboardType(.URL)
.textContentType(.URL)
.onSubmit {
networkModel.fetchThirdPartyProject(urlString: thirdPartyLink)
thirdPartyLink = ""
}
.padding(.horizontal)
Form {
Section{
Label("[Go to GitHub](https://github.com/adafruit/pyleap.github.io)", systemImage: "link")
}
header: {
Text("""
Find more information on adding your own project here:
""")
}
Section{
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
}
.font(.system(size: 16, weight: .semibold))
}
.onChange(of: viewModel.invalidURL, perform: { newValue in
showInvalidURLEntryAlert()
viewModel.invalidURL = false
})
.onChange(of: viewModel.confirmDownload, perform: { newValue in
showDownloadConfirmationAlert()
viewModel.confirmDownload = false
})
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
.foregroundColor(.blue)
}
.padding(12)
}
}
}
.background(Color(UIColor.systemGroupedBackground))
.safeAreaInset(edge: .top) {
VStack {
HStack {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
}
.padding(.leading, 20)
Spacer()
// switch appState {
//
// case .ble:
// Button {
// rootViewModel.goToFileTransfer()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .wifi:
// Button {
// rootViewModel.goToWifiView()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .none:
// Button {
// rootViewModel.goToMainSelection()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
// }
}
.frame(height: UIScreen.main.bounds.height / 19)
.background(Color(.systemGroupedBackground))
HStack {
Text("Settings")
.font(Font.custom("ReadexPro-Bold", size: 32))
.padding(.leading,20)
Spacer()
}
}
.padding(.top, 25)
}
}
}
extension View {
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(.init(title: primaryTitle, style: .destructive, handler: { _ in
disconnect()
}))
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}
func alertMessage(title: String, exitTitle: String, cancel: @escaping()->()){
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(.init(title: exitTitle, style: .cancel, handler: { _ in
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}
func alertTF(title: String, message: String, hintText: String, primaryTitle: String, secondaryTitle: String, primaryAction: @escaping(String)->(), secondaryAction: @escaping()->()) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addTextField { field in
field.keyboardType = .decimalPad
field.placeholder = hintText
}
alert.addAction(.init(title: secondaryTitle, style: .cancel, handler: { _ in
secondaryAction()
}))
alert.addAction(.init(title: primaryTitle, style: .default, handler: { _ in
if let text = alert.textFields?[0].text {
primaryAction(text)
} else {
primaryAction("")
}
}))
rootController().present(alert, animated: true, completion: nil)
}
func rootController()->UIViewController{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}

View file

@ -8,37 +8,22 @@
import SwiftUI import SwiftUI
import Foundation import Foundation
class ExpandedState: ObservableObject {
@Published var currentCell = ""
}
struct WifiCell: View { struct WifiCell: View {
@EnvironmentObject var expandedState : ExpandedState
let result : ResultItem let result : ResultItem
@State var isExpanded: Bool = false { @State private var isExpanded: Bool = false {
didSet { didSet {
onViewGeometryChanged() onViewGeometryChanged()
} }
} }
@State var isExpandedTest: String = ""
@ObservedObject var viewModel = WifiCellViewModel()
@Binding var isConnected: Bool @Binding var isConnected: Bool
@Binding var bootOne: String @Binding var bootOne: String
@Binding var stateBinder: DownloadState @Binding var stateBinder: DownloadState
var showRunItButton = false var showRunItButton = false
var projectName = String()
let onViewGeometryChanged: ()->Void let onViewGeometryChanged: ()->Void
var body: some View { var body: some View {
@ -46,53 +31,48 @@ struct WifiCell: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
var header: some View { private var content: some View {
HStack {
Text(result.projectName)
.font(Font.custom("ReadexPro-Regular", size: 24))
.padding(8)
.foregroundColor(.white)
Spacer()
Image(systemName: "chevron.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 15, alignment: .center)
.foregroundColor(.white)
.padding(.trailing, 30)
}
.padding(.vertical, 5)
.padding(.leading)
.frame(maxWidth: .infinity)
.background(Color("pyleap_purple"))
.onTapGesture {
expandedState.currentCell = result.bundleLink
}
}
var content: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
header header
if isExpanded { if isExpanded {
Group {
WifiSubViewCell(result: result, bindingString: $bootOne, downloadStateBinder: $stateBinder,isConnected: $isConnected)
Group {
WifiSubViewCell(bindingString: $bootOne, downloadStateBinder: $stateBinder, title: result.projectName,
image: result.projectImage,
description: result.description,
learnGuideLink: URLRequest(url: URL(string: result.learnGuideLink)!),
downloadLink: result.bundleLink,
compatibility: result.compatibility,
isConnected: $isConnected)
} }
} }
} }
} }
private var header: some View {
HStack {
Text(result.projectName)
.font(Font.custom("ReadexPro-Regular", size: 24))
.padding(8)
.foregroundColor(.white)
Spacer()
Image(systemName: "chevron.down")
.resizable()
.frame(width: 30, height: 15, alignment: .center)
.foregroundColor(.white)
.padding(.trailing, 30)
}
.padding(.vertical, 5)
.padding(.leading)
.frame(maxWidth: .infinity)
.background(Color("pyleap_purple"))
.onTapGesture { isExpanded.toggle() }
}
} }

View file

@ -1,27 +0,0 @@
//
// WifiCellViewModel.swift
// PyLeap
//
// Created by Trevor Beaton on 12/2/22.
//
import Foundation
import SwiftUI
class WifiCellViewModel: ObservableObject {
@Published var isExpanded : Bool = false
init() {
print("Initialized")
print("\(#function) @Line: \(#line)")
}
deinit {
print("Deinitialized")
print("\(#function) @Line: \(#line)")
}
@Published var projectBundle = ""
}

File diff suppressed because it is too large Load diff

View file

@ -1,163 +0,0 @@
//
// WifiPairingView.swift
// PyLeap
//
// Created by Trevor Beaton on 11/8/22.
//
import SwiftUI
import WebKit
struct SwiftUIWebView: UIViewRepresentable {
typealias UIViewType = WKWebView
let webAddress: String
let webView: WKWebView
init(webAddress: String) {
self.webAddress = webAddress
webView = WKWebView(frame: .zero)
webView.load(URLRequest(url: URL(string: webAddress)!))
}
func makeUIView(context: Context) -> WKWebView {
webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct WifiPairingView: View {
@EnvironmentObject var rootViewModel: RootViewModel
@State private var showModal = false
@State private var showProgress = false
@State private var userWaitThreshold = false
@State private var nextText = 0
@State var showSheetView = false
@State var showConnectionErrorView = false
let tutorialAddress = "https://learn.adafruit.com/pyleap-app/wifi-pairing"
var body: some View {
VStack {
HStack {
Button {
self.rootViewModel.goToWiFiSelection()
} label: {
Image(systemName: "arrow.backward")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.foregroundColor(Color("pyleap_gray"))
}
Spacer()
Image(systemName: "wifi")
.resizable()
.foregroundColor(.clear)
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(y: -5)
}
.padding(.top, 50)
.padding(.horizontal, 30)
//
//
//
// Image("pyleapLogo")
// .resizable()
// .aspectRatio(contentMode: .fit)
// .padding(.top, 50)
// .padding(.horizontal, 60)
//
//
// if nextText == 0 {
//
//
//
// Text("""
// PyLeaps WiFi mode requires EPS32 devices to have WiFi credentials in an ./env file.
//
// If youre 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()
}
}

View file

@ -8,8 +8,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
struct ResolvedService: Identifiable, Equatable { struct ResolvedService {
var id = UUID()
var ipAddress: String var ipAddress: String
var hostName: String var hostName: String
var device: String var device: String
@ -38,58 +37,36 @@ class WifiServiceManager: NSObject, ObservableObject {
override init() { override init() {
super.init() super.init()
print("Wifi Module Used")
serviceManagerBrowser.delegate = self serviceManagerBrowser.delegate = self
findService() findService()
} }
deinit {
print("Wifi Module Removed")
self.serviceManagerBrowser.stop()
}
func findService() { func findService() {
print("Start Scan")
// self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
resolvedServices.removeAll()
print("Current state of isSearching: \(isSearching)") print("Current state of isSearching: \(isSearching)")
if isSearching == false { if isSearching == false {
print("Start Scanning")
startDiscovery() startDiscovery()
} }
} }
func startDiscovery() { func startDiscovery() {
print("Start Discovery") // connectionStatus = .connected
DispatchQueue.main.async { print("Start Scan")
self.isSearching = true isSearching = true
}
print("\(#function) @Line: \(#line)")
print("Current state of isSearching: \(isSearching)")
self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain) self.serviceManagerBrowser.searchForServices(ofType: CircuitPythonType.serviceType, inDomain: CircuitPythonType.serviceDomain)
let timer = Timer.scheduledTimer(withTimeInterval: 12, repeats: false) { timer in
self.stopDiscoveryScan()
}
} }
func stopDiscoveryScan() { func stopDiscoveryScan() {
if isSearching { if isSearching {
isSearching = false isSearching = false
self.serviceManagerBrowser.stop() self.serviceManagerBrowser.stop()
print(resolvedServices)
} }
} }
} }
extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate { extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
@ -111,26 +88,11 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
print("didFindDomain") print("didFindDomain")
} }
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
print("netServiceBrowserDidStopSearch")
}
func netServiceWillResolve(_ sender: NetService) {
print("netServiceWillResolve")
}
func netServiceBrowser(_ browser: NetServiceBrowser, didRemoveDomain domainString: String, moreComing: Bool) {
"didRemoveDomain"
}
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
print(#function) print(#function)
discoveredService = service discoveredService = service
discoveredService?.delegate = self discoveredService?.delegate = self
discoveredService?.resolve(withTimeout: 7) discoveredService?.resolve(withTimeout: 10)
if services.contains(service) { if services.contains(service) {
print("All ready in service array") print("All ready in service array")
@ -139,22 +101,13 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
services.append(service) services.append(service)
} }
if moreComing == false {
browser.stop()
if !moreComing {
serviceManagerBrowser.stop()
isSearching = false
} }
print("Service: \(service)")
print("Service count: \(services.count)") print("Service count: \(services.count)")
self.serviceManagerBrowser.remove(from: .main, forMode: .common)
} }
func netServiceDidStop(_ sender: NetService) { func netServiceDidStop(_ sender: NetService) {
isSearching = false isSearching = false
print("isSearching: \(isSearching)") print("isSearching: \(isSearching)")
@ -183,16 +136,7 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name) let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
resolvedServices.append(resolvedService)
if resolvedServices.contains(where: {$0.ipAddress == resolvedService.ipAddress}) {
// it exists, do nothing
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) exists in network")
} else {
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) Added to Network List")
resolvedServices.append(resolvedService)
}
print("resolvedServices count: \(resolvedServices.count)") print("resolvedServices count: \(resolvedServices.count)")
} }
@ -204,7 +148,17 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
print("didRemove")
//self.discovered.removeAll { $0.name == service.name }
} }
// func netServiceDidResolveAddress(_ sender: NetService) {
// print("netServiceDidResolveAddress")
// if let data = sender.txtRecordData() {
// let dict = NetService.dictionary(fromTXTRecord: data)
// /// do stuff with txtRecord dict here and then add to discovered array.
// // discoveredService = nil
// }
// }
} }

View file

@ -1,293 +0,0 @@
//
// WifiServiceSelectionView.swift
// PyLeap
//
// Created by Trevor Beaton on 10/24/22.
//
import SwiftUI
struct WifiServiceSelectionView: View {
@ObservedObject var wifiServiceViewModel = WifiServiceManager()
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = WifiViewModel()
let userDefaults = UserDefaults.standard
private let kPrefix = Bundle.main.bundleIdentifier!
@State private var scrollViewID = UUID()
// For Blinka spinning animation
@State private var isAnimating = false
var foreverAnimation: Animation {
Animation.linear(duration: 2.0)
.repeatForever(autoreverses: false)
}
func storeResolvedAddress(service: ResolvedService) {
print("Storing resolved address")
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
userDefaults.set(service.hostName, forKey: kPrefix+".storeResolvedAddress.hostName" )
userDefaults.set(service.device, forKey: kPrefix+".storeResolvedAddress.device" )
print("Stored UserDefaults")
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
}
func showConfirmationPrompt(service: ResolvedService, hostName: String) {
comfirmationAlertMessage(title: "Would you like to connect to \(hostName)?", exitTitle: "Cancel", primaryTitle: "Connect") {
storeResolvedAddress(service: service)
viewModel.printStoredInfo()
rootViewModel.goToWifiView()
} cancel: {
}
}
func showValidationPrompt() {
alertTF(title: "Enter Device IP Address",
message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
hintText: "IP Address...",
primaryTitle: "Done",
secondaryTitle: "Cancel") { text in
viewModel.checkServices(ip: text)
} secondaryAction: {
print("Cancel")
}
}
func showAlertMessage() {
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
showValidationPrompt()
}
}
func toggleViewModelIP() {
viewModel.isInvalidIP.toggle()
}
var body: some View {
VStack {
HStack {
Button {
rootViewModel.goToSelection()
} label: {
Image(systemName: "arrow.backward")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.foregroundColor(.black)
}
.padding(.leading, 30)
Spacer()
}
.padding(.top, 15)
Image("pyleapLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.minimumScaleFactor(0.1)
.padding(.top, 50)
.padding(.horizontal, 60)
if wifiServiceViewModel.isSearching && wifiServiceViewModel.resolvedServices.isEmpty {
Text("WiFi Connect")
.font(Font.custom("ReadexPro-Regular", size: 36))
.multilineTextAlignment(.center)
.lineLimit(1)
.padding()
Spacer()
BlinkaAnimationView(height: 150, width: 145)
.padding(.bottom, 20)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0))
.onAppear() {
Animation.linear(duration: 1.0)
.repeatForever(autoreverses: false)
isAnimating = true
}
VStack {
Text("""
Scanning for PyLeap
compatible devices...
""")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.minimumScaleFactor(0.1)
.lineLimit(2)
Button {
rootViewModel.goToWifiPairingTutorial()
} label: {
Text("Pairing Tutorial")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.minimumScaleFactor(0.1)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_pink"))
.clipShape(Capsule())
.padding(5)
}
}
.padding(.horizontal, 30)
.padding(.bottom, 60)
}
else {
Spacer()
VStack {
if !wifiServiceViewModel.resolvedServices.isEmpty {
Text("WiFi Devices Found")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.lineLimit(1)
.minimumScaleFactor(0.1)
} else {
Text("No WiFi Devices Found")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.lineLimit(1)
.minimumScaleFactor(0.1)
}
Button {
} label: {
HStack {
Image(systemName: "arrow.clockwise")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25, alignment: .center)
.foregroundColor(.white)
Button {
wifiServiceViewModel.findService()
} label: {
Text("Rescan")
}
}
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(width: 146, height: 50, alignment: .center)
.background(Color("pyleap_purple"))
.clipShape(Capsule())
}
if wifiServiceViewModel.isSearching {
ProgressView()
}
}
.padding(.vertical, 40)
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scroll in
ForEach(wifiServiceViewModel.resolvedServices) { service in
WifiServiceCellView(resolvedService: service, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(service.id)
}
})
.onTapGesture {
// Save Cred to User Defaults
showConfirmationPrompt(service: service, hostName: service.hostName)
}
}
}
.id(self.scrollViewID)
}
.foregroundColor(.black)
}
if wifiServiceViewModel.resolvedServices.isEmpty && !wifiServiceViewModel.isSearching {
VStack {
Text("""
Unable to find any WiFi
compatible Adafruit devices
on your network
""")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.minimumScaleFactor(0.1)
.lineLimit(3)
Button {
rootViewModel.goToWifiPairingTutorial()
} label: {
Text("Pairing Tutorial")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.minimumScaleFactor(0.1)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_pink"))
.clipShape(Capsule())
.padding(5)
}
}
.padding(.horizontal, 30)
}
}
.padding(.bottom, 60)
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
if newValue {
rootViewModel.goToWifiView()
viewModel.ipInputValidation.toggle()
}
})
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
print("viewModel.isInvalidIP .onChange")
if newValue {
showAlertMessage()
toggleViewModelIP()
}
})
}
}
struct WifiServiceSelectionView_Previews: PreviewProvider {
static var previews: some View {
WifiServiceSelectionView()
}
}

View file

@ -12,39 +12,14 @@ struct WifiStatusConnectedView: View {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
private let kPrefix = Bundle.main.bundleIdentifier! private let kPrefix = Bundle.main.bundleIdentifier!
@EnvironmentObject var rootViewModel: RootViewModel
@Binding var hostName: String @Binding var hostName: String
func showConfirmationPrompt() {
comfirmationAlertMessage(title: "Are you sure you want to disconnect?", exitTitle: "Cancel", primaryTitle: "Disconnect") {
rootViewModel.goToSelection()
} cancel: {
}
}
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 0, content: { HStack(alignment: .center, spacing: 8, content: {
Image(systemName: "wifi")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(5)
Text("Connected to \(hostName). ")
.font(Font.custom("ReadexPro-Regular", size: 14))
Button {
showConfirmationPrompt()
} label: {
Text("Disconnect")
.font(Font.custom("ReadexPro-Bold", size: 14))
.underline()
.minimumScaleFactor(0.1)
}
Text("Connected To \(hostName)")
.font(Font.custom("ReadexPro-Regular", size: 16))
}) })
.padding(.all, 0.0) .padding(.all, 0.0)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -59,7 +34,7 @@ struct WifiStatusNoConnectionView: View {
HStack(alignment: .center, spacing: 8, content: { HStack(alignment: .center, spacing: 8, content: {
Text("No Device Detected") Text("No Device Detected")
.font(Font.custom("ReadexPro-SemiBold", size: 14)) .font(Font.custom("ReadexPro-Regular", size: 16))
}) })
.padding(.all, 0.0) .padding(.all, 0.0)
@ -75,7 +50,7 @@ struct WifiStatusConnectingView: View {
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 8, content: { HStack(alignment: .center, spacing: 8, content: {
Text("Searching for Adafruit Devices...") Text("Searching for Adafruit Devices...")
.font(Font.custom("ReadexPro-Regular", size: 14)) .font(Font.custom("ReadexPro-Regular", size: 16))
}) })
.padding(.all, 0.0) .padding(.all, 0.0)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -93,7 +68,7 @@ struct NetworkConnectionBanner: View {
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 8, content: { HStack(alignment: .center, spacing: 8, content: {
Text("Searching local network...") Text("Searching local network...")
.font(Font.custom("ReadexPro-Regular", size: 14)) .font(Font.custom("ReadexPro-Regular", size: 16))
// ProgressView() // ProgressView()
//.resizable() //.resizable()

View file

@ -8,14 +8,14 @@
import SwiftUI import SwiftUI
struct WifiSubViewCell: View { struct WifiSubViewCell: View {
@State var transferInProgress = false @State var transferInProgress = false
@State var isDownloaded = false @State var isDownloaded = false
@EnvironmentObject var globalString : GlobalString
@StateObject var wifiFileTransfer = WifiFileTransfer() @StateObject var wifiFileTransfer = WifiFileTransfer()
@StateObject var wifiTransferService = WifiTransferService()
let result : ResultItem
@Binding var bindingString: String @Binding var bindingString: String
@ -23,17 +23,18 @@ struct WifiSubViewCell: View {
@State private var toggleView: Bool = false @State private var toggleView: Bool = false
let title: String
let image: String
let description: String
let learnGuideLink: URLRequest
let downloadLink: String
let compatibility: [String]
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
@StateObject var downloadModel = DownloadViewModel() @StateObject var downloadModel = DownloadViewModel()
@ObservedObject var viewModel = WifiSubViewCellModel()
@Binding var isConnected : Bool @Binding var isConnected : Bool
@State private var counter = 0
@State private var numOfFiles = 0
@State var downloadState: DownloadState = .idle
@State private var showWebViewPopover: Bool = false @State private var showWebViewPopover: Bool = false
@State var errorOccured = false @State var errorOccured = false
@State private var presentAlert = false @State private var presentAlert = false
@ -41,100 +42,6 @@ struct WifiSubViewCell: View {
@State var offlineWithoutProject = false @State var offlineWithoutProject = false
func showTransferErrorMessage() {
alertMessage(title: """
Download Error
Unable to download project
Try again later
""", exitTitle: "Retry") {
wifiFileTransfer.transferError = false
}
}
func usbInUseErrorMessage() {
alertMessage(title: """
USB In Use
Files cannot be tranferred or moved while USB is in use.
Remove device from USB. Press "Reset" on the device.
""", exitTitle: "Retry") {
// wifiFileTransfer.transferError = false
}
}
func startTransferProcess() {
if isDownloaded {
print("Project found")
wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
} else {
print("Project not found")
downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
}
}
func testOperation() {
let operationQueue = OperationQueue()
let operation1 = BlockOperation {
wifiTransferService.optionRequest(handler: { results in
switch results {
case .success(let contents):
if contents.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
} else {
print("Connected to USB")
DispatchQueue.main.async {
usbInUseErrorMessage()
wifiFileTransfer.stopTransfer = true
}
}
case .failure:
print("Failure")
}
})
}
let operation2 = BlockOperation {
if isDownloaded {
print("Project found")
wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
} else {
print("Project not found")
downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
}
}
// Add operations to the operation queue
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
// Block the current thread until all operations have finished executing
operationQueue.waitUntilAllOperationsAreFinished()
}
var body: some View { var body: some View {
@ -142,14 +49,14 @@ Remove device from USB. Press "Reset" on the device.
VStack(alignment: .leading, spacing: 0, content: { VStack(alignment: .leading, spacing: 0, content: {
ImageWithURL(result.projectImage) ImageWithURL(image)
.scaledToFit() .scaledToFit()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.cornerRadius(14) .cornerRadius(14)
.padding(.top, 30) .padding(.top, 30)
Text(result.description) Text(description)
.font(Font.custom("ReadexPro-Regular", size: 18)) .font(Font.custom("ReadexPro-Regular", size: 18))
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.minimumScaleFactor(0.1) .minimumScaleFactor(0.1)
@ -158,19 +65,8 @@ Remove device from USB. Press "Reset" on the device.
.font(Font.custom("ReadexPro-Bold", size: 18)) .font(Font.custom("ReadexPro-Bold", size: 18))
.padding(.top, 5) .padding(.top, 5)
HStack {
Image(systemName: "checkmark")
.resizable()
.frame(width: 25, height: 22, alignment: .center)
.foregroundColor(.green)
Text("ESP32-S2")
.font(Font.custom("ReadexPro-Regular", size: 18))
.foregroundColor(.black)
}
.padding(.top, 10)
ForEach(compatibility, id: \.self) { string in
ForEach(result.compatibility, id: \.self) { string in
if string == "circuitplayground_bluefruit" { if string == "circuitplayground_bluefruit" {
HStack { HStack {
@ -213,7 +109,7 @@ Remove device from USB. Press "Reset" on the device.
.padding(.top, 20) .padding(.top, 20)
} }
.sheet(isPresented: $showWebViewPopover, content: { .sheet(isPresented: $showWebViewPopover, content: {
SwiftUIWebView(webAddress: result.learnGuideLink) WebView(URLRequest(url: learnGuideLink.url!))
}) })
@ -221,16 +117,18 @@ Remove device from USB. Press "Reset" on the device.
if isConnected { if isConnected {
if result.compatibility.contains(bindingString) { if compatibility.contains(bindingString) {
if wifiFileTransfer.testIndex.downloadState == .idle {
Button { Button {
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse) print("Wifi Project Attempt \(title)")
testOperation() if wifiFileTransfer.projectDownloaded {
wifiFileTransfer.projectValidation(nameOf: title)
} else {
downloadModel.startDownload(urlString: downloadLink, projectTitle: title) {
print("DONE")
}
}
} label: { } label: {
RunItButton() RunItButton()
@ -239,41 +137,9 @@ Remove device from USB. Press "Reset" on the device.
} }
if wifiFileTransfer.testIndex.downloadState == .transferring {
DownloadingButton()
.padding(.top, 20)
.disabled(true)
VStack(alignment: .center, spacing: 5) {
ProgressView("", value: CGFloat(counter), total: CGFloat(numOfFiles))
.padding(.horizontal, 90)
.padding(.top, -5)
.padding(.bottom, 10)
.accentColor(Color.gray)
.scaleEffect(x: 1, y: 2, anchor: .center)
.cornerRadius(10)
.frame(height: 10)
ProgressView()
}
}
if wifiFileTransfer.testIndex.downloadState == .complete {
CompleteButton()
.padding(.top, 20)
.disabled(true)
}
if wifiFileTransfer.testIndex.downloadState == .failed {
FailedButton()
.padding(.top, 20)
.disabled(true)
}
}
} else { } else {
Button { Button {
@ -285,87 +151,61 @@ Remove device from USB. Press "Reset" on the device.
} }
} }
Spacer() Spacer()
.frame(height: 30) .frame(height: 30)
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
.onAppear(perform: {
wifiFileTransfer.registerWifiNotification(enabled: true) .alert("Project Not Found", isPresented: $offlineWithoutProject) {
Button("OK") {
// Handle acknowledgement.
print("OK")
offlineWithoutProject = false
downloadStateBinder = .idle
// selectionModel.state = .idle
print("\(offlineWithoutProject)")
}
} message: {
Text("""
To use this project, connect to the internet.
""")
.multilineTextAlignment(.leading)
}
viewModel.searchPathForProject(nameOf: result.projectName) .onChange(of: downloadModel.isDownloading, perform: { newValue in
wifiFileTransfer.getProjectForSubClass(nameOf: title)
})
.onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
print("For project: \(title), project download is \(newValue)")
// globalString.projectString = title
if newValue {
DispatchQueue.main.async {
print("Getting project from Subclass \(title)")
// viewModel.getProjectForSubClass(nameOf: title)
wifiFileTransfer.projectValidation(nameOf: title)
if viewModel.projectDownloaded {
isDownloaded = true isDownloaded = true
} else {
isDownloaded = false
}
print("is downloaded? \(viewModel.projectDownloaded)")
})
.padding(.top, 8)
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
if newValue {
showTransferErrorMessage()
}
})
.onChange(of: viewModel.showUsbInUseError) { newValue in
if newValue {
usbInUseErrorMessage()
} }
}else {
print("Is not downloaded")
isDownloaded = false
} }
.onChange(of: wifiFileTransfer.counter) { newValue in })
print("New counter : \(newValue)") .onAppear(perform: {
counter = newValue wifiFileTransfer.getProjectForSubClass(nameOf: title)
} if wifiFileTransfer.projectDownloaded {
isDownloaded = true
.onChange(of: wifiFileTransfer.numOfFiles) { newValue in } else {
print("New numOfFiles : \(newValue)") isDownloaded = false
numOfFiles = newValue
}
.onChange(of: wifiFileTransfer.testIndex.count) { newValue in
print("New count index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.numberOfFiles) { newValue in
print("New numberOfFiles index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.downloadState) { newValue in
print("New download state : \(newValue)")
}
.onChange(of: wifiFileTransfer.downloadState) { newValue in
switch newValue {
case .idle:
downloadState = .idle
print("idle")
case .transferring:
downloadState = .transferring
print("transferring")
case .complete:
downloadState = .complete
print("complete")
case .downloading:
downloadState = .downloading
print("downloading")
case .failed:
downloadState = .failed
print("failed")
}
} }
print("is downloaded? \(isDownloaded)")
})
.padding(.top, 8)
} }
} }

View file

@ -6,119 +6,40 @@
// //
import Foundation import Foundation
import SwiftUI
class WifiSubViewCellModel: ObservableObject { class WifiSubViewCellModel: ObservableObject {
@ObservedObject var wifiTransferService = WifiTransferService()
@ObservedObject var wifiFileTransfer = WifiFileTransfer()
@Published var downloadState: DownloadState = .idle
@Published var projectDownloaded = false @Published var projectDownloaded = false
@Published var failedProjectLaunch = false @Published var failedProjectLaunch = false
@Published var usbInUse = false
@Published var showUsbInUseError = false
init() {
registerForUSBInUseErrorNotification(enabled: true)
}
private weak var usbInUseErrorNotification: NSObjectProtocol?
private func registerForUSBInUseErrorNotification(enabled: Bool) {
print("\(#function) @Line: \(#line)")
let notificationCenter = NotificationCenter.default
if enabled {
// NotificationCenter.default.addObserver(self, selector: #selector(zipSuccess(_:)), name: .usbInUseErrorNotification,object: nil)
usbInUseErrorNotification = notificationCenter.addObserver(forName: .usbInUseErrorNotification, object: nil, queue: .main, using: {[weak self] _ in self?.zipSuccess()})
} else {
}
}
func zipSuccess() {
showUsbInUseError = true
}
func checkIfUSBInUse() {
wifiTransferService.optionRequest(handler: { result in
switch result {
case .success:
print("Success")
self.wifiTransferService.getRequest(read: "boot_out.txt") { result in
print(result)
}
case .failure:
print("Failure")
}
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
//
// print("USB not in use.")
// DispatchQueue.main.async {
// self.usbInUse = false
// }
//
// // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// // if wifiFileTransfer.projectDownloaded {
// //
// // wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
// //
// // } else {
// // downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
// // }
// // }
//
// } else {
// DispatchQueue.main.async {
// self.usbInUse = true
// }
// print("USB in use - files cannot be tranferred or moved while USB is in use. Show Error")
// }
})
}
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func searchPathForProject(nameOf project: String) { func getProjectForSubClass(nameOf project: String) {
var manager = FileManager.default
let nestedFolderURL = directoryPath.appendingPathComponent(project) if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
// for case condition: Only process URLs
for case let fileURL as URL in enumerator {
if fileURL.lastPathComponent == project {
failedProjectLaunch = false
projectDownloaded = true
print(#function)
print("Searching for... \(project)")
print("URL Path: \(fileURL.path)")
print("URL : \(fileURL)")
return
} else {
failedProjectLaunch = true
projectDownloaded = false
print("Project was not found...")
}
}
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("\(project) - Exist")
projectDownloaded = true
} else {
print("Does not exist - \(project)")
projectDownloaded = false
} }
} }
} }

View file

@ -16,19 +16,19 @@ protocol WifiTransferServiceDelegate: AnyObject {
class WifiTransferService: ObservableObject { class WifiTransferService: ObservableObject {
// weak var delegate: WifiTransferServiceDelegate? weak var delegate: WifiTransferServiceDelegate?
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
private let kPrefix = Bundle.main.bundleIdentifier! private let kPrefix = Bundle.main.bundleIdentifier!
@Published var counter = 0
@Published var webDirectoryInfo = [WebDirectoryModel]() @Published var webDirectoryInfo = [WebDirectoryModel]()
@Published var hostName = "" @Published var hostName = ""
func startup() { func startup() {
print("\(#function) @Line: \(#line)") print(#function)
print("Startup")
if (userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName")) != nil { if (userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName")) != nil {
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String) print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String)
@ -43,7 +43,6 @@ class WifiTransferService: ObservableObject {
startup() startup()
} }
//*
func sendPutRequest(fileName: String, func sendPutRequest(fileName: String,
body: Data, body: Data,
then handler: @escaping(Result<Data, Error>) -> Void) { then handler: @escaping(Result<Data, Error>) -> Void) {
@ -77,88 +76,30 @@ class WifiTransferService: ObservableObject {
print("Print curl:") print("Print curl:")
print(request.cURL(pretty: true)) print(request.cURL(pretty: true))
let task = URLSession.shared.dataTask(with: request) { data, response, error in let task = urlSession.dataTask(
with: request,
completionHandler: { data, response, error in
// Validate response and call handler
if let error = error { if let error = error {
print("File write error") print("File write error")
handler(.failure(error)) handler(.failure(error))
}
if let data = data {
print("File write success!")
handler(.success(data))
}
} }
)
if let data = data {
print("File write success!")
handler(.success(data))
}
}
task.resume() task.resume()
} }
func requestWithCheck() {
}
func optionRequest(handler: @escaping(Result<String, Error>) -> Void) {
print("HOST | \(hostName)")
let username = ""
let password = "passw0rd"
let loginString = "\(username):\(password)"
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
return
}
let base64LoginString = loginData.base64EncodedString()
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = "OPTIONS"
//request.httpBody = try? JSONSerialization.data(withJSONObject: [:], options: [])
print("Print curl:")
print(request.cURL(pretty: true))
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
print("Specific header: \(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found")")
handler(.success(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found"))
}
if let error = error {
print("File write error")
handler(.failure(error))
}
guard let data = data else {
print(String(describing: "Error Found: \(String(describing: error))"))
print("Failed! Option Request")
return
}
if let str = String(data: data, encoding: .utf8) {
print("Output: \(str)")
}
}
task.resume()
}
func getRequest() { func getRequest() {
@ -175,11 +116,8 @@ class WifiTransferService: ObservableObject {
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity) // var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity) var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET" request.httpMethod = "GET"
print("Print curl:") print("Print curl:")
@ -209,13 +147,8 @@ class WifiTransferService: ObservableObject {
task.resume() task.resume()
} }
//completion: @escaping (Bool) -> Void func getRequest(incoming: String) -> String {
typealias CompletionHandler = (_ success:String) -> Void
func getRequest(read: String, completionHandler: @escaping CompletionHandler) {
print("Second network call made!")
var semaphore = DispatchSemaphore (value: 0) var semaphore = DispatchSemaphore (value: 0)
let username = "" let username = ""
@ -224,14 +157,13 @@ class WifiTransferService: ObservableObject {
var outgoingString = String() var outgoingString = String()
let loginData = loginString.data(using: String.Encoding.utf8) guard let loginData = loginString.data(using: String.Encoding.utf8) else {
return "Error"
}
let base64LoginString = loginData.base64EncodedString()
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
let base64LoginString = loginData!.base64EncodedString() var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(incoming)")!,timeoutInterval: Double.infinity)
print("Host Name: \(hostName)")
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(read)")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET" request.httpMethod = "GET"
@ -243,116 +175,17 @@ class WifiTransferService: ObservableObject {
let task = URLSession.shared.dataTask(with: request) { data, response, error in let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { guard let data = data else {
print(String(describing: "Error Found: \(error)")) print(String(describing: "Error Found: \(error)"))
return semaphore.signal()
}
do {
print("In do-catch loop of getRequest")
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
DispatchQueue.main.async {
self.webDirectoryInfo = wifiIncomingData
}
} catch {
print(error.localizedDescription)
}
if let str = String(data: data, encoding: .utf8) {
print("Out-going getRequest data: \(str)")
outgoingString = str
print("\(#function) @Line: \(#line)")
completionHandler(outgoingString)
} else {
print("\(#function) @Line: \(#line)")
print("Error")
}
}
task.resume()
}
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
typealias CompletionHandlerForCheck = (_ success: [WebDirectoryModel]) -> Void
func getRequestForFileCheck(read: String, completionHandler: @escaping CompletionHandlerForCheck){
print("Incoming Read String: \(read)")
var semaphore = DispatchSemaphore (value: 0)
let username = ""
let password = "passw0rd"
let loginString = "\(username):\(password)"
var outgoingString = String()
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData!.base64EncodedString()
print("Host Name: \(hostName)")
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/\(read)")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
print("GET Request: \(String(describing: request.url?.absoluteURL))\n")
print("Print curl:")
print(request.cURL(pretty: true))
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: "Error Found: \(error)"))
return return
} }
// print(String(data: data, encoding: .utf8)!) // print(String(data: data, encoding: .utf8)!)
semaphore.signal()
do { do {
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data) let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
DispatchQueue.main.async { DispatchQueue.main.async {
self.webDirectoryInfo = wifiIncomingData self.webDirectoryInfo = wifiIncomingData
completionHandler(self.webDirectoryInfo)
print("On completion \(self.webDirectoryInfo)")
} }
} catch { } catch {
print(error.localizedDescription) print(error.localizedDescription)
@ -361,21 +194,16 @@ class WifiTransferService: ObservableObject {
if let str = String(data: data, encoding: .utf8) { if let str = String(data: data, encoding: .utf8) {
print(str) print(str)
outgoingString = str outgoingString = str
print("\(#function) @Line: \(#line)")
} else {
print("\(#function) @Line: \(#line)")
print("Error")
} }
} }
task.resume() task.resume()
semaphore.wait()
return outgoingString
} }
// func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) { // func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
func putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) { func putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) {
print("Test Transfer")
let parameters = fileContent let parameters = fileContent
let postData = parameters let postData = parameters
@ -413,15 +241,14 @@ class WifiTransferService: ObservableObject {
completion(.success(data)) completion(.success(data))
} }
// print(String(data: data, encoding: .utf8)!)
} }
task.resume() task.resume()
} }
// Make // Make
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) { func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
print("\(#function) @Line: \(#line)")
let username = "" let username = ""
let password = "passw0rd" let password = "passw0rd"
@ -444,20 +271,12 @@ class WifiTransferService: ObservableObject {
let task = URLSession.shared.dataTask(with: request) { data, response, error in let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
if let error = error { if let error = error {
print("Write Directory Failure")
completion(.failure(error)) completion(.failure(error))
} }
if let data = data { if let data = data {
print("Write Directory Success")
completion(.success(data)) completion(.success(data))
} }
@ -482,7 +301,7 @@ class WifiTransferService: ObservableObject {
let base64LoginString = loginData.base64EncodedString() let base64LoginString = loginData.base64EncodedString()
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity) // var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/test.txt")!,timeoutInterval: Double.infinity) var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/testing.txt")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization") request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.addValue("text/plain", forHTTPHeaderField: "Content-Type") request.addValue("text/plain", forHTTPHeaderField: "Content-Type")

View file

@ -5,6 +5,7 @@
// Created by Trevor Beaton on 8/9/22. // Created by Trevor Beaton on 8/9/22.
// //
// My IP Address - 192.168.1.111
import SwiftUI import SwiftUI
import Combine import Combine
@ -13,10 +14,12 @@ struct WifiView: View {
@StateObject var viewModel = WifiViewModel() @StateObject var viewModel = WifiViewModel()
private let kPrefix = Bundle.main.bundleIdentifier! private let kPrefix = Bundle.main.bundleIdentifier!
@StateObject var wifiviewModel = WifiServiceManager()
// User Defaults // User Defaults
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
@ObservedObject var networkModel = NetworkService()
@EnvironmentObject var rootViewModel: RootViewModel @EnvironmentObject var rootViewModel: RootViewModel
@State private var downloadState = DownloadState.idle @State private var downloadState = DownloadState.idle
@ -25,58 +28,18 @@ struct WifiView: View {
@State private var boardBootInfo = "esp32-s2" @State private var boardBootInfo = "esp32-s2"
@State var hostName = "" @State var hostName = ""
@EnvironmentObject var test : ExpandedState
@State var falseTog = false
@State var trueTog = true
@State private var showPopover: Bool = false
func toggleViewModelIP() {
viewModel.isInvalidIP.toggle()
}
func scanNetworkWifi() {
viewModel.wifiServiceManager.findService()
}
func printArray(array: [Any]) {
for i in array {
print("\(i)")
}
}
func checkForStoredIPAddress() {
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
print("storeResolvedAddress - not stored")
} else {
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
viewModel.ipAddressStored = true
print("storeResolvedAddress - is stored")
viewModel.connectionStatus = .connected
}
}
func showValidationPrompt() { func showValidationPrompt() {
alertTF(title: "Enter Device IP Address", alertTF(title: "Enter Device IP Address",
message: "PyLeap will use this IP address to search for Adafruit devices on your local network", message: "PyLeap will use this IP address to search for Adafruit devices on your local network",
hintText: "IP Address...", hintText: "IP Address...",
primaryTitle: "Done", primaryTitle: "Done",
secondaryTitle: "Cancel") { text in secondaryTitle: "Cancel") { text in
viewModel.checkServices(ip: text) viewModel.checkServices(ip: text)
} secondaryAction: { } secondaryAction: {
print("Cancel") print("Cancel")
} }
} }
func showAlertMessage() { func showAlertMessage() {
alertMessage(title: "IP address Not Found", exitTitle: "Ok") { alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
@ -84,61 +47,106 @@ struct WifiView: View {
} }
} }
func initialIPStoreCheck() {
if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
print("No IP address found.")
showValidationPrompt()
} else {
viewModel.connectionStatus = .connected
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
}
}
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
WifiHeaderView() WifiHeaderView()
Group{ if wifiviewModel.isSearching {
switch viewModel.connectionStatus { NetworkConnectionBanner()
case .connected: } else {
WifiStatusConnectedView(hostName: $hostName)
case .noConnection:
WifiStatusNoConnectionView()
case .connecting:
WifiStatusConnectingView()
}
} }
ScrollView(.vertical, showsIndicators: false) { Group{
switch viewModel.connectionStatus {
case .connected:
WifiStatusConnectedView(hostName: $hostName)
case .noConnection:
WifiStatusNoConnectionView()
case .connecting:
WifiStatusConnectingView()
}
}
if !viewModel.ipAddressStored {
HStack(alignment: .center, content: {
Button {
showValidationPrompt()
} label: {
Text("Enter IP address")
.font(Font.custom("ReadexPro-Regular", size: 16))
.foregroundColor(.white)
.background(.indigo)
// .cornerRadius(15)
.padding(5)
}
Button {
wifiviewModel.findService()
} label: {
Text("Scan Network")
.foregroundColor(.white)
.background(.indigo)
// .cornerRadius(15)
.padding(5)
}
Button {
rootViewModel.goTobluetoothPairing()
} label: {
Text("BLE Mode")
.foregroundColor(.white)
.background(.indigo)
// .cornerRadius(15)
.padding(5)
}
})
.padding(.all, 0.0)
.frame(maxWidth: .infinity)
.frame(maxHeight: 40)
.background(Color.clear)
.foregroundColor(.black)
} else {
}
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scroll in ScrollViewReader { scroll in
SubHeaderView() SubHeaderView()
let check = networkModel.pdemos.filter {
let check = viewModel.pdemos.filter {
$0.compatibility.contains(boardBootInfo) $0.compatibility.contains(boardBootInfo)
} }
ForEach(check) { demo in ForEach(check) { demo in
if demo.bundleLink == test.currentCell { WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: { withAnimation {
scroll.scrollTo(demo.id)
})
.onAppear(){
withAnimation {
scroll.scrollTo(demo.id)
}
} }
})
} else {
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
withAnimation {
// scroll.scrollTo(demo.id)
}
})
}
} }
} }
@ -146,47 +154,36 @@ struct WifiView: View {
.id(self.scrollViewID) .id(self.scrollViewID)
} }
.foregroundColor(.black) .foregroundColor(.black)
.environmentObject(test)
} }
.onChange(of: viewModel.connectionStatus, perform: { newValue in .onChange(of: viewModel.connectionStatus, perform: { newValue in
if newValue == .connected { if newValue == .connected {
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
} }
}) })
.onChange(of: viewModel.wifiServiceManager.resolvedServices, perform: { newValue in
print("Credential Check!")
print(newValue)
if newValue.contains(where: { result in
result.hostName == userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
}) {
print("Matched")
} else {
print("Un-Matched")
}
})
.onChange(of: viewModel.isInvalidIP, perform: { newValue in .onChange(of: viewModel.isInvalidIP, perform: { newValue in
print("viewModel.isInvalidIP .onChange") print("viewModel.isInvalidIP .onChange")
if newValue { if newValue {
showAlertMessage() showAlertMessage()
toggleViewModelIP() viewModel.isInvalidIP.toggle()
} }
}) })
.onAppear(){ .onAppear(){
checkForStoredIPAddress() print("On Appear")
viewModel.printStoredInfo() networkModel.fetch()
viewModel.read()
// viewModel.checkStoredIP()
initialIPStoreCheck()
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
print("Nothing stored.")
} else {
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
}
} }
} }
@ -196,23 +193,13 @@ struct WifiView: View {
struct WifiView_Previews: PreviewProvider { struct WifiView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
WifiView() WifiView()
} }
} }
extension Notification.Name { extension Notification.Name {
private static let kPrefix = Bundle.main.bundleIdentifier! private static let kPrefix = Bundle.main.bundleIdentifier!
public static let testNotificationName = Notification.Name(kPrefix+".testNotificationName")
public static let didUpdateState = Notification.Name(kPrefix+".test") public static let didUpdateState = Notification.Name(kPrefix+".test")
public static let invalidIPNotif = Notification.Name(kPrefix+".invalidIPNotif") public static let invalidIPNotif = Notification.Name(kPrefix+".invalidIPNotif")
public static let invalidCustomNetworkRequest = Notification.Name(kPrefix+".invalidCustomNetworkRequest") public static let invalidCustomNetworkRequest = Notification.Name(kPrefix+".invalidCustomNetworkRequest")
public static let didCollectCustomProject = Notification.Name(kPrefix+".didCollectCustomProject") public static let didCollectCustomProject = Notification.Name(kPrefix+".didCollectCustomProject")
public static let didEncounterZipError = Notification.Name(kPrefix+".didEncounterZipError")
public static let didCompleteZip = Notification.Name(kPrefix+".didCompleteZip")
public static let wifiDownloadComplete = Notification.Name(kPrefix+".wifiDownloadComplete")
public static let didCompleteTransfer = Notification.Name(kPrefix+".didCompleteTransfer")
public static let didEncounterTransferError = Notification.Name(kPrefix+".didEncounterTransferError")
public static let downloadErrorDidOccur = Notification.Name(kPrefix+".downloadErrorDidOccur")
public static let usbInUseErrorNotification = Notification.Name(kPrefix+".usbInUseErrorNotification")
} }

View file

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import UIKit
import CoreLocation import CoreLocation
import Network import Network
@ -22,33 +23,21 @@ class WifiViewModel: ObservableObject {
private let kPrefix = Bundle.main.bundleIdentifier! private let kPrefix = Bundle.main.bundleIdentifier!
@Published var connectionStatus: ConnectionStatus = .noConnection @Published var connectionStatus: ConnectionStatus = .noConnection
@Published var isInvalidIP = false @Published var isInvalidIP = false
@Published var ipInputValidation = false
//Dependencies //Dependencies
var networkMonitor = NetworkMonitor() var networkMonitor = NetworkMonitor()
var networkAuth = LocalNetworkAuthorization() var networkAuth = LocalNetworkAuthorization()
public var wifiNetworkService = WifiNetworkService() public var wifiNetworkService = WifiNetworkService()
@Published var wifiTransferService = WifiTransferService() @Published var wifiTransferService = WifiTransferService()
@Published var wifiServiceManager = WifiServiceManager() @Published var wifiServiceManager = WifiServiceManager()
var circuitPythonVersion = Int()
@Published var webDirectoryInfo = [WebDirectoryModel]() @Published var webDirectoryInfo = [WebDirectoryModel]()
@Published var hostName = "" @Published var hostName = ""
@Published var downloadState: DownloadState = .idle
let dataStore = DataStore()
@Published var pdemos : [ResultItem] = []
// File Manager Data // File Manager Data
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
@Published var fileArray: [ContentFile] = [] @Published var fileArray: [ContentFile] = []
@ -56,47 +45,21 @@ class WifiViewModel: ObservableObject {
var projectDirectories: [URL] = [] var projectDirectories: [URL] = []
var returnedArray = [[String]]()
var ipAddressStored = false var ipAddressStored = false
init() { init() {
pdemos = dataStore.loadDefaultList()
checkIP() checkIP()
registerNotifications(enabled: true) registerNotifications(enabled: true)
wifiServiceManager.findService() wifiServiceManager.findService()
//read()
} }
/// Makes a network call to populate our project list
func fetch() {
// networkModel.fetch()
}
@Published var pyleapProjects = [ResultItem]()
func read() {
// This method can't be used until the device has permission to communicate.
print("READING CP Vers.")
wifiTransferService.getRequest(read: "boot_out.txt") { result in
if result.contains("CircuitPython 7") {
WifiCPVersion.versionNumber = 7
print("WifiCPVersion.versionNumber set to: \(WifiCPVersion.versionNumber)")
}
if result.contains("CircuitPython 8") {
WifiCPVersion.versionNumber = 8
print("WifiCPVersion.versionNumber set to: \(WifiCPVersion.versionNumber)")
}
}
}
private weak var invalidIPObserver: NSObjectProtocol?
private weak var testObserver: NSObjectProtocol? private weak var testObserver: NSObjectProtocol?
private weak var invalidIPObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) { private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default let notificationCenter = NotificationCenter.default
@ -113,9 +76,6 @@ class WifiViewModel: ObservableObject {
} }
func checkIP() { func checkIP() {
print("Tiggered checkIP") print("Tiggered checkIP")
@ -124,7 +84,9 @@ class WifiViewModel: ObservableObject {
ipAddressStored = true ipAddressStored = true
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
} else { } else {
ipAddressStored = false
print(userDefaults.object(forKey: kPrefix+".storedIP"))
ipAddressStored = false
} }
} }
@ -148,18 +110,16 @@ class WifiViewModel: ObservableObject {
// @Published var connectionStatus: ConnectionStatus = AppEnvironment.isRunningTests ? .connected : .noConnection // @Published var connectionStatus: ConnectionStatus = AppEnvironment.isRunningTests ? .connected : .noConnection
func printIPStorageAtLocation() {
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
func printStoredInfo() {
print("======Stored UserDefaults======")
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName"))
print(userDefaults.object(forKey: kPrefix+".storeResolvedAddress.device"))
} }
func storeIPAddress(ipAddress: String) {
userDefaults.set(ipAddress, forKey: kPrefix+".storedIP" )
printIPStorageAtLocation()
}
func storeResolvedAddress(service: ResolvedService) { func storeResolvedAddress(service: ResolvedService) {
userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" ) userDefaults.set(service.ipAddress, forKey: kPrefix+".storeResolvedAddress.ipAddress" )
@ -175,6 +135,7 @@ class WifiViewModel: ObservableObject {
func clearKnownIPAddress() { func clearKnownIPAddress() {
userDefaults.set(nil, forKey: kPrefix+".storedIP") userDefaults.set(nil, forKey: kPrefix+".storedIP")
printIPStorageAtLocation()
} }
@ -188,11 +149,14 @@ class WifiViewModel: ObservableObject {
let resolvedService = wifiServiceManager.resolvedServices.filter { $0.ipAddress == ip } let resolvedService = wifiServiceManager.resolvedServices.filter { $0.ipAddress == ip }
// To store in UserDefaults // To store in UserDefaults
storeResolvedAddress(service: resolvedService[0]) storeResolvedAddress(service: resolvedService[0])
storeIPAddress(ipAddress: ip)
connectionStatus = .connected connectionStatus = .connected
ipInputValidation = true
} else { } else {
isInvalidIP = true isInvalidIP = true
print("1 does not exists in the array") print("1 does not exists in the array")
@ -200,19 +164,16 @@ class WifiViewModel: ObservableObject {
} }
func checkStoredIP() { func checkStoredIP() {
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.ipAddress") == nil { if userDefaults.object(forKey: kPrefix+".storedIP") == nil {
print("Nothing stored.") print("Nothing stored.")
} else { } else {
NotificationCenter.default.post(name: .invalidIPNotif, object: nil, userInfo: nil) NotificationCenter.default.post(name: .invalidIPNotif, object: nil, userInfo: nil)
print("Stored: \(String(describing: userDefaults.object(forKey: kPrefix+".storedIP"))), @: \(kPrefix+".storedIP")")
} }
} }
public func internetMonitoring() { public func internetMonitoring() {
networkMonitor.monitor.pathUpdateHandler = { path in networkMonitor.monitor.pathUpdateHandler = { path in