Compare commits

...

15 commits

Author SHA1 Message Date
TrevKnows
02a0f52c0c
Merge pull request #127 from adafruit/Caching-Fix
Network response caching - Fixed
2023-03-14 10:29:17 -04:00
TrevKnows
598b340a2c First commit - Fixed
Ran into an issue loading new projects into the project list. The app was using the cached list while there was a network connection. The Network library has been updated, so I needed to fix how the project files are cached.
2023-03-14 10:10:43 -04:00
TrevKnows
bbf94b1b34
Merge pull request #126 from adafruit/Adding-MIT-License
Adding mit license
2023-02-16 12:25:18 -05:00
TrevKnows
b546ad5040 Commit 2023-02-16 12:23:54 -05:00
TrevKnows
7938077cd1 Fixed WebView
WebView within the app wasn't displaying.
2023-01-17 10:19:31 -05:00
TrevKnows
f848407bdf Fixed searchView drifting 2023-01-13 15:04:45 -05:00
TrevKnows
0433aaf55e BUG FIX - Third-party projects are duplicated
BUG - Third-party projects are duplicated when appended to the project list.
2023-01-12 16:54:19 -05:00
TrevKnows
4200663fc5 Test Commit 2023-01-11 11:38:00 -05:00
TrevKnows
d837bfe230 Updated Wifi Pairing info 2023-01-11 10:59:04 -05:00
TrevKnows
0cc62bb5ce commit 2022-12-17 18:58:06 -05:00
TrevKnows
ea4e1114ad Updated "Failed Button"
Show "Failed Button" on "USB in Use" Error #114
2022-12-05 18:40:15 -05:00
TrevKnows
eeb34b5319 Added a few fixes
• Show "Failed Button" on "USB in Use" Error
• Updated the "Options" request
2022-12-05 18:35:40 -05:00
TrevKnows
3ed8a4a792 Fixed Issue
While the wifi subview cells were open, copies of the WifiTransferService class were being initiated.

I figured out a way to collapse the cells not being used.

Also removed class references that are not being used.
2022-12-05 01:03:19 -05:00
TrevKnows
78fffb0103 Fixed .filetransfer screen appears upon disconnection
https://github.com/adafruit/PyLeap-iOS/issues/109
2022-11-28 12:10:44 -05:00
TrevKnows
f2888a88a6 Removed Dups 2022-11-28 10:39:26 -05:00
50 changed files with 1892 additions and 1348 deletions

25
LICENSE.txt Normal file
View file

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

View file

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

View file

@ -11,11 +11,12 @@
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D505B99D2756894300386E9F /* ViewModifier.swift */; };
D517F68126C5771D002996E8 /* FillerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F68026C5771D002996E8 /* FillerView.swift */; };
D5199A2F28DD16F100ACC34C /* BleContentTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */; };
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */; };
D5267411292E902700D4C79E /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5267410292E902700D4C79E /* Networking.swift */; };
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269BFF291960A300C0CE4B /* WifiSelection.swift */; };
D5269C02291997DE00C0CE4B /* WifiServiceCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */; };
D5269C042919985400C0CE4B /* WifiServiceCellSubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */; };
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5269C07291AB75800C0CE4B /* WifiPairingView.swift */; };
D52A92692906E1A900973B6B /* WifiSearchListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A92682906E1A900973B6B /* WifiSearchListRowView.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 */; };
@ -27,9 +28,10 @@
D52F7E742672F4C400911D43 /* PyLeapApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52F7E732672F4C400911D43 /* PyLeapApp.swift */; };
D52F7E782672F4C500911D43 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E772672F4C500911D43 /* Assets.xcassets */; };
D52F7E7B2672F4C500911D43 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */; };
D53495A02910639300640692 /* KeyboardGuardian.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534959F2910639300640692 /* KeyboardGuardian.swift */; };
D534F3FC280B59090053699C /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D534F3FB280B59090053699C /* ExampleView.swift */; };
D535E21628E1FA910096E548 /* ScrollRefreshableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535E21528E1FA910096E548 /* ScrollRefreshableView.swift */; };
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */; };
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5361099296FB2BB00228E15 /* DataStore.swift */; };
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A1D1281B9BB70038D483 /* Buttons.swift */; };
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24A281F92840038D483 /* ImageCaching.swift */; };
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = D544A24E282046840038D483 /* OnAnimationComplete.swift */; };
@ -51,6 +53,8 @@
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */; };
D567E2BC28C1527F0009F768 /* WifiSubViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */; };
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D567E2DE28C8D40C0009F768 /* SettingsView.swift */; };
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */; };
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */; };
D56F640C270242CA000E5975 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */; };
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */; };
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */; };
@ -114,11 +118,12 @@
D505B99D2756894300386E9F /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = "<group>"; };
D517F68026C5771D002996E8 /* FillerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillerView.swift; sourceTree = "<group>"; };
D5199A2E28DD16F100ACC34C /* BleContentTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleContentTransfer.swift; sourceTree = "<group>"; };
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiCellViewModel.swift; sourceTree = "<group>"; };
D5267410292E902700D4C79E /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
D5269BFF291960A300C0CE4B /* WifiSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiSelection.swift; path = "PyLeap/Views/Unpaired View/WifiSelection.swift"; sourceTree = SOURCE_ROOT; };
D5269C01291997DE00C0CE4B /* WifiServiceCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellView.swift; path = ../../../../../../Desktop/WifiServiceCellView.swift; sourceTree = "<group>"; };
D5269C032919985400C0CE4B /* WifiServiceCellSubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WifiServiceCellSubView.swift; path = ../../../../../../Desktop/WifiServiceCellSubView.swift; sourceTree = "<group>"; };
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiPairingView.swift; sourceTree = "<group>"; };
D52A92682906E1A900973B6B /* WifiSearchListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSearchListRowView.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>"; };
@ -128,9 +133,10 @@
D52F7E772672F4C500911D43 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D52F7E7A2672F4C500911D43 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
D52F7E7C2672F4C500911D43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D534959F2910639300640692 /* KeyboardGuardian.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardGuardian.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>"; };
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONDecoderHelper.swift; sourceTree = "<group>"; };
D5361099296FB2BB00228E15 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
D544A1D1281B9BB70038D483 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
D544A24A281F92840038D483 /* ImageCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCaching.swift; sourceTree = "<group>"; };
D544A24E282046840038D483 /* OnAnimationComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnAnimationComplete.swift; sourceTree = "<group>"; };
@ -156,6 +162,8 @@
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCell.swift; sourceTree = "<group>"; };
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiSubViewCellModel.swift; sourceTree = "<group>"; };
D567E2DE28C8D40C0009F768 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsView.swift; sourceTree = "<group>"; };
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESettingsViewModel.swift; sourceTree = "<group>"; };
D56F6407270242CA000E5975 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
D56F6408270242CA000E5975 /* String+DeletingPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DeletingPrefix.swift"; sourceTree = "<group>"; };
D56F640A270242CA000E5975 /* FileTransferPathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferPathUtils.swift; sourceTree = "<group>"; };
@ -352,17 +360,30 @@
D567E2DD28C8D3E20009F768 /* SettingsView */ = {
isa = PBXGroup;
children = (
D56B75D2294BAA8900D008E7 /* BLESetttings */,
D567E2DE28C8D40C0009F768 /* SettingsView.swift */,
D534959F2910639300640692 /* KeyboardGuardian.swift */,
D5DD39AA28D234C3000FAEB8 /* SettingsViewModel.swift */,
);
path = SettingsView;
sourceTree = "<group>";
};
D56B75D2294BAA8900D008E7 /* BLESetttings */ = {
isa = PBXGroup;
children = (
D56B75D3294BAAB400D008E7 /* BLESettingsView.swift */,
D56B75D5294BAACE00D008E7 /* BLESettingsViewModel.swift */,
);
path = BLESetttings;
sourceTree = "<group>";
};
D58E1C8628A2B0DE00AB683E /* Wifi View */ = {
isa = PBXGroup;
children = (
D567E2DD28C8D3E20009F768 /* SettingsView */,
D58E1C8728A2B10B00AB683E /* WifiView.swift */,
D567E2B728C137880009F768 /* WifiCell.swift */,
D51D1412293A53BD0028AEDD /* WifiCellViewModel.swift */,
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
D58E1C8928A2B15E00AB683E /* WifiViewModel.swift */,
D5269C07291AB75800C0CE4B /* WifiPairingView.swift */,
D52A926E29078E0A00973B6B /* WifiServiceSelectionView.swift */,
@ -372,12 +393,9 @@
D5BA1F7E28B66F280012FC62 /* WifiServiceManager.swift */,
D5DD39A628D11817000FAEB8 /* WifiFileTransfer.swift */,
D5DD39A828D11962000FAEB8 /* WifiTransferService.swift */,
D52A92682906E1A900973B6B /* WifiSearchListRowView.swift */,
D567E2B728C137880009F768 /* WifiCell.swift */,
D5AA27F728CA785B001CCE25 /* CircuitPythonType.swift */,
D5482F4A28E75053000B0C8E /* LocalNetworkAuth.swift */,
D5AA27F928CA8D46001CCE25 /* WifiStatusHeaderBarView.swift */,
D567E2B928C1382E0009F768 /* WifiSubViewCell.swift */,
D567E2BB28C1527F0009F768 /* WifiSubViewCellModel.swift */,
D567E2B528B81B730009F768 /* Queue.swift */,
D5BA1F7928B52A490012FC62 /* WifiListDetailView.swift */,
@ -394,7 +412,6 @@
D59DFDB2268CCEAC001737F6 /* Views */ = {
isa = PBXGroup;
children = (
D567E2DD28C8D3E20009F768 /* SettingsView */,
D58E1C8628A2B0DE00AB683E /* Wifi View */,
D5507ACE26C668BC00512BAA /* UI Components */,
D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
@ -473,9 +490,11 @@
children = (
D505B99B2755323C00386E9F /* NetworkMonitor.swift */,
D544A24A281F92840038D483 /* ImageCaching.swift */,
D5267410292E902700D4C79E /* Networking.swift */,
D5D1F4A327EBA7E30040E2BF /* NetworkManager.swift */,
D58358ED27DA5C0F0069F7F5 /* NetworkError.swift */,
D595FC2D2812C23D00569D8C /* Image Extension.swift */,
D5361097296F5E5400228E15 /* JSONDecoderHelper.swift */,
);
path = Networking;
sourceTree = "<group>";
@ -484,6 +503,7 @@
isa = PBXGroup;
children = (
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
D5361099296FB2BB00228E15 /* DataStore.swift */,
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
);
path = Model;
@ -643,6 +663,7 @@
D544A1D2281B9BB70038D483 /* Buttons.swift in Sources */,
D5D1F4AE27ECFDA10040E2BF /* GifImage.swift in Sources */,
D58E1C8A28A2B15E00AB683E /* WifiViewModel.swift in Sources */,
D5267411292E902700D4C79E /* Networking.swift in Sources */,
D5C474AC27E174A5002DD160 /* WebView Content.swift in Sources */,
D544A2512822D4730038D483 /* Spotlight Extension.swift in Sources */,
D56F640D270242CA000E5975 /* FileTransferPathUtils.swift in Sources */,
@ -672,9 +693,9 @@
D5CC6BB428173AE0008629FB /* HeaderView.swift in Sources */,
D52BE85626A0E5A700630900 /* PeripheralAutoConnect.swift in Sources */,
D52A926D29071DF400973B6B /* SelectionView.swift in Sources */,
D52A92692906E1A900973B6B /* WifiSearchListRowView.swift in Sources */,
D505B99E2756894300386E9F /* ViewModifier.swift in Sources */,
D5CC6BB628173B91008629FB /* SubHeaderView.swift in Sources */,
D51D1413293A53BD0028AEDD /* WifiCellViewModel.swift in Sources */,
D5269C08291AB75800C0CE4B /* WifiPairingView.swift in Sources */,
D544A24B281F92840038D483 /* ImageCaching.swift in Sources */,
D517F68126C5771D002996E8 /* FillerView.swift in Sources */,
@ -682,7 +703,6 @@
D5AA27FA28CA8D46001CCE25 /* WifiStatusHeaderBarView.swift in Sources */,
D5F53CEB2694B524007634C2 /* Blinka Animation.swift in Sources */,
D5269C00291960A300C0CE4B /* WifiSelection.swift in Sources */,
D53495A02910639300640692 /* KeyboardGuardian.swift in Sources */,
D5597BF826A9E14B00DF17C0 /* AppDelegate.swift in Sources */,
D56F640E270242CA000E5975 /* String+DeletingPrefix.swift in Sources */,
D57858F328333CBC008E8BE4 /* TroubleshootView.swift in Sources */,
@ -691,6 +711,7 @@
D5482F4928E63DB7000B0C8E /* MainSelectionViewModel.swift in Sources */,
D5597C3B26B98E1E00DF17C0 /* NumbersOnly.swift in Sources */,
D5D1F4A427EBA7E30040E2BF /* NetworkManager.swift in Sources */,
D56B75D6294BAACE00D008E7 /* BLESettingsViewModel.swift in Sources */,
D5DD39AB28D234C3000FAEB8 /* SettingsViewModel.swift in Sources */,
D52A926F29078E0A00973B6B /* WifiServiceSelectionView.swift in Sources */,
D5BA1F8328B68ED40012FC62 /* NetworkPeripheral.swift in Sources */,
@ -699,6 +720,7 @@
D567E2BA28C1382E0009F768 /* WifiSubViewCell.swift in Sources */,
D59DFD8F268A4A4D001737F6 /* BTConnectionView.swift in Sources */,
D58D887B26CC02B60085604A /* OnboardingViewPure.swift in Sources */,
D5361098296F5E5400228E15 /* JSONDecoderHelper.swift in Sources */,
D52BE85426A0E39100630900 /* BTConnectionViewModel.swift in Sources */,
D5DD39A928D11962000FAEB8 /* WifiTransferService.swift in Sources */,
D59DFDC2268CFA36001737F6 /* OnboardingStepView.swift in Sources */,
@ -706,6 +728,7 @@
D544A24F282046840038D483 /* OnAnimationComplete.swift in Sources */,
D5507AD126C668BC00512BAA /* SearchBarView.swift in Sources */,
D59DFDBA268CDEEC001737F6 /* RootViewModel.swift in Sources */,
D536109A296FB2BB00228E15 /* DataStore.swift in Sources */,
D5C74DF527EB93E300730505 /* DemoViewCell.swift in Sources */,
D567E2DF28C8D40C0009F768 /* SettingsView.swift in Sources */,
D5482F4B28E75053000B0C8E /* LocalNetworkAuth.swift in Sources */,
@ -717,6 +740,7 @@
D5D1F4B027ECFDE00040E2BF /* NavBarModifier.swift in Sources */,
D5597C0C26AF018800DF17C0 /* View+If.swift in Sources */,
D58E1C8D28A2B32C00AB683E /* Wifi_ifaddrs.m in Sources */,
D56B75D4294BAAB400D008E7 /* BLESettingsView.swift in Sources */,
D52BE7EE269DF36E00630900 /* DownloadViewModel.swift in Sources */,
D59E31AA281B8DD300D24211 /* DownloadState.swift in Sources */,
D5BA1F7F28B66F280012FC62 /* WifiServiceManager.swift in Sources */,
@ -857,17 +881,17 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 0;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;
@ -887,17 +911,17 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 0;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;

View file

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

Binary file not shown.

View file

@ -98,7 +98,7 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
func unzipProjectFile(urlString: String, projectTitle: String) {
print("Times unzipProjectFile was called")
let CPZipName = directoryPath.appendingPathComponent("\(projectTitle).zip")
// _ = directoryPath.appendingPathComponent("PyLeap Folder")
@ -110,7 +110,7 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
if let zipTempFileUrl = tempFileUrl {
do {
print("Times do looped in unzipProjectFile")
let zipData = try Data(contentsOf: zipTempFileUrl)
try zipData.write(to: CPZipName)
@ -126,6 +126,8 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
print("times wifiDownloadComplete was triggered")
NotificationCenter.default.post(name: .wifiDownloadComplete, object: nil, userInfo: projectResponse)
} catch {
@ -183,6 +185,8 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
print(error)
@ -196,6 +200,8 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
/// 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")
@ -224,21 +230,9 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
try FileManager.default.copyItem(at: location, to: destinationURL)
DispatchQueue.main.async {
print("unzipProjectFile loop")
self.unzipProjectFile(urlString: self.bundleURL, projectTitle: self.bundleTitle)
// 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()
}
@ -280,7 +274,6 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
downloadTaskSession = session.downloadTask(with: validURL)
downloadTaskSession.resume()
// unzipProjectFile(urlString: urlString, projectTitle: projectTitle)
}

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: "Send projects directly from the Adafruit Learning System to your Bluefruit Compatible Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
PageView(title: "Send projects directly from the Adafruit Learning System to your Adafruit Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
}
.tabViewStyle(PageTabViewStyle())
}

View file

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

View file

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

View file

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

View file

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

View file

@ -14,232 +14,9 @@ import Foundation
import SwiftUI
class NetworkService: ObservableObject {
let dataStore = DataStore()
static let shared = NetworkService()
@Published var pdemos = [ResultItem]()
@State var storedURL = ""
let userDefaults = UserDefaults.standard
init(){
fetch()
// 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")
}
saveCustomProjects(content: customProjects)
}
func saveCustomProjects(content: [ResultItem]) {
print("\(#function) @Line: \(#line)")
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 {
print("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")
print("\(#function) @Line: \(#line)")
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")
}
}
} else {
print("Saving Error")
}
}
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) is called twice to run. Ignore this method.")
var customList: [ResultItem] = []
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load custom projectsxo")
// print(loadedProjects)
for i in loadedProjects {
print("\(i.projectName)")
}
customList = loadedProjects
}
}
return customList
}
func mergeProjects() {
print(#function)
var standardList: [ResultItem] = []
var customList: [ResultItem] = []
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
// let check = loadedProjects.map { $0.bundleLink == "" }
pdemos = loadedProjects
print(loadedProjects)
}
}
if let savedProjects = userDefaults.object(forKey: "CustomProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
print(loadedProjects)
}
}
}
func load() {
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
let mergedList = loadedProjects + loadCustomProjects()
// save(content: <#T##[ResultItem]#>)
pdemos = mergedList
// print("----Standard Projects----")
for i in loadedProjects {
// print("\(i.projectName)")
}
// print("++++Custom Projects++++")
for i in loadCustomProjects() {
// print("\(i.projectName)")
}
}
}
}
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent)
private var dataTask: URLSessionDataTask?
@ -249,24 +26,16 @@ class NetworkService: ObservableObject {
// Session Configuration & Caching Policy
let configuration = URLSessionConfiguration.default
// configuration.requestCachePolicy = .useProtocolCachePolicy
configuration.requestCachePolicy = .returnCacheDataElseLoad
return URLSession(configuration: configuration)
}()
@Published var projectInfo = Data()
func fetch() {
let cache = URLCache.shared
let requestForCache = URLRequest(url: URL(string: AdafruitInfo.baseURL)!)
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
print("Making Network Request")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
func fetch(completion: @escaping() -> Void) {
print("Attempting Network Request")
let request = URLRequest(url: URL(string: AdafruitInfo.baseURL)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 60.0)
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
print("error: \(error)")
@ -279,93 +48,81 @@ class NetworkService: ObservableObject {
DispatchQueue.main.async {
// for i in projectData.projects {
// print("\(i.projectName)")
// }
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList)
self.save(content: projectData.projects)
self.load()
completion()
}
} else {
print("No data found")
}
} else {
print("Updating UIList with Cached data...")
DispatchQueue.main.async {
self.load()
self.dataStore.loadDefaultProjectList()
completion()
}
}
}
task.resume()
}
func fetchThirdParyProject(urlString: String?) {
let cache = URLCache.shared
guard let urlString = urlString else {
print("\(#function) @Line: \(#line)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
print("Error urlString")
return
}
func fetchThirdPartyProject(urlString: String?) {
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.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)")
thirdPartyBackgroundQueue.async {
guard let urlString = urlString else {
print("\(#function) @Line: \(#line)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
print("Error urlString")
return
}
if let data = data {
print("Updating UIList with new data...")
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
if let projects = projectData?.projects {
DispatchQueue.main.async {
print(#function)
for i in projects {
print("\(i.projectName)")
}
// self.pdemos.append(contentsOf: projects)
// self.saveCustomProjects(content: projects)
self.save(customProjects: projects)
}
}
if urlString.contains(" ") {
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
guard let requestURL = URL(string: urlString) else {
print("\(#function) @Line: \(#line)")
print("Error requestURL")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
let request = URLRequest(url: requestURL, cachePolicy: URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 0.0)
print("Making Network Request for Custom Project.")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Could not load project. Please check your URL Invalid URL: \(urlString)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
if let data = data {
let projectData = JSONDecoderHelper.decode(data: data) as RootResults?
if let projects = projectData?.projects {
DispatchQueue.main.async {
self.dataStore.save(customProjects: projects, completion: self.dataStore.loadThirdPartyProjectsFromFileManager)
}
}
}
}
task.resume()
}
task.resume()
}
}

View file

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

BIN
PyLeap/Views/.DS_Store vendored

Binary file not shown.

View file

@ -295,11 +295,8 @@ struct BTConnectionView: View {
case .disconnected(let error):
if let error = error {
text = "Disconnected: \(error.localizedDescription)"
//self.showSheetView.toggle()
} else {
text = "Disconnected"
}
}
return text

View file

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

View file

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

View file

@ -128,6 +128,9 @@ class BleContentTransfer: ObservableObject {
if let projectInfo = notification.userInfo as Dictionary? {
if let title = projectInfo["projectTitle"] as? String, let link = projectInfo["projectLink"] as? String {
testFileExistance(for: title, bundleLink: link)
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.downloadState = .transferring
}
}
}
}
@ -561,8 +564,10 @@ class BleContentTransfer: ObservableObject {
if sharedBootinfo.contains("CircuitPython 7") {
print(tempPathComponents)
indexOfCP = tempPathComponents.firstIndex(of: "CircuitPython 7.x")!
tempPathComponents.removeSubrange(0...indexOfCP)
var joinedArrayPath = tempPathComponents.joined(separator: "/")
print("\(#function) @Line: \(#line)")

View file

@ -8,6 +8,10 @@
import SwiftUI
import FileTransferClient
class ExpandedBLECellState: ObservableObject {
@Published var currentCell = ""
}
struct BleModuleView: View {
// Data
@ -22,31 +26,30 @@ struct BleModuleView: View {
}
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var expandedState : ExpandedBLECellState
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
let selectedPeripheral = FileTransferConnectionManager.shared.selectedPeripheral
@StateObject var viewModel = BleModuleViewModel()
@ObservedObject var networkService = NetworkService()
@EnvironmentObject var rootViewModel: RootViewModel
//clearKnownPeripheralUUIDs
@State private var isConnected = false
@State private var errorOccured = false
@State var notExpanded = false
@State var isExpanded = true
@State private var scrollViewID = UUID()
@State private var activeAlert: ActiveAlert?
@State private var boardBootInfo = ""
@State private var inConnectedInSelectionView = true
@AppStorage("shouldShowOnboarding123") var switchedView: Bool = false
@State var isExpanded = true
@State var subviewHeight : CGFloat = 0
func showConfirmationPrompt() {
@ -259,7 +262,7 @@ struct BleModuleView: View {
ScrollView(.vertical, showsIndicators: true) {
ScrollView(.vertical, showsIndicators: false) {
ScrollViewReader { scroll in
@ -273,20 +276,33 @@ struct BleModuleView: View {
}
let check = networkService.pdemos.filter {
let check = viewModel.pdemos.filter {
$0.compatibility.contains(boardBootInfo)
}
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, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
}
})
} else {
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
}
}
@ -294,7 +310,7 @@ struct BleModuleView: View {
.id(self.scrollViewID)
}
.environmentObject(expandedState)
}
}

View file

@ -18,6 +18,16 @@ class BleModuleViewModel: ObservableObject {
@Published var isTransmiting = false
@Published var bootUpInfo = ""
let dataStore = DataStore()
@Published var pdemos : [ResultItem] = []
init() {
pdemos = dataStore.loadDefaultList()
}
enum ProjectViewError: LocalizedError {
case fileTransferUndefined
}

View file

@ -142,7 +142,7 @@ Try again later
.padding(.top, 20)
}
.sheet(isPresented: $showWebViewPopover, content: {
WebView(URLRequest(url: URL(string: result.learnGuideLink)!))
SwiftUIWebView(webAddress: result.learnGuideLink)
})

View file

@ -9,10 +9,13 @@ import Foundation
struct DemoViewCell: View {
@EnvironmentObject var expandedState : ExpandedBLECellState
let result : ResultItem
@State private var isExpanded: Bool = false {
@State var isExpanded: Bool = false {
didSet {
onViewGeometryChanged()
onViewGeometryChanged()
}
}
@ -61,7 +64,10 @@ struct DemoViewCell: View {
.padding(.leading)
.frame(maxWidth: .infinity)
.background(Color("pyleap_purple"))
.onTapGesture { isExpanded.toggle() }
.onTapGesture {
expandedState.currentCell = result.bundleLink
}
}
}

View file

@ -11,6 +11,8 @@ import FileTransferClient
struct RootView: View {
@StateObject private var model = RootViewModel()
@StateObject var currentCellID = ExpandedState()
@StateObject var currentBLECellID = ExpandedBLECellState()
@ObservedObject var connectionManager = FileTransferConnectionManager.shared
@AppStorage("onboarding") var onboardingSeen = true
@ -60,12 +62,17 @@ struct RootView: View {
case .settings:
SettingsView()
case .bleSettings:
BLESettingsView()
default:
FillerView()
}
}
.environmentObject(currentCellID)
.environmentObject(currentBLECellID)
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
@ -83,13 +90,11 @@ struct RootView: View {
.onChange(of: connectionManager.isSelectedPeripheralReconnecting) { isConnectedOrReconnecting in
if isConnectedOrReconnecting, model.destination == .fileTransfer {
model.destination = .fileTransfer
isReconnecting = true
} else {
isReconnecting = false
}
@ -98,11 +103,12 @@ struct RootView: View {
.onChange(of: connectionManager.isDisconnectingFromCurrent) { isDisconnected in
if isDisconnected {
print("Is disconnected.")
isReconnecting = false
model.destination = .selection
connectionManager.clearAllPeripheralInfo()
connectionManager.peripherals = []
connectionManager.isDisconnectingFromCurrent = false
isReconnecting = false
model.destination = .selection
}
}

View file

@ -22,11 +22,13 @@ public class RootViewModel: ObservableObject {
case fileTransfer
case wifi
case settings
case bleSettings
case mainSelection
case wifiSelection
case wifiPairingTutorial
case wifiServiceSelection
case selection
}
@Published var destination: Destination = AppEnvironment.isRunningTests ? .mainSelection : .startup
@ -74,6 +76,10 @@ public class RootViewModel: ObservableObject {
}
}
func backToMain() {
destination = .main
}
func goToStartup(){
destination = .startup
}
@ -86,10 +92,14 @@ public class RootViewModel: ObservableObject {
destination = .fileTransfer
}
func goToSettings(){
func goToSettings(content: SettingState){
destination = .settings
}
func goToBLESettings(){
destination = .bleSettings
}
func showWarningIfBluetoothStateIsNotReady() {
let bluetoothState = BleManager.shared.state
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized

View file

@ -1,95 +0,0 @@
//
// KeyboardGuardian.swift
// PyLeap
//
// Created by Trevor Beaton on 10/31/22.
//
/// Credit to: https://stackoverflow.com/questions/56491881/move-textfield-up-when-the-keyboard-has-appeared-in-swiftui
import SwiftUI
import Combine
final class KeyboardGuardian: ObservableObject {
public var rects: Array<CGRect>
public var keyboardRect: CGRect = CGRect()
// keyboardWillShow notification may be posted repeatedly,
// this flag makes sure we only act once per keyboard appearance
public var keyboardIsHidden = true
@Published var slide: CGFloat = 0
var showField: Int = 0 {
didSet {
updateSlide()
}
}
init(textFieldCount: Int) {
self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)
}
func addObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
}
func removeObserver() {
NotificationCenter.default.removeObserver(self)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func keyBoardWillShow(notification: Notification) {
if keyboardIsHidden {
keyboardIsHidden = false
if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
keyboardRect = rect
updateSlide()
}
}
}
@objc func keyBoardDidHide(notification: Notification) {
keyboardIsHidden = true
updateSlide()
}
func updateSlide() {
if keyboardIsHidden {
slide = 0
} else {
let tfRect = self.rects[self.showField]
let diff = keyboardRect.minY - tfRect.maxY
if diff > 0 {
slide += diff
} else {
slide += min(diff, 0)
}
}
}
}
struct GeometryGetter: View {
@Binding var rect: CGRect
var body: some View {
GeometryReader { geometry in
Group { () -> AnyView in
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}
return AnyView(Color.clear)
}
}
}
}

View file

@ -17,7 +17,7 @@ struct RunItButton: View {
.cornerRadius(25)
.foregroundColor(Color("pyleap_pink"))
Text("Run it!")
Text("Run")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.frame(height: 50)

View file

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

View file

@ -11,7 +11,7 @@ struct SubHeaderView: View {
var body: some View {
HStack {
Text("Browse available Wi-Fi PyLeap Projects")
Text("Browse available WiFi PyLeap Projects")
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.font(Font.custom("ReadexPro-Regular", size: 25))

View file

@ -38,9 +38,9 @@ struct WifiHeaderView: View {
Spacer()
Button {
rootViewModel.goToSettings()
rootViewModel.goToSettings(content: .wifi)
} label: {
Image(systemName: "gearshape")
Image(systemName: "plus")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)

View file

@ -14,13 +14,14 @@ enum AdafruitDevices {
case esp32s2
}
struct MainSelectionView: View {
struct MainSelectionView: View {
@Environment(\.presentationMode) private var presentationMode
@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()
@ -38,27 +39,6 @@ struct MainSelectionView: View {
VStack(alignment: .center, spacing: 0) {
MainHeaderView()
// HStack(alignment: .center, spacing: 8, content: {
//
// Button {
// rootViewModel.goToWifiView()
//
// } label: {
// Text("Connect to Wi-Fi Mode")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// .underline()
// .transition(.move(edge: .top))
// }
//
// })
// .padding(.all, 0.0)
// .frame(maxWidth: .infinity)
// .frame(maxHeight: 40)
// .background(Color("pyleap_blue"))
// .foregroundColor(.white)
HStack(alignment: .center, spacing: 8, content: {
Text("Not Connected to a Device.")
.font(Font.custom("ReadexPro-Regular", size: 16))
@ -77,14 +57,11 @@ struct MainSelectionView: View {
.background(Color("pyleap_burg"))
.foregroundColor(.white)
ScrollView {
MainSubHeaderView(device: "Adafruit device")
if networkModel.pdemos.isEmpty {
if viewModel.pdemos.isEmpty {
HStack{
Spacer()
ProgressView()
@ -97,13 +74,35 @@ struct MainSelectionView: View {
ScrollViewReader { scroll in
ForEach(networkModel.pdemos) { demo in
DemoViewCell(result: demo, isConnected: $isConnected, deviceInfo: $test, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
ForEach(viewModel.pdemos) { demo in
if demo.bundleLink == expandedState.currentCell {
DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
.onAppear(){
print("Cell Appeared")
withAnimation {
scroll.scrollTo(demo.id)
}
}
})
} else {
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
}
}
}
}
}
@ -146,7 +145,7 @@ struct MainSelectionView: View {
})
.onAppear() {
// networkModel.fetch()
print("Opened MainSelectionView")
}
@ -163,10 +162,85 @@ struct MainSelectionView: View {
}
//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,42 +7,82 @@
import Foundation
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 {
let userDefaults = UserDefaults.standard
@ObservedObject var networkModel = NetworkService()
@ObservedObject var networkMonitor = InternetConnectionManager()
let fileManager = FileManager.default
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let dataStore = DataStore()
@Published var pdemos : [ResultItem] = []
var networkMonitorCancellable: AnyCancellable?
init() {
// load()
// self.pdemos = load()
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
networkMonitorCancellable = networkMonitor.$isConnected.sink { isConnected in
if isConnected {
print("The device is currently connected to the internet.")
// Perform some action when the device is connected to the internet.
self.networkModel.fetch {
self.pdemos = self.dataStore.loadDefaultList()
}
} else {
print("The device is not currently connected to the internet.")
// Perform some action when the device is not connected to the internet.
print("Loading cached remote data.")
self.pdemos = self.dataStore.loadDefaultList()
}
}
}
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

@ -16,22 +16,23 @@ struct SelectionView: View {
VStack {
// HStack {
// Button {
// rootViewModel.goToMain()
//
// } label: {
// Image(systemName: "arrow.backward")
// .resizable()
// .frame(width: 25, height: 25, alignment: .center)
// .offset(y: 15)
// .foregroundColor(.black)
// }
// .padding()
//
// Spacer()
// }
// .padding(.top, 15)
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()
@ -51,7 +52,6 @@ struct SelectionView: View {
VStack {
Button {
// connectionManager.isDisconnectingFromCurrent = true
rootViewModel.goToWiFiSelection()
} label: {
Text("Wifi")
@ -64,7 +64,6 @@ struct SelectionView: View {
}
Button {
rootViewModel.goTobluetoothPairing()
} label: {
@ -80,7 +79,7 @@ struct SelectionView: View {
Button {
rootViewModel.goToMainSelection()
rootViewModel.backToMain()
} label: {
Text("I Don't Know")
.font(Font.custom("ReadexPro-Regular", size: 25))
@ -108,9 +107,9 @@ struct SelectionView: View {
}
Spacer()
}
}
.padding(.bottom, 60)
}
}

View file

@ -8,7 +8,6 @@
import SwiftUI
struct WifiSelection: View {
@ObservedObject var wifiServiceViewModel = WifiServiceManager()
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = WifiViewModel()
@ -77,12 +76,11 @@ struct WifiSelection: View {
.offset(y: 15)
.foregroundColor(.black)
}
.padding()
.padding(.leading, 30)
Spacer()
}
.padding(.top, 15)
// .border(.indigo, width: 2)
Image("pyleapLogo")
.resizable()
@ -114,7 +112,6 @@ struct WifiSelection: View {
}
Button {
showValidationPrompt()
} label: {
@ -153,8 +150,9 @@ struct WifiSelection: View {
}
Spacer()
}
.padding(.bottom, 60)
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
if newValue {

View file

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

View file

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

View file

@ -7,34 +7,30 @@
import SwiftUI
enum SettingState {
case ble
case wifi
case none
}
struct SettingsView: View {
// @State public var appState: SettingState = .none
@State private var jsonFileName: String = ""
@State private var pythonFileName: String = ""
@State private var presentJSONAlert = false
@State private var presentPythonAlert = false
@State private var thirdPartyLink: String = ""
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var viewModel = SettingsViewModel()
@ObservedObject var networkModel = NetworkService()
@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 1)
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") {
}
}
@ -43,126 +39,46 @@ struct SettingsView: View {
viewModel.clearKnownIPAddress()
rootViewModel.goToWifiView()
} cancel: {
}
}
var body: some View {
VStack {
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 {
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 {
rootViewModel.goToSelection()
} label: {
Text("Disconnect")
}
}
}
if viewModel.connectedToDevice {
Section {
Text("Enter project URL")
TextField("https://", text: $pythonFileName)
.background(GeometryGetter(rect: $kGuardian.rects[0]))
.keyboardType(.URL)
.textContentType(.URL)
.onSubmit {
networkModel.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 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")
@ -171,9 +87,6 @@ struct SettingsView: View {
}
.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
.onAppear { self.kGuardian.addObserver() }
.onDisappear { self.kGuardian.removeObserver() }
.onChange(of: viewModel.invalidURL, perform: { newValue in
showInvalidURLEntryAlert()
@ -191,9 +104,9 @@ struct SettingsView: View {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
// .font(.system(size: 18, weight: .regular, design: .default))
.foregroundColor(.blue)
}
.padding(12)
@ -205,14 +118,44 @@ struct SettingsView: View {
.safeAreaInset(edge: .top) {
VStack {
HStack {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
}
.padding(.leading,20)
.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)
@ -220,13 +163,10 @@ struct SettingsView: View {
HStack {
Text("Settings")
.font(.largeTitle)
.bold()
.font(Font.custom("ReadexPro-Bold", size: 32))
.padding(.leading,20)
Spacer()
}
.background(Color(.systemGroupedBackground))
}
.padding(.top, 25)
@ -235,9 +175,6 @@ struct SettingsView: View {
}
}
extension View {
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
@ -251,8 +188,6 @@ extension View {
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}

View file

@ -19,6 +19,7 @@ class SettingsViewModel: ObservableObject {
@Published var invalidURL = false
@Published var confirmDownload = false
init() {
check()
registerNotifications(enabled: true)

View file

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

View file

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

View file

@ -11,8 +11,91 @@ struct WifiCPVersion {
static var versionNumber = 0
}
struct TestIndex {
var count = 0
var numberOfFiles = 0
}
class WifiFileTransfer: ObservableObject {
// func fetchDocuments<T: Sequence>(in sequence: T) where T.Element == Int {
// var documentNumbers = sequence.map { String($0) }
//
// let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
// guard
// let self = self,
// let documentNumber = documentNumbers.first
// else {
// timer.invalidate()
// return
// }
//
// self.fetchDocument(byNumber: documentNumber)
// documentNumbers.removeLast()
// }
// timer.fire() // if you don't want to wait 2 seconds for the first one to fire, go ahead and fire it manually
// }
func fetchDocumentsq<T: Sequence>(in sequence: T) where T.Element == URL {
print(sequence)
guard let value = sequence.first(where: { _ in true }) else {
print("Complete - fetchDocumentsq")
return
}
// let docNumber = String(value)
// fetchDocument(byNumber: docNumber)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
// self?.fetchDocuments(in: sequence.dropFirst())
print(value)
self?.fetchDocumentsq(in: sequence.dropFirst())
}
}
struct TestIndex {
var count = 0
var numberOfFiles = 0
var downloadState: DownloadState = .idle
mutating func backToIdle(){
count = 0
numberOfFiles = 0
downloadState = .idle
}
}
var testIndex = TestIndex()
func copy(with zone: NSZone? = nil) -> Any {
let copy = WifiFileTransfer()
copy.counter = counter
copy.numOfFiles = numOfFiles
return copy
}
init() {
print("WifiFileTransfer initialized")
}
deinit {
print("Deinitializing WifiFileTransfer")
}
@Published var wifiTransferService = WifiTransferService()
// File Manager Data
@ -24,6 +107,8 @@ class WifiFileTransfer: ObservableObject {
@Published var transferError = false
@Published var stopTransfer = false
@Published var downloadState: DownloadState = .idle
var manager = FileManager.default
@ -45,6 +130,8 @@ class WifiFileTransfer: ObservableObject {
}
}
func restFileCounter() {
DispatchQueue.main.async {
self.counter = 0
@ -66,18 +153,25 @@ class WifiFileTransfer: ObservableObject {
projectFiles.removeAll()
}
func showFailedButton() {
init() {
registerNotification(enabled: true)
}
DispatchQueue.main.async {
self.downloadState = .failed
self.testIndex.downloadState = .failed
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
self.downloadState = .idle
self.testIndex.downloadState = .idle
self.stopTransfer = false
}
}
private weak var wifiDownloadComplete: NSObjectProtocol?
private weak var didEncounterTransferError: NSObjectProtocol?
private weak var downloadErrorDidOccur: NSObjectProtocol?
private func registerNotification(enabled: Bool) {
func registerWifiNotification(enabled: Bool) {
print("\(#function) @Line: \(#line)")
let notificationCenter = NotificationCenter.default
@ -93,6 +187,7 @@ class WifiFileTransfer: ObservableObject {
} else {
print("Else testObserver")
if let testObserver = wifiDownloadComplete {notificationCenter.removeObserver(testObserver)}
}
}
@ -129,6 +224,7 @@ class WifiFileTransfer: ObservableObject {
func getProjectForSubClass(nameOf project: String) {
if let enumerator = FileManager.default.enumerator(at: directoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
@ -157,18 +253,18 @@ class WifiFileTransfer: ObservableObject {
func testFileExistance(for project: String, bundleLink: String) {
print("\(#function) @Line: \(#line)")
//removeFileArrayElements()
projectName = project
DispatchQueue.main.async {
self.downloadState = .transferring
self.testIndex.downloadState = .transferring
}
let nestedFolderURL = directoryPath.appendingPathComponent(project)
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("Exist")
print("Exist within testFileExistance")
removeDirectoryAndFiles()
startFileTransfer(url: nestedFolderURL)
@ -188,22 +284,10 @@ class WifiFileTransfer: ObservableObject {
func filesDownloaded(url: URL) {
// removeFileArrayElements()
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 {
}
}
}
func startFileTransfer(url: URL) {
print("times startFileTransfer was called")
print("Project Location: \(url)")
let localFileManager = FileManager()
let resourceKeys = Set<URLResourceKey>([.nameKey, .isDirectoryKey])
@ -225,8 +309,6 @@ class WifiFileTransfer: ObservableObject {
} else {
if isDirectory {
print("Directories Found")
print(fileURL.lastPathComponent)
if name == "_extras" {
dirEnumerator.skipDescendants()
}
@ -247,6 +329,7 @@ class WifiFileTransfer: ObservableObject {
}
}
print("times newMakeDirectory was called here")
newMakeDirectory(directoryArray: filterOutCPDirectories(urls: projectDirectories), regularFilesArray: filterOutCPDirectories(urls: projectFiles))
}
@ -288,6 +371,7 @@ class WifiFileTransfer: ObservableObject {
}
func removeNonCPDirectories(urls: [URL]) -> [URL] {
print("removeNonCPDirectories called")
var mutableURLList = urls
var outgoingArray = [URL]()
@ -298,7 +382,6 @@ class WifiFileTransfer: ObservableObject {
} else {
print("Removed non-CP Directories: \(i)")
}
}
return outgoingArray
@ -306,29 +389,41 @@ class WifiFileTransfer: ObservableObject {
func newMakeDirectory(directoryArray: [URL], regularFilesArray: [URL]) {
if stopTransfer {
showFailedButton()
print("Stopped")
return
}
print("==============Start=================\n")
print("Count: \(directoryArray.count)")
var recursiveArray = removeNonCPDirectories(urls: directoryArray)
if recursiveArray.isEmpty {
print("newMakeDirectory is empty!")
var recursiveArray = removeNonCPDirectories(urls: directoryArray)
print("directoryArray Count : \(directoryArray.count)")
printArray(array: directoryArray)
print("recursiveArray Count : \(recursiveArray.count)")
printArray(array: recursiveArray)
if recursiveArray.count == 0 {
var tempArray = removeNonCPDirectories(urls: regularFilesArray)
print("TempArray count \(tempArray)")
DispatchQueue.main.async {
self.numOfFiles = tempArray.count
self.makeFile(files: tempArray)
// self.testIndex.numberOfFiles = self.numOfFiles
}
print("tempArry set numOfFiles to : \(self.numOfFiles)")
makeFile(files: tempArray)
} else {
print("Input URL: \(recursiveArray.first!)")
@ -460,17 +555,26 @@ class WifiFileTransfer: ObservableObject {
func completedTransfer() {
print("Is main queue: \(Thread.isMainThread)")
DispatchQueue.main.async {
DispatchQueue.main.asyncAfter(deadline: .now()) { [self] in
print("\(#function) @Line: \(#line)")
self.downloadState = .complete
self.counter = 0
self.numOfFiles = 0
}
downloadState = .complete
testIndex.downloadState = .complete
counter = 0
testIndex.count = 0
testIndex.numberOfFiles = 0
numOfFiles = 0
print("downloadState")
print(downloadState)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
print("\(#function) @Line: \(#line)")
self.downloadState = .idle
self.testIndex.backToIdle()
}
}
@ -478,6 +582,8 @@ class WifiFileTransfer: ObservableObject {
var indexOfCP = 0
var tempPathComponents = url.pathComponents
print("URL in checkForExistingFilesOnBoard : \(tempPathComponents)")
if WifiCPVersion.versionNumber == 7 {
indexOfCP = tempPathComponents.firstIndex(of: "CircuitPython 7.x")!
@ -512,8 +618,17 @@ class WifiFileTransfer: ObservableObject {
printArray(array: files)
var copiedArray = files
// self.fetchDocumentsq(in: files)
DispatchQueue.main.async {
self.counter += 1
self.testIndex.count += 1
print("Counted.")
}
if copiedArray.isEmpty {
completedTransfer()
print("Transfer Complete")
print("Copied Array: \(copiedArray)")
print("Files Array: \(files)")
@ -521,6 +636,9 @@ class WifiFileTransfer: ObservableObject {
print("\(#function) @Line: \(#line)")
print("Status: \(downloadState)")
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.completedTransfer()
}
} else {
@ -540,9 +658,7 @@ class WifiFileTransfer: ObservableObject {
print("Removing: \(copiedArray.first!.lastPathComponent)\n")
DispatchQueue.main.async {
self.counter += 1
}
copiedArray.removeFirst()
self.makeFile(files: copiedArray)
@ -567,9 +683,6 @@ class WifiFileTransfer: ObservableObject {
if success.contains(where: { name in name.name == copiedArray.first?.lastPathComponent }) {
print("Exists in the array")
DispatchQueue.main.async {
self.counter += 1
}
copiedArray.removeFirst()
self.makeFile(files: copiedArray)
@ -582,10 +695,7 @@ class WifiFileTransfer: ObservableObject {
case .success(_):
print("Successful Write for: \(copiedArray.first!.lastPathComponent)\n")
DispatchQueue.main.async {
self.counter += 1
print("Current counter: \(self.counter)")
}
copiedArray.removeFirst()
self.makeFile(files: copiedArray)
case .failure(_):

View file

@ -6,13 +6,31 @@
//
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
@ -20,9 +38,13 @@ struct WifiPairingView: View {
@State var showSheetView = false
@State var showConnectionErrorView = false
let tutorialAddress = "https://learn.adafruit.com/pyleap-app/wifi-pairing"
var body: some View {
VStack{
VStack {
HStack {
@ -43,6 +65,7 @@ struct WifiPairingView: View {
Spacer()
Image(systemName: "wifi")
.resizable()
.foregroundColor(.clear)
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(y: -5)
@ -51,58 +74,80 @@ struct WifiPairingView: View {
}
.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()
}) {
Image("pyleapLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.top, 50)
.padding(.horizontal, 60)
Text("Back")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.padding()
.padding(.horizontal, 60)
if nextText == 0 {
.frame(height: 50)
.background(Color("pyleap_purple"))
.clipShape(Capsule())
Text("""
PyLeaps Wi-Fi mode requires EPS32 devices to have Wi-Fi 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)
}
}
.padding()
}
.edgesIgnoringSafeArea(.all)

View file

@ -1,26 +0,0 @@
//
// WifiSearchListRowView.swift
// PyLeap
//
// Created by Trevor Beaton on 10/24/22.
//
import SwiftUI
struct WifiRowView: View {
@State var wifiService: ResolvedService
var body: some View {
VStack(alignment: .leading) {
Text(wifiService.hostName)
.font(.headline)
Text(wifiService.device)
.font(.subheadline)
Text("IP: \(wifiService.ipAddress)")
.font(.subheadline)
Spacer()
}
.padding(.vertical)
}
}

View file

@ -38,11 +38,17 @@ class WifiServiceManager: NSObject, ObservableObject {
override init() {
super.init()
print("Wifi Module Used")
serviceManagerBrowser.delegate = self
findService()
}
deinit {
print("Wifi Module Removed")
self.serviceManagerBrowser.stop()
}
func findService() {
print("Current state of isSearching: \(isSearching)")
@ -177,7 +183,16 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
resolvedServices.append(resolvedService)
if resolvedServices.contains(where: {$0.ipAddress == resolvedService.ipAddress}) {
// it exists, do nothing
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) exists in network")
} else {
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) Added to Network List")
resolvedServices.append(resolvedService)
}
print("resolvedServices count: \(resolvedServices.count)")
}

View file

@ -75,8 +75,6 @@ struct WifiServiceSelectionView: View {
VStack {
HStack {
Button {
@ -89,7 +87,7 @@ struct WifiServiceSelectionView: View {
.offset(y: 15)
.foregroundColor(.black)
}
.padding()
.padding(.leading, 30)
Spacer()
}
@ -105,11 +103,6 @@ struct WifiServiceSelectionView: View {
if wifiServiceViewModel.isSearching && wifiServiceViewModel.resolvedServices.isEmpty {
Text("WiFi Connect")
.font(Font.custom("ReadexPro-Regular", size: 36))
@ -158,9 +151,7 @@ struct WifiServiceSelectionView: View {
}
.padding(.horizontal, 30)
.padding(.bottom, 50)
.padding(.bottom, 60)
}
@ -170,14 +161,14 @@ struct WifiServiceSelectionView: View {
VStack {
if !wifiServiceViewModel.resolvedServices.isEmpty {
Text("Wi-Fi Devices Found")
Text("WiFi Devices Found")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.lineLimit(1)
.minimumScaleFactor(0.1)
} else {
Text("No Wi-Fi Devices Found")
Text("No WiFi Devices Found")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.lineLimit(1)
@ -185,15 +176,6 @@ struct WifiServiceSelectionView: View {
}
// Button {
// print("Is searching: \(wifiServiceViewModel.isSearching)")
//
// } label: {
// Text("Searching?")
// }
Button {
} label: {
@ -219,8 +201,10 @@ struct WifiServiceSelectionView: View {
.background(Color("pyleap_purple"))
.clipShape(Capsule())
}
if wifiServiceViewModel.isSearching {
ProgressView()
}
}
@ -243,21 +227,10 @@ struct WifiServiceSelectionView: View {
.id(self.scrollViewID)
}
.foregroundColor(.black)
}
if wifiServiceViewModel.resolvedServices.isEmpty && !wifiServiceViewModel.isSearching {
Spacer()
VStack {
Text("""
@ -265,14 +238,12 @@ struct WifiServiceSelectionView: View {
compatible Adafruit devices
on your network
""")
.font(Font.custom("ReadexPro-Regular", size: 24))
.multilineTextAlignment(.center)
.minimumScaleFactor(0.1)
.lineLimit(3)
.padding(.bottom, 30)
Button {
rootViewModel.goToWifiPairingTutorial()
@ -280,7 +251,7 @@ struct WifiServiceSelectionView: View {
Text("Pairing Tutorial")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
.minimumScaleFactor(0.1)
.minimumScaleFactor(0.1)
.frame(width: 270, height: 50, alignment: .center)
.background(Color("pyleap_pink"))
.clipShape(Capsule())
@ -289,17 +260,12 @@ struct WifiServiceSelectionView: View {
}
.padding(.horizontal, 30)
.padding(.bottom, 30)
}
}
.onAppear(){
// wifiServiceViewModel.startDiscovery()
}
.padding(.bottom, 60)
.onChange(of: viewModel.ipInputValidation, perform: { newValue in
if newValue {

View file

@ -8,13 +8,15 @@
import SwiftUI
struct WifiSubViewCell: View {
@State var transferInProgress = false
@State var isDownloaded = false
@StateObject var wifiFileTransfer = WifiFileTransfer()
@StateObject var wifiTransferService = WifiTransferService()
let result : ResultItem
@Binding var bindingString: String
@Binding var downloadStateBinder: DownloadState
@ -23,10 +25,13 @@ struct WifiSubViewCell: View {
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var downloadModel = DownloadViewModel()
@StateObject var viewModel = WifiSubViewCellModel()
@ObservedObject var viewModel = WifiSubViewCellModel()
@Binding var isConnected : Bool
@State private var counter = 0
@State private var numOfFiles = 0
@State var downloadState: DownloadState = .idle
@State private var showWebViewPopover: Bool = false
@ -40,6 +45,7 @@ struct WifiSubViewCell: View {
alertMessage(title: """
Download Error
Unable to download project
Try again later
""", exitTitle: "Retry") {
wifiFileTransfer.transferError = false
}
@ -49,52 +55,93 @@ Unable to download project
alertMessage(title: """
USB In Use
Disconnect device from the computer.
Files cannot be tranferred or moved while USB is in use.
Press "Reset" on the device and use a battery source.
Remove device from USB. Press "Reset" on the device.
""", exitTitle: "Retry") {
// wifiFileTransfer.transferError = false
// 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 {
VStack {
VStack(alignment: .leading, spacing: 0, content: {
if viewModel.projectDownloaded {
// HStack {
// Spacer()
//
// Text("Downloaded")
// .foregroundColor(.green)
// .padding(.trailing, -15)
// Circle()
// .fill(.green)
// .frame(width: 15, height: 15)
// .padding()
// }
// .padding(.vertical, -8)
}
Button {
wifiFileTransfer.wifiTransferService.optionRequest(completionHandler: { success in
if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
print("USB not in use.")
} else {
print("USB in use.")
}
})
} label: {
Text("Options Req.")
}
ImageWithURL(result.projectImage)
.scaledToFit()
.frame(maxWidth: .infinity)
@ -122,6 +169,7 @@ Press "Reset" on the device and use a battery source.
}
.padding(.top, 10)
ForEach(result.compatibility, id: \.self) { string in
if string == "circuitplayground_bluefruit" {
@ -165,12 +213,9 @@ Press "Reset" on the device and use a battery source.
.padding(.top, 20)
}
.sheet(isPresented: $showWebViewPopover, content: {
WebView(URLRequest(url: URL(fileURLWithPath: result.learnGuideLink)))
SwiftUIWebView(webAddress: result.learnGuideLink)
})
.onAppear(){
wifiFileTransfer.counter = 0
wifiFileTransfer.numOfFiles = 0
}
@ -179,34 +224,13 @@ Press "Reset" on the device and use a battery source.
if result.compatibility.contains(bindingString) {
if wifiFileTransfer.downloadState == .idle {
if wifiFileTransfer.testIndex.downloadState == .idle {
Button {
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
if wifiFileTransfer.projectDownloaded {
wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
} else {
downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
}
// wifiFileTransfer.wifiTransferService.optionRequest(completionHandler: { success in
//
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
// print("USB not in use.")
//
//
//
// } else {
// print("USB in use.")
// NotificationCenter.default.post(name: .usbInUseErrorNotification, object: nil, userInfo: nil)
// }
//
// })
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
testOperation()
} label: {
RunItButton()
@ -215,15 +239,15 @@ Press "Reset" on the device and use a battery source.
}
if wifiFileTransfer.downloadState == .transferring {
if wifiFileTransfer.testIndex.downloadState == .transferring {
DownloadingButton()
.padding(.top, 20)
.disabled(true)
VStack(alignment: .center, spacing: 5) {
ProgressView("", value: CGFloat(wifiFileTransfer.counter), total: CGFloat(wifiFileTransfer.numOfFiles) )
ProgressView("", value: CGFloat(counter), total: CGFloat(numOfFiles))
.padding(.horizontal, 90)
.padding(.top, -8)
.padding(.top, -5)
.padding(.bottom, 10)
.accentColor(Color.gray)
.scaleEffect(x: 1, y: 2, anchor: .center)
@ -232,26 +256,23 @@ Press "Reset" on the device and use a battery source.
ProgressView()
}
.onChange(of: wifiFileTransfer.counter) { i in
print("Wifi Project = \(result.projectName) index: \(wifiFileTransfer.counter)")
}
}
if wifiFileTransfer.downloadState == .complete {
if wifiFileTransfer.testIndex.downloadState == .complete {
CompleteButton()
.padding(.top, 20)
.disabled(true)
}
if wifiFileTransfer.downloadState == .failed {
if wifiFileTransfer.testIndex.downloadState == .failed {
FailedButton()
.padding(.top, 20)
.disabled(true)
}
}
}
} else {
@ -264,71 +285,87 @@ Press "Reset" on the device and use a battery source.
}
}
Spacer()
.frame(height: 30)
.ignoresSafeArea(.all)
.ignoresSafeArea(.all)
// .onChange(of: downloadModel.didDownloadBundle, perform: { newValue in
// print("For project: \(title), project download is \(newValue)")
//
// if newValue {
// DispatchQueue.main.async {
// print("Getting project from Subclass \(title)")
// // viewModel.getProjectForSubClass(nameOf: title)
// wifiFileTransfer.projectValidation(nameOf: title)
//
// isDownloaded = true
// }
// }else {
// print("Is not downloaded")
// isDownloaded = false
// }
//
// })
.onAppear(perform: {
viewModel.searchPathForProject(nameOf: result.projectName)
.onAppear(perform: {
// wifiFileTransfer.getProjectForSubClass(nameOf: title)
wifiFileTransfer.registerWifiNotification(enabled: true)
if viewModel.projectDownloaded {
isDownloaded = true
} else {
isDownloaded = false
viewModel.searchPathForProject(nameOf: result.projectName)
if viewModel.projectDownloaded {
isDownloaded = true
} else {
isDownloaded = false
}
print("is downloaded? \(viewModel.projectDownloaded)")
})
.padding(.top, 8)
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
if newValue {
showTransferErrorMessage()
}
})
.onChange(of: viewModel.showUsbInUseError) { newValue in
if newValue {
usbInUseErrorMessage()
}
}
print("is downloaded? \(viewModel.projectDownloaded)")
})
// .onAppear(perform: {
// DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
//
// wifiFileTransfer.wifiTransferService.optionRequest(completionHandler: { success in
//
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
// print("USB not in use.")
// } else {
// print("USB in use.")
// }
//
// })
//
// }
// })
.padding(.top, 8)
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
if newValue {
showTransferErrorMessage()
.onChange(of: wifiFileTransfer.counter) { newValue in
print("New counter : \(newValue)")
counter = newValue
}
})
.onChange(of: viewModel.usbInUseError) { newValue in
if newValue {
usbInUseErrorMessage()
.onChange(of: wifiFileTransfer.numOfFiles) { newValue in
print("New numOfFiles : \(newValue)")
numOfFiles = newValue
}
.onChange(of: wifiFileTransfer.testIndex.count) { newValue in
print("New count index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.numberOfFiles) { newValue in
print("New numberOfFiles index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.downloadState) { newValue in
print("New download state : \(newValue)")
}
.onChange(of: wifiFileTransfer.downloadState) { newValue in
switch newValue {
case .idle:
downloadState = .idle
print("idle")
case .transferring:
downloadState = .transferring
print("transferring")
case .complete:
downloadState = .complete
print("complete")
case .downloading:
downloadState = .downloading
print("downloading")
case .failed:
downloadState = .failed
print("failed")
}
}
}
}
}

View file

@ -6,28 +6,37 @@
//
import Foundation
import SwiftUI
class WifiSubViewCellModel: ObservableObject {
@ObservedObject var wifiTransferService = WifiTransferService()
@ObservedObject var wifiFileTransfer = WifiFileTransfer()
@Published var downloadState: DownloadState = .idle
@Published var projectDownloaded = false
@Published var failedProjectLaunch = false
@Published var usbInUseError = false
@Published var usbInUse = false
@Published var showUsbInUseError = false
init() {
registerNotification(enabled: true)
registerForUSBInUseErrorNotification(enabled: true)
}
private weak var usbInUseErrorNotification: NSObjectProtocol?
private func registerNotification(enabled: Bool) {
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)
// 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()})
@ -36,10 +45,63 @@ class WifiSubViewCellModel: ObservableObject {
}
}
func zipSuccess() {
usbInUseError = true
func zipSuccess() {
showUsbInUseError = true
}
func checkIfUSBInUse() {
wifiTransferService.optionRequest(handler: { result in
switch result {
case .success:
print("Success")
self.wifiTransferService.getRequest(read: "boot_out.txt") { result in
print(result)
}
case .failure:
print("Failure")
}
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
//
// print("USB not in use.")
// DispatchQueue.main.async {
// self.usbInUse = false
// }
//
// // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// // if wifiFileTransfer.projectDownloaded {
// //
// // wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
// //
// // } else {
// // downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
// // }
// // }
//
// } else {
// DispatchQueue.main.async {
// self.usbInUse = true
// }
// print("USB in use - files cannot be tranferred or moved while USB is in use. Show Error")
// }
})
}
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func searchPathForProject(nameOf project: String) {
@ -48,11 +110,11 @@ class WifiSubViewCellModel: ObservableObject {
let nestedFolderURL = directoryPath.appendingPathComponent(project)
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("\(project) - Exist")
print("\(project) - Exist")
projectDownloaded = true
} else {
print("Does not exist - \(project)")
projectDownloaded = false
projectDownloaded = false
}
}

View file

@ -16,7 +16,7 @@ protocol WifiTransferServiceDelegate: AnyObject {
class WifiTransferService: ObservableObject {
weak var delegate: WifiTransferServiceDelegate?
// weak var delegate: WifiTransferServiceDelegate?
let userDefaults = UserDefaults.standard
private let kPrefix = Bundle.main.bundleIdentifier!
@ -43,6 +43,7 @@ class WifiTransferService: ObservableObject {
startup()
}
//*
func sendPutRequest(fileName: String,
body: Data,
then handler: @escaping(Result<Data, Error>) -> Void) {
@ -89,16 +90,17 @@ class WifiTransferService: ObservableObject {
print("File write success!")
handler(.success(data))
}
// print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
func optionRequest(completionHandler: @escaping CompletionHandler) {
func requestWithCheck() {
}
func optionRequest(handler: @escaping(Result<String, Error>) -> Void) {
print("HOST | \(hostName)")
let username = ""
@ -111,14 +113,11 @@ class WifiTransferService: ObservableObject {
let base64LoginString = loginData.base64EncodedString()
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = "OPTIONS"
request.httpBody = try? JSONSerialization.data(withJSONObject: [:], options: [])
//request.httpBody = try? JSONSerialization.data(withJSONObject: [:], options: [])
print("Print curl:")
@ -132,29 +131,31 @@ class WifiTransferService: ObservableObject {
print("Specific header: \(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found")")
completionHandler(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
}
do {
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("Output:")
print(str)
print("Output: \(str)")
}
}
task.resume()
}
@ -213,8 +214,8 @@ class WifiTransferService: ObservableObject {
typealias CompletionHandler = (_ success:String) -> Void
func getRequest(read: String, completionHandler: @escaping CompletionHandler){
func getRequest(read: String, completionHandler: @escaping CompletionHandler) {
print("Second network call made!")
var semaphore = DispatchSemaphore (value: 0)
let username = ""
@ -244,9 +245,9 @@ class WifiTransferService: ObservableObject {
print(String(describing: "Error Found: \(error)"))
return
}
// print(String(data: data, encoding: .utf8)!)
do {
print("In do-catch loop of getRequest")
let wifiIncomingData = try JSONDecoder().decode([WebDirectoryModel].self, from: data)
DispatchQueue.main.async {
@ -257,7 +258,7 @@ class WifiTransferService: ObservableObject {
}
if let str = String(data: data, encoding: .utf8) {
print(str)
print("Out-going getRequest data: \(str)")
outgoingString = str
print("\(#function) @Line: \(#line)")
completionHandler(outgoingString)
@ -271,6 +272,41 @@ class WifiTransferService: ObservableObject {
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
@ -340,7 +376,6 @@ class WifiTransferService: ObservableObject {
// func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
func putRequest(fileName: String, fileContent: Data, completion: @escaping (Result<Data?, Error>) -> Void) {
print("Test Transfer")
let parameters = fileContent
let postData = parameters
@ -378,14 +413,15 @@ class WifiTransferService: ObservableObject {
completion(.success(data))
}
// print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
// Make
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
print("\(#function) @Line: \(#line)")
let username = ""
let password = "passw0rd"
@ -408,12 +444,20 @@ class WifiTransferService: ObservableObject {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
if let error = error {
print("Write Directory Failure")
completion(.failure(error))
}
if let data = data {
print("Write Directory Success")
completion(.success(data))
}
@ -438,7 +482,7 @@ class WifiTransferService: ObservableObject {
let base64LoginString = loginData.base64EncodedString()
// var request = URLRequest(url: URL(string: "http://cpy-9cbe10.local/fs/")!,timeoutInterval: Double.infinity)
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/testing.txt")!,timeoutInterval: Double.infinity)
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/test.txt")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")

View file

@ -5,22 +5,19 @@
// Created by Trevor Beaton on 8/9/22.
//
// My IP Address - 192.168.1.111
import SwiftUI
import Combine
struct WifiView: View {
@Environment(\.dismiss) private var dismiss
@StateObject var viewModel = WifiViewModel()
private let kPrefix = Bundle.main.bundleIdentifier!
// User Defaults
let userDefaults = UserDefaults.standard
@EnvironmentObject var rootViewModel: RootViewModel
@ObservedObject var networkModel = NetworkService()
@ObservedObject var wifiServiceViewModel = WifiServiceManager()
@State private var downloadState = DownloadState.idle
@State private var scrollViewID = UUID()
@ -28,15 +25,22 @@ struct WifiView: View {
@State private var boardBootInfo = "esp32-s2"
@State var hostName = ""
@EnvironmentObject var test : ExpandedState
@State var falseTog = false
@State var trueTog = true
@State private var showPopover: Bool = false
func toggleViewModelIP() {
viewModel.isInvalidIP.toggle()
}
func fetch() {
// viewModel.networkModel.fetch()
}
func scanNetworkWifi() {
viewModel.wifiServiceManager.findService()
@ -67,12 +71,12 @@ struct WifiView: View {
hintText: "IP Address...",
primaryTitle: "Done",
secondaryTitle: "Cancel") { text in
viewModel.checkServices(ip: text)
viewModel.checkServices(ip: text)
} secondaryAction: {
print("Cancel")
}
}
} secondaryAction: {
print("Cancel")
}
}
func showAlertMessage() {
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
@ -87,87 +91,54 @@ struct WifiView: View {
Group{
switch viewModel.connectionStatus {
case .connected:
case .connected:
WifiStatusConnectedView(hostName: $hostName)
case .noConnection:
WifiStatusNoConnectionView()
case .connecting:
WifiStatusConnectingView()
}
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)
// .padding(5)
// }
//
// Button {
// viewModel.wifiTransferService.getRequestForFileCheck(read: "lib/") { success in
// printArray(array: success)
// }
// } label: {
// Text("List all files")
// .foregroundColor(.white)
// .background(.indigo)
// .padding(5)
// }
//
// Button {
// scanNetworkWifi()
// } label: {
// Text("Scan Network")
// .foregroundColor(.white)
// .background(.indigo)
// .padding(5)
// }
//
// Button {
// rootViewModel.goTobluetoothPairing()
// } label: {
// Text("BLE Mode")
// .foregroundColor(.white)
// .background(.indigo)
// .padding(5)
// }
//
// })
// .padding(.all, 0.0)
// .frame(maxWidth: .infinity)
// .frame(maxHeight: 40)
// .background(Color.clear)
// .foregroundColor(.black)
// }
ScrollView(.vertical, showsIndicators: true) {
ScrollView(.vertical, showsIndicators: false) {
ScrollViewReader { scroll in
SubHeaderView()
let check = networkModel.pdemos.filter {
let check = viewModel.pdemos.filter {
$0.compatibility.contains(boardBootInfo)
}
ForEach(check) { demo in
WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
if demo.bundleLink == test.currentCell {
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
})
.onAppear(){
withAnimation {
scroll.scrollTo(demo.id)
}
}
})
} else {
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
withAnimation {
// scroll.scrollTo(demo.id)
}
})
}
}
}
@ -175,12 +146,9 @@ struct WifiView: View {
.id(self.scrollViewID)
}
.foregroundColor(.black)
.environmentObject(test)
}
.onDisappear() {
print("On Disappear")
dismiss()
}
.onChange(of: viewModel.connectionStatus, perform: { newValue in
@ -228,6 +196,7 @@ struct WifiView: View {
struct WifiView_Previews: PreviewProvider {
static var previews: some View {
WifiView()
}
}

View file

@ -35,8 +35,6 @@ class WifiViewModel: ObservableObject {
@Published var wifiServiceManager = WifiServiceManager()
// @ObservedObject var networkModel = NetworkService()
var circuitPythonVersion = Int()
@Published var webDirectoryInfo = [WebDirectoryModel]()
@ -45,6 +43,11 @@ class WifiViewModel: ObservableObject {
@Published var downloadState: DownloadState = .idle
let dataStore = DataStore()
@Published var pdemos : [ResultItem] = []
// File Manager Data
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
@ -56,7 +59,7 @@ class WifiViewModel: ObservableObject {
var ipAddressStored = false
init() {
pdemos = dataStore.loadDefaultList()
checkIP()
registerNotifications(enabled: true)
wifiServiceManager.findService()
@ -70,9 +73,6 @@ class WifiViewModel: ObservableObject {
@Published var pyleapProjects = [ResultItem]()
func appendPyleapProjects() {
}
func read() {
// This method can't be used until the device has permission to communicate.
@ -92,6 +92,8 @@ class WifiViewModel: ObservableObject {
}
}
private weak var invalidIPObserver: NSObjectProtocol?
private weak var testObserver: NSObjectProtocol?