Compare commits

..

No commits in common. "master" and "Remove-System-Back-button" have entirely different histories.

50 changed files with 1357 additions and 1901 deletions

View file

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

View file

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

View file

@ -11,12 +11,11 @@
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 */; };
@ -28,10 +27,9 @@
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 */; };
@ -53,8 +51,6 @@
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 */; };
@ -118,12 +114,11 @@
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>"; };
@ -133,10 +128,9 @@
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>"; };
@ -162,8 +156,6 @@
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>"; };
@ -360,30 +352,17 @@
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 */,
@ -393,9 +372,12 @@
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 */,
@ -412,6 +394,7 @@
D59DFDB2268CCEAC001737F6 /* Views */ = {
isa = PBXGroup;
children = (
D567E2DD28C8D3E20009F768 /* SettingsView */,
D58E1C8628A2B0DE00AB683E /* Wifi View */,
D5507ACE26C668BC00512BAA /* UI Components */,
D59DFDB3268CCEB9001737F6 /* Onboarding Views */,
@ -490,11 +473,9 @@
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>";
@ -503,7 +484,6 @@
isa = PBXGroup;
children = (
D5D1F4B127ECFF760040E2BF /* ProjectsModel.swift */,
D5361099296FB2BB00228E15 /* DataStore.swift */,
D5D7DF2C28B489C0008552D1 /* WebDirectoryModel.swift */,
);
path = Model;
@ -663,7 +643,6 @@
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 */,
@ -693,9 +672,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 */,
@ -703,6 +682,7 @@
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 */,
@ -711,7 +691,6 @@
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 */,
@ -720,7 +699,6 @@
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 */,
@ -728,7 +706,6 @@
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 */,
@ -740,7 +717,6 @@
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 */,
@ -881,17 +857,17 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 0;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.1;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;
@ -911,17 +887,17 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = PyLeap/PyLeap.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 0;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"PyLeap/Preview Content\"";
DEVELOPMENT_TEAM = 2X94RM7457;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = PyLeap/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.1;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.PyLeap;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;

View file

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

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,8 +126,6 @@ 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 {
@ -185,8 +183,6 @@ class DownloadViewModel: NSObject, ObservableObject, URLSessionDownloadDelegate
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
print(error)
@ -200,8 +196,6 @@ 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")
@ -230,9 +224,21 @@ 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()
}
@ -274,6 +280,7 @@ 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 Adafruit Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
PageView(title: "Send projects directly from the Adafruit Learning System to your Bluefruit Compatible Device...", subtitle: "...without opening a code editor or connecting to a computer.", imageName: "slide4", showDismissButton: true, shouldShowOnboarding: $shouldShowOnboarding)
}
.tabViewStyle(PageTabViewStyle())
}

View file

@ -20,6 +20,11 @@
<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>
@ -28,11 +33,6 @@
<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,6 +64,10 @@
<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

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

View file

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

View file

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

View file

@ -14,9 +14,232 @@ import Foundation
import SwiftUI
class NetworkService: ObservableObject {
let dataStore = DataStore()
let thirdPartyBackgroundQueue = DispatchQueue(label: "com.PyLeap.thirdPartyBackgroundQueue", qos: .background, attributes: .concurrent)
static let shared = NetworkService()
@Published var pdemos = [ResultItem]()
@State var storedURL = ""
let userDefaults = UserDefaults.standard
init(){
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)")
}
}
}
}
private var dataTask: URLSessionDataTask?
@ -26,16 +249,24 @@ class NetworkService: ObservableObject {
// Session Configuration & Caching Policy
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
// configuration.requestCachePolicy = .useProtocolCachePolicy
return URLSession(configuration: configuration)
}()
@Published var projectInfo = Data()
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
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
if let error = error {
print("error: \(error)")
@ -47,82 +278,94 @@ class NetworkService: ObservableObject {
if let projectData = try? JSONDecoder().decode(RootResults.self, from: data) {
DispatchQueue.main.async {
self.dataStore.save(content: projectData.projects, completion: self.dataStore.loadDefaultProjectList)
completion()
// for i in projectData.projects {
// print("\(i.projectName)")
// }
self.save(content: projectData.projects)
self.load()
}
} else {
print("No data found")
}
} else {
print("Updating UIList with Cached data...")
DispatchQueue.main.async {
self.dataStore.loadDefaultProjectList()
completion()
self.load()
}
}
}
task.resume()
}
func fetchThirdPartyProject(urlString: String?) {
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 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()
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
}
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)")
NotificationCenter.default.post(name: .invalidCustomNetworkRequest, object: nil, userInfo: nil)
return
}
if let data = data {
print("Updating UIList with new data...")
let projectData = try? JSONDecoder().decode(RootResults.self, from: data)
if let projects = projectData?.projects {
DispatchQueue.main.async {
print(#function)
for i in projects {
print("\(i.projectName)")
}
// self.pdemos.append(contentsOf: projects)
// self.saveCustomProjects(content: projects)
self.save(customProjects: projects)
}
}
}
}
task.resume()
}
}

View file

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

BIN
PyLeap/Views/.DS_Store vendored

Binary file not shown.

View file

@ -295,8 +295,11 @@ 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: "App-Prefs:root=Bluetooth")!)
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
} label: {
ZStack {

View file

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

View file

@ -128,9 +128,6 @@ 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
}
}
}
}
@ -564,10 +561,8 @@ 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,10 +8,6 @@
import SwiftUI
import FileTransferClient
class ExpandedBLECellState: ObservableObject {
@Published var currentCell = ""
}
struct BleModuleView: View {
// Data
@ -26,30 +22,31 @@ 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() {
@ -262,7 +259,7 @@ struct BleModuleView: View {
ScrollView(.vertical, showsIndicators: false) {
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scroll in
@ -276,33 +273,20 @@ struct BleModuleView: View {
}
let check = viewModel.pdemos.filter {
let check = networkService.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: {
})
}
})
}
@ -310,7 +294,7 @@ struct BleModuleView: View {
.id(self.scrollViewID)
}
.environmentObject(expandedState)
}
}

View file

@ -18,16 +18,6 @@ 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: {
SwiftUIWebView(webAddress: result.learnGuideLink)
WebView(URLRequest(url: URL(string: result.learnGuideLink)!))
})

View file

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

View file

@ -11,8 +11,6 @@ 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
@ -62,17 +60,12 @@ struct RootView: View {
case .settings:
SettingsView()
case .bleSettings:
BLESettingsView()
default:
FillerView()
}
}
.environmentObject(currentCellID)
.environmentObject(currentBLECellID)
.onReceive(NotificationCenter.default.publisher(for: .didUpdateBleState)) { notification in
@ -90,25 +83,26 @@ struct RootView: View {
.onChange(of: connectionManager.isSelectedPeripheralReconnecting) { isConnectedOrReconnecting in
if isConnectedOrReconnecting, model.destination == .fileTransfer {
model.destination = .fileTransfer
isReconnecting = true
} else {
isReconnecting = false
}
}
.onChange(of: connectionManager.isDisconnectingFromCurrent) { isDisconnected in
if isDisconnected {
print("Is disconnected.")
isReconnecting = false
connectionManager.clearAllPeripheralInfo()
connectionManager.peripherals = []
connectionManager.isDisconnectingFromCurrent = false
model.destination = .selection
connectionManager.clearAllPeripheralInfo()
connectionManager.isDisconnectingFromCurrent = false
isReconnecting = false
}
}

View file

@ -22,13 +22,11 @@ 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
@ -76,10 +74,6 @@ public class RootViewModel: ObservableObject {
}
}
func backToMain() {
destination = .main
}
func goToStartup(){
destination = .startup
}
@ -92,14 +86,10 @@ public class RootViewModel: ObservableObject {
destination = .fileTransfer
}
func goToSettings(content: SettingState){
func goToSettings(){
destination = .settings
}
func goToBLESettings(){
destination = .bleSettings
}
func showWarningIfBluetoothStateIsNotReady() {
let bluetoothState = BleManager.shared.state
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized

View file

@ -0,0 +1,95 @@
//
// 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

@ -7,30 +7,34 @@
import SwiftUI
enum SettingState {
case ble
case wifi
case none
}
struct SettingsView: View {
// @State public var appState: SettingState = .none
@State private var thirdPartyLink: String = ""
@State private var jsonFileName: String = ""
@State private var pythonFileName: String = ""
@State private var presentJSONAlert = false
@State private var presentPythonAlert = false
@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") {
}
}
@ -39,46 +43,126 @@ struct SettingsView: View {
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)
VStack {
Form {
Section{
Label("[Go to GitHub](https://github.com/adafruit/pyleap.github.io)", systemImage: "link")
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")
}
}
header: {
Text("""
Find more information on adding your own project here:
""")
}
Section{
Label("[Go to Adafruit.com](https://www.adafruit.com)", systemImage: "link")
@ -87,6 +171,9 @@ Find more information on adding your own project here:
}
.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()
@ -104,9 +191,9 @@ Find more information on adding your own project here:
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
// .font(.system(size: 18, weight: .regular, design: .default))
.foregroundColor(.blue)
}
.padding(12)
@ -118,44 +205,14 @@ Find more information on adding your own project here:
.safeAreaInset(edge: .top) {
VStack {
HStack {
Button {
rootViewModel.goToWifiView()
} label: {
Text("Back")
}
.padding(.leading, 20)
Spacer()
// switch appState {
//
// case .ble:
// Button {
// rootViewModel.goToFileTransfer()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .wifi:
// Button {
// rootViewModel.goToWifiView()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
//
// case .none:
// Button {
// rootViewModel.goToMainSelection()
// } label: {
// Text("Back")
// }
// .padding(.leading, 20)
// Spacer()
// }
.padding(.leading,20)
Spacer()
}
.frame(height: UIScreen.main.bounds.height / 19)
@ -163,18 +220,24 @@ Find more information on adding your own project here:
HStack {
Text("Settings")
.font(Font.custom("ReadexPro-Bold", size: 32))
.font(.largeTitle)
.bold()
.padding(.leading,20)
Spacer()
}
.background(Color(.systemGroupedBackground))
}
.padding(.top, 25)
}
}
}
extension View {
func comfirmationAlertMessage(title: String, exitTitle: String, primaryTitle: String,disconnect: @escaping() -> (),cancel: @escaping() -> ()){
@ -188,6 +251,8 @@ extension View {
cancel()
}))
rootController().present(alert, animated: true, completion: nil)
}

View file

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

View file

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

View file

@ -10,43 +10,20 @@ import SwiftUI
struct HeaderView: View {
@State var showSheetView = false
@EnvironmentObject var rootViewModel: RootViewModel
var body: some View {
HStack (alignment: .center, spacing: 0) {
HStack {
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.padding(.leading, CGFloat(20))
.foregroundColor(.clear)
Spacer()
Image("pyleap_logo_white")
.resizable()
.scaledToFit()
.frame(width: 125, height: 125)
.offset(y: 12)
Spacer()
Button {
rootViewModel.goToBLESettings()
} label: {
Image(systemName: "plus")
Image("pyleap_logo_white")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)
.padding(.trailing, CGFloat(20))
.foregroundColor(.white)
.scaledToFit()
.frame(width: 125, height: 125)
.offset(y: 12)
}
}
.frame(maxWidth: .infinity)
.frame(maxHeight: 120)
@ -101,7 +78,6 @@ 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 WiFi PyLeap Projects")
Text("Browse available Wi-Fi 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(content: .wifi)
rootViewModel.goToSettings()
} label: {
Image(systemName: "plus")
Image(systemName: "gearshape")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.offset(y: 15)

View file

@ -3,7 +3,7 @@
// PyLeap
//
// Created by Trevor Beaton on 10/16/21.
//
//
import SwiftUI
import FileTransferClient
@ -14,31 +14,51 @@ 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()
@State private var isConnected = false
@State private var test = ""
@State private var nilBinder = DownloadState.idle
@EnvironmentObject var rootViewModel: RootViewModel
@AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
var body: some View {
VStack(alignment: .center, spacing: 0) {
MainHeaderView()
// HStack(alignment: .center, spacing: 8, content: {
//
// Button {
// rootViewModel.goToWifiView()
//
// } label: {
// Text("Connect to Wi-Fi Mode")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// .underline()
// .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))
@ -49,19 +69,22 @@ enum AdafruitDevices {
.font(Font.custom("ReadexPro-Regular", size: 16))
.underline()
}
})
.padding(.all, 0.0)
.frame(maxWidth: .infinity)
.frame(maxHeight: 40)
.background(Color("pyleap_burg"))
.foregroundColor(.white)
ScrollView {
MainSubHeaderView(device: "Adafruit device")
if viewModel.pdemos.isEmpty {
if networkModel.pdemos.isEmpty {
HStack{
Spacer()
ProgressView()
@ -69,48 +92,26 @@ enum AdafruitDevices {
Spacer()
}
.padding(0)
}
ScrollViewReader { scroll in
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)
}
ForEach(networkModel.pdemos) { demo in
DemoViewCell(result: demo, isConnected: $isConnected, deviceInfo: $test, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
}
} else {
DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
})
}
})
}
}
}
}
.onDisappear() {
}
/// **Pull down to Refresh feature**
// ScrollRefreshableView(title: "Refresh", tintColor: .purple) {
// HStack{
@ -136,19 +137,19 @@ enum AdafruitDevices {
// }
//
// }
.onChange(of: viewModel.pdemos, perform: { newValue in
print("Update")
})
.onAppear() {
// networkModel.fetch()
print("Opened MainSelectionView")
}
.fullScreenCover(isPresented: $shouldShowOnboarding, content: {
ExampleView(shouldShowOnboarding: $shouldShowOnboarding)
})
@ -156,91 +157,16 @@ enum AdafruitDevices {
.background(Color.white)
.navigationBarColor(UIColor(named: "pyleap_gray"))
.navigationBarTitleDisplayMode(.inline)
}
}
//struct MainSelectionView_Previews: PreviewProvider {
// static var previews: some View {
// MainSelectionView()
// }
//}
//
//struct MainSelectionView: View {
//
// @State private var showWebViewPopover: Bool = false
// @ObservedObject var networkModel = NetworkService()
// @ObservedObject var viewModel = MainSelectionViewModel()
// @State private var test = ""
// @State private var nilBinder = DownloadState.idle
// @EnvironmentObject var rootViewModel: RootViewModel
// @AppStorage("shouldShowOnboarding") var shouldShowOnboarding: Bool = true
//
// var body: some View {
// VStack(alignment: .center, spacing: 0) {
// MainHeaderView()
// connectionMessageView()
// ScrollView {
// MainSubHeaderView(device: "Adafruit device")
// if networkModel.pdemos.isEmpty {
// loadingIndicatorView()
// }
// ScrollViewReader { scroll in
// ForEach(networkModel.pdemos) { demo in
// demoViewCell(demo: demo, scroll: scroll)
// }
// }
// }
// }
// }
//
// private func connectionMessageView() -> some View {
// HStack(alignment: .center, spacing: 8) {
// Text("Not Connected to a Device.")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// Button {
// rootViewModel.goToSelection()
// } label: {
// Text("Connect Now")
// .font(Font.custom("ReadexPro-Regular", size: 16))
// .underline()
// }
// }
// .padding(.all, 0.0)
// .frame(maxWidth: .infinity)
// .frame(maxHeight: 40)
// .background(Color("pyleap_burg"))
// .foregroundColor(.white)
// }
//
// private func loadingIndicatorView() -> some View {
// HStack{
// Spacer()
// ProgressView()
// .scaleEffect(2)
// Spacer()
// }
// .padding(0)
// }
//
// private func demoViewCell(demo: Demo, scroll: ScrollViewProxy) -> some View {
// if demo.bundleLink == expandedState.currentCell {
// DemoViewCell(result: demo, isExpanded: true, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
// })
// .onAppear(){
// print("Cell Appeared")
// withAnimation {
// scroll.scrollTo(demo.id)
// }
// }
// } else {
// DemoViewCell(result: demo, isExpanded: false, isConnected: $inConnectedInSelectionView, deviceInfo: $boardBootInfo, onViewGeometryChanged: {
// })
// }
// }
//}
struct MainSelectionView_Previews: PreviewProvider {
static var previews: some View {
MainSelectionView()
}
}

View file

@ -7,82 +7,42 @@
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 {
@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()
let userDefaults = UserDefaults.standard
@Published var pdemos : [ResultItem] = []
var networkMonitorCancellable: AnyCancellable?
init() {
let fileURL = documentsDirectory.appendingPathComponent("StandardPyLeapProjects.json")
networkMonitorCancellable = networkMonitor.$isConnected.sink { isConnected in
if isConnected {
print("The device is currently connected to the internet.")
// Perform some action when the device is connected to the internet.
self.networkModel.fetch {
self.pdemos = self.dataStore.loadDefaultList()
}
} else {
print("The device is not currently connected to the internet.")
// Perform some action when the device is not connected to the internet.
print("Loading cached remote data.")
self.pdemos = self.dataStore.loadDefaultList()
}
}
// load()
// self.pdemos = load()
}
func makeNetworkCall(){
// networkModel.fetch()
}
func load() -> [ResultItem] {
if let savedProjects = userDefaults.object(forKey: "SavedProjects") as? Data {
let decoder = JSONDecoder()
if let loadedProjects = try? decoder.decode([ResultItem].self, from: savedProjects) {
print("Load saved projects")
print(loadedProjects)
// pdemos = loadedProjects
return loadedProjects
}
}
print("Returned Empty pdemos")
return []
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -8,37 +8,22 @@
import SwiftUI
import Foundation
class ExpandedState: ObservableObject {
@Published var currentCell = ""
}
struct WifiCell: View {
@EnvironmentObject var expandedState : ExpandedState
let result : ResultItem
@State var isExpanded: Bool = false {
@State private 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 {
@ -46,53 +31,42 @@ struct WifiCell: View {
.frame(maxWidth: .infinity)
}
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 {
private 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

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

View file

@ -11,91 +11,8 @@ 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
@ -107,8 +24,6 @@ class WifiFileTransfer: ObservableObject {
@Published var transferError = false
@Published var stopTransfer = false
@Published var downloadState: DownloadState = .idle
var manager = FileManager.default
@ -130,8 +45,6 @@ class WifiFileTransfer: ObservableObject {
}
}
func restFileCounter() {
DispatchQueue.main.async {
self.counter = 0
@ -153,25 +66,18 @@ class WifiFileTransfer: ObservableObject {
projectFiles.removeAll()
}
func showFailedButton() {
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
}
}
init() {
registerNotification(enabled: true)
}
private weak var wifiDownloadComplete: NSObjectProtocol?
private weak var didEncounterTransferError: NSObjectProtocol?
private weak var downloadErrorDidOccur: NSObjectProtocol?
func registerWifiNotification(enabled: Bool) {
private func registerNotification(enabled: Bool) {
print("\(#function) @Line: \(#line)")
let notificationCenter = NotificationCenter.default
@ -187,7 +93,6 @@ class WifiFileTransfer: ObservableObject {
} else {
print("Else testObserver")
if let testObserver = wifiDownloadComplete {notificationCenter.removeObserver(testObserver)}
}
}
@ -222,7 +127,6 @@ class WifiFileTransfer: ObservableObject {
}
func getProjectForSubClass(nameOf project: String) {
@ -253,18 +157,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 within testFileExistance")
print("Exist")
removeDirectoryAndFiles()
startFileTransfer(url: nestedFolderURL)
@ -284,10 +188,22 @@ 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])
@ -309,6 +225,8 @@ class WifiFileTransfer: ObservableObject {
} else {
if isDirectory {
print("Directories Found")
print(fileURL.lastPathComponent)
if name == "_extras" {
dirEnumerator.skipDescendants()
}
@ -328,8 +246,7 @@ class WifiFileTransfer: ObservableObject {
}
}
}
print("times newMakeDirectory was called here")
newMakeDirectory(directoryArray: filterOutCPDirectories(urls: projectDirectories), regularFilesArray: filterOutCPDirectories(urls: projectFiles))
}
@ -371,7 +288,6 @@ class WifiFileTransfer: ObservableObject {
}
func removeNonCPDirectories(urls: [URL]) -> [URL] {
print("removeNonCPDirectories called")
var mutableURLList = urls
var outgoingArray = [URL]()
@ -382,6 +298,7 @@ class WifiFileTransfer: ObservableObject {
} else {
print("Removed non-CP Directories: \(i)")
}
}
return outgoingArray
@ -389,117 +306,105 @@ 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)
print("directoryArray Count : \(directoryArray.count)")
printArray(array: directoryArray)
print("recursiveArray Count : \(recursiveArray.count)")
printArray(array: recursiveArray)
if recursiveArray.count == 0 {
if recursiveArray.isEmpty {
print("newMakeDirectory is empty!")
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!)")
var tempPath = recursiveArray.first!.pathComponents
if tempPath.contains("CircuitPython 7.x") {
var indexOfCP = 0
print("Found CircuitPython 8.x")
indexOfCP = recursiveArray[0].pathComponents.firstIndex(of: "CircuitPython 7.x")!
tempPath.removeSubrange(0...indexOfCP)
let joined = tempPath.joined(separator: "/")
print("Outgoing path:\(joined)")
print(indexOfCP)
print(recursiveArray[0].pathComponents.count)
wifiTransferService.putDirectory(directoryPath: joined) { result in
switch result {
case .success(let consent):
print("Successful")
recursiveArray.removeFirst()
self.newMakeDirectory(directoryArray: recursiveArray, regularFilesArray: regularFilesArray)
case .failure(let error):
print("Error: \(error)")
}
}
}
if tempPath.contains("CircuitPython 8.x") {
var indexOfCP = 0
print("For \(recursiveArray[0].absoluteString)")
print("Count \(tempPath.count)")
indexOfCP = recursiveArray[0].pathComponents.firstIndex(of: "CircuitPython 8.x")!
print("indexOfCP \(indexOfCP)")
print(tempPath)
tempPath.removeSubrange(0...indexOfCP)
let joined = tempPath.joined(separator: "/")
print("Outgoing path:\(joined)")
print(indexOfCP)
print(recursiveArray[0].pathComponents.count)
wifiTransferService.putDirectory(directoryPath: joined) { result in
switch result {
case .success(let consent):
print("Successful")
recursiveArray.removeFirst()
self.newMakeDirectory(directoryArray: recursiveArray, regularFilesArray: regularFilesArray)
case .failure(let error):
print("Error: \(error)")
}
}
}
}
}
@ -555,26 +460,17 @@ class WifiFileTransfer: ObservableObject {
func completedTransfer() {
print("Is main queue: \(Thread.isMainThread)")
DispatchQueue.main.asyncAfter(deadline: .now()) { [self] in
DispatchQueue.main.async {
print("\(#function) @Line: \(#line)")
downloadState = .complete
testIndex.downloadState = .complete
counter = 0
testIndex.count = 0
testIndex.numberOfFiles = 0
numOfFiles = 0
print("downloadState")
print(downloadState)
}
self.downloadState = .complete
self.counter = 0
self.numOfFiles = 0
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
print("\(#function) @Line: \(#line)")
self.downloadState = .idle
self.testIndex.backToIdle()
}
}
@ -582,8 +478,6 @@ 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")!
@ -618,28 +512,16 @@ 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)")
print("Counter: \(counter)")
print("\(#function) @Line: \(#line)")
print("Status: \(downloadState)")
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.completedTransfer()
}
} else {
@ -649,66 +531,74 @@ class WifiFileTransfer: ObservableObject {
}
// || copiedArray.first?.lastPathComponent == "README.txt"
if copiedArray.first?.lastPathComponent == "code.py" {
wifiTransferService.sendPutRequest(fileName: copiedArray.first!.lastPathComponent , body: data) { result in
switch result {
case .success(_):
print("Success\n")
print("Removing: \(copiedArray.first!.lastPathComponent)\n")
DispatchQueue.main.async {
self.counter += 1
}
copiedArray.removeFirst()
self.makeFile(files: copiedArray)
case .failure(_):
print("Failed to write")
}
}
} else {
print("ELSE")
guard let data = try? Data(contentsOf: URL(string: copiedArray.first!.absoluteString)!) else {
print("File not found")
return
}
print("Checking for URL: \(copiedArray.first?.lastPathComponent)")
wifiTransferService.getRequestForFileCheck(read: checkForExistingFilesOnBoard(url: copiedArray.first!.absoluteURL)) { success in
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)
} else {
self.wifiTransferService.sendPutRequest(fileName: self.makeFileString(url: copiedArray.first!), body: data) { result in
switch result {
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(_):
print("Failed to write")
}
}
}
}
}
}
}

View file

@ -6,31 +6,13 @@
//
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
@ -38,18 +20,14 @@ 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 {
Button {
self.rootViewModel.goToWiFiSelection()
} label: {
Image(systemName: "arrow.backward")
@ -58,96 +36,73 @@ struct WifiPairingView: View {
.frame(width: 25, height: 25, alignment: .center)
.foregroundColor(Color("pyleap_gray"))
}
Spacer()
Image(systemName: "wifi")
.resizable()
.foregroundColor(.clear)
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(y: -5)
}
.padding(.top, 50)
.padding(.horizontal, 30)
//
//
//
// Image("pyleapLogo")
// .resizable()
// .aspectRatio(contentMode: .fit)
// .padding(.top, 50)
// .padding(.horizontal, 60)
//
//
// if nextText == 0 {
//
//
//
// Text("""
// PyLeaps WiFi mode requires EPS32 devices to have WiFi credentials in an ./env file.
//
// If youre having trouble connecting, check this documentation:
//
// https://docs.circuitpython.org/en/latest/docs/workflows.html#web
//
// """)
// .font(Font.custom("ReadexPro-Regular", size: 24))
// .minimumScaleFactor(0.01)
// .multilineTextAlignment(.leading)
// .padding(.top, 100)
// .padding(.horizontal, 30)
// .padding(.bottom, 69)
//
//
// Spacer()
//
// Button(action: {
// rootViewModel.goToWiFiSelection()
// }) {
//
// Text("Back")
// .font(Font.custom("ReadexPro-Regular", size: 25))
// .foregroundColor(Color.white)
//
// .padding()
// .padding(.horizontal, 60)
//
// .frame(height: 50)
// .background(Color("pyleap_purple"))
// .clipShape(Capsule())
//
// }
// Spacer()
// .frame(height: 60)
//
// }
SwiftUIWebView(webAddress: tutorialAddress)
.padding(.vertical)
Button(action: {
rootViewModel.goToWiFiSelection()
}) {
Text("Back")
.font(Font.custom("ReadexPro-Regular", size: 25))
.foregroundColor(Color.white)
Image("pyleapLogo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.top, 50)
.padding(.horizontal, 60)
.padding()
.padding(.horizontal, 60)
.frame(height: 50)
.background(Color("pyleap_purple"))
.clipShape(Capsule())
}
.padding()
if nextText == 0 {
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)
}
}
.edgesIgnoringSafeArea(.all)

View file

@ -0,0 +1,26 @@
//
// 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,16 +38,10 @@ 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() {
@ -183,16 +177,7 @@ extension WifiServiceManager: NetServiceBrowserDelegate, NetServiceDelegate {
let resolvedService = ResolvedService(ipAddress: ipAddress, hostName: updatedHostName ?? "Unknown", device: sender.name)
if resolvedServices.contains(where: {$0.ipAddress == resolvedService.ipAddress}) {
// it exists, do nothing
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) exists in network")
} else {
print("\(resolvedService.ipAddress) for \(resolvedService.hostName) Added to Network List")
resolvedServices.append(resolvedService)
}
resolvedServices.append(resolvedService)
print("resolvedServices count: \(resolvedServices.count)")
}

View file

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

View file

@ -8,30 +8,25 @@
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
@State private var toggleView: Bool = false
@EnvironmentObject var rootViewModel: RootViewModel
@StateObject var downloadModel = DownloadViewModel()
@ObservedObject var viewModel = WifiSubViewCellModel()
@StateObject 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
@ -45,7 +40,6 @@ struct WifiSubViewCell: View {
alertMessage(title: """
Download Error
Unable to download project
Try again later
""", exitTitle: "Retry") {
wifiFileTransfer.transferError = false
}
@ -55,99 +49,58 @@ Try again later
alertMessage(title: """
USB In Use
Files cannot be tranferred or moved while USB is in use.
Disconnect device from the computer.
Remove device from USB. Press "Reset" on the device.
Press "Reset" on the device and use a battery source.
""", 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)
.cornerRadius(14)
.padding(.top, 30)
Text(result.description)
.font(Font.custom("ReadexPro-Regular", size: 18))
@ -157,7 +110,7 @@ Remove device from USB. Press "Reset" on the device.
Text("Compatible with:")
.font(Font.custom("ReadexPro-Bold", size: 18))
.padding(.top, 5)
HStack {
Image(systemName: "checkmark")
.resizable()
@ -169,7 +122,6 @@ Remove device from USB. Press "Reset" on the device.
}
.padding(.top, 10)
ForEach(result.compatibility, id: \.self) { string in
if string == "circuitplayground_bluefruit" {
@ -185,7 +137,7 @@ Remove device from USB. Press "Reset" on the device.
.padding(.top, 10)
}
if string == "clue_nrf52840_express" {
@ -213,24 +165,48 @@ Remove device from USB. Press "Reset" on the device.
.padding(.top, 20)
}
.sheet(isPresented: $showWebViewPopover, content: {
SwiftUIWebView(webAddress: result.learnGuideLink)
WebView(URLRequest(url: URL(fileURLWithPath: result.learnGuideLink)))
})
.onAppear(){
wifiFileTransfer.counter = 0
wifiFileTransfer.numOfFiles = 0
}
if isConnected {
if result.compatibility.contains(bindingString) {
if wifiFileTransfer.testIndex.downloadState == .idle {
if wifiFileTransfer.downloadState == .idle {
Button {
// NotificationCenter.default.post(name: .didCompleteZip, object: nil, userInfo: projectResponse)
testOperation()
// 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)
// }
//
// })
} label: {
RunItButton()
@ -239,15 +215,15 @@ Remove device from USB. Press "Reset" on the device.
}
if wifiFileTransfer.testIndex.downloadState == .transferring {
if wifiFileTransfer.downloadState == .transferring {
DownloadingButton()
.padding(.top, 20)
.disabled(true)
VStack(alignment: .center, spacing: 5) {
ProgressView("", value: CGFloat(counter), total: CGFloat(numOfFiles))
ProgressView("", value: CGFloat(wifiFileTransfer.counter), total: CGFloat(wifiFileTransfer.numOfFiles) )
.padding(.horizontal, 90)
.padding(.top, -5)
.padding(.top, -8)
.padding(.bottom, 10)
.accentColor(Color.gray)
.scaleEffect(x: 1, y: 2, anchor: .center)
@ -256,24 +232,27 @@ Remove device from USB. Press "Reset" on the device.
ProgressView()
}
.onChange(of: wifiFileTransfer.counter) { i in
print("Wifi Project = \(result.projectName) index: \(wifiFileTransfer.counter)")
}
}
if wifiFileTransfer.testIndex.downloadState == .complete {
if wifiFileTransfer.downloadState == .complete {
CompleteButton()
.padding(.top, 20)
.disabled(true)
}
if wifiFileTransfer.testIndex.downloadState == .failed {
if wifiFileTransfer.downloadState == .failed {
FailedButton()
.padding(.top, 20)
.disabled(true)
}
}
}
} else {
Button {
@ -285,87 +264,71 @@ Remove device from USB. Press "Reset" on the device.
}
}
Spacer()
.frame(height: 30)
.ignoresSafeArea(.all)
.onAppear(perform: {
wifiFileTransfer.registerWifiNotification(enabled: true)
viewModel.searchPathForProject(nameOf: result.projectName)
if viewModel.projectDownloaded {
isDownloaded = true
} else {
isDownloaded = false
}
print("is downloaded? \(viewModel.projectDownloaded)")
})
.padding(.top, 8)
.onChange(of: wifiFileTransfer.transferError, perform: { newValue in
if newValue {
showTransferErrorMessage()
}
})
.onChange(of: viewModel.showUsbInUseError) { newValue in
if newValue {
usbInUseErrorMessage()
}
}
.onChange(of: wifiFileTransfer.counter) { newValue in
print("New counter : \(newValue)")
counter = newValue
}
.onChange(of: wifiFileTransfer.numOfFiles) { newValue in
print("New numOfFiles : \(newValue)")
numOfFiles = newValue
}
.onChange(of: wifiFileTransfer.testIndex.count) { newValue in
print("New count index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.numberOfFiles) { newValue in
print("New numberOfFiles index : \(newValue)")
}
.onChange(of: wifiFileTransfer.testIndex.downloadState) { newValue in
print("New download state : \(newValue)")
}
.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: {
.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")
}
viewModel.searchPathForProject(nameOf: result.projectName)
// wifiFileTransfer.getProjectForSubClass(nameOf: title)
if viewModel.projectDownloaded {
isDownloaded = true
} else {
isDownloaded = false
}
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: viewModel.usbInUseError) { newValue in
if newValue {
usbInUseErrorMessage()
}
}
}
}

View file

@ -6,119 +6,57 @@
//
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 usbInUse = false
@Published var showUsbInUseError = false
@Published var usbInUseError = false
init() {
registerForUSBInUseErrorNotification(enabled: true)
registerNotification(enabled: true)
}
private weak var usbInUseErrorNotification: NSObjectProtocol?
private func registerForUSBInUseErrorNotification(enabled: Bool) {
private func registerNotification(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()})
} else {
}
}
func zipSuccess() {
showUsbInUseError = true
func zipSuccess() {
usbInUseError = true
}
func checkIfUSBInUse() {
wifiTransferService.optionRequest(handler: { result in
switch result {
case .success:
print("Success")
self.wifiTransferService.getRequest(read: "boot_out.txt") { result in
print(result)
}
case .failure:
print("Failure")
}
// if success.contains("GET, OPTIONS, PUT, DELETE, MOVE") {
//
// print("USB not in use.")
// DispatchQueue.main.async {
// self.usbInUse = false
// }
//
// // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// // if wifiFileTransfer.projectDownloaded {
// //
// // wifiFileTransfer.testFileExistance(for: result.projectName, bundleLink: result.bundleLink)
// //
// // } else {
// // downloadModel.trueDownload(useProject: result.bundleLink, projectName: result.projectName)
// // }
// // }
//
// } else {
// DispatchQueue.main.async {
// self.usbInUse = true
// }
// print("USB in use - files cannot be tranferred or moved while USB is in use. Show Error")
// }
})
}
let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func searchPathForProject(nameOf project: String) {
var manager = FileManager.default
let nestedFolderURL = directoryPath.appendingPathComponent(project)
if manager.fileExists(atPath: nestedFolderURL.relativePath) {
print("\(project) - Exist")
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,7 +43,6 @@ class WifiTransferService: ObservableObject {
startup()
}
//*
func sendPutRequest(fileName: String,
body: Data,
then handler: @escaping(Result<Data, Error>) -> Void) {
@ -90,17 +89,16 @@ class WifiTransferService: ObservableObject {
print("File write success!")
handler(.success(data))
}
// print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
func requestWithCheck() {
}
func optionRequest(handler: @escaping(Result<String, Error>) -> Void) {
func optionRequest(completionHandler: @escaping CompletionHandler) {
print("HOST | \(hostName)")
let username = ""
@ -113,11 +111,14 @@ 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: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
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:")
@ -130,32 +131,30 @@ class WifiTransferService: ObservableObject {
print("Response HTTP Status code: \(response.statusCode)")
print("Specific header: \(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found")")
handler(.success(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found"))
completionHandler(response.value(forHTTPHeaderField: "Access-Control-Allow-Methods") ?? "Header Not found")
}
if let error = error {
print("File write error")
handler(.failure(error))
}
guard let data = data else {
print(String(describing: "Error Found: \(String(describing: error))"))
print("Failed! Option Request")
return
}
if let str = String(data: data, encoding: .utf8) {
print("Output: \(str)")
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)
}
}
task.resume()
}
@ -214,8 +213,8 @@ class WifiTransferService: ObservableObject {
typealias CompletionHandler = (_ success:String) -> Void
func getRequest(read: String, completionHandler: @escaping CompletionHandler) {
print("Second network call made!")
func getRequest(read: String, completionHandler: @escaping CompletionHandler){
var semaphore = DispatchSemaphore (value: 0)
let username = ""
@ -245,9 +244,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 {
@ -258,7 +257,7 @@ class WifiTransferService: ObservableObject {
}
if let str = String(data: data, encoding: .utf8) {
print("Out-going getRequest data: \(str)")
print(str)
outgoingString = str
print("\(#function) @Line: \(#line)")
completionHandler(outgoingString)
@ -272,41 +271,6 @@ 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
@ -376,6 +340,7 @@ 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
@ -412,16 +377,15 @@ class WifiTransferService: ObservableObject {
print("File write success!")
completion(.success(data))
}
// print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
// Make
func putDirectory(directoryPath: String, completion: @escaping (Result<Data?, Error>) -> Void) {
print("\(#function) @Line: \(#line)")
let username = ""
let password = "passw0rd"
@ -444,20 +408,12 @@ 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))
}
@ -482,7 +438,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/test.txt")!,timeoutInterval: Double.infinity)
var request = URLRequest(url: URL(string: "http://\(hostName).local/fs/testing.txt")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")

View file

@ -5,19 +5,22 @@
// 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()
@ -25,22 +28,15 @@ 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()
@ -56,7 +52,7 @@ struct WifiView: View {
func checkForStoredIPAddress() {
if userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") == nil {
print("storeResolvedAddress - not stored")
} else {
hostName = userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
viewModel.ipAddressStored = true
@ -71,12 +67,12 @@ struct WifiView: View {
hintText: "IP Address...",
primaryTitle: "Done",
secondaryTitle: "Cancel") { text in
viewModel.checkServices(ip: text)
} secondaryAction: {
print("Cancel")
}
}
viewModel.checkServices(ip: text)
} secondaryAction: {
print("Cancel")
}
}
func showAlertMessage() {
alertMessage(title: "IP address Not Found", exitTitle: "Ok") {
@ -85,60 +81,93 @@ struct WifiView: View {
}
var body: some View {
VStack(spacing: 0) {
WifiHeaderView()
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: false) {
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scroll in
SubHeaderView()
let check = viewModel.pdemos.filter {
let check = networkModel.pdemos.filter {
$0.compatibility.contains(boardBootInfo)
}
ForEach(check) { demo in
if demo.bundleLink == test.currentCell {
WifiCell(result: demo,isExpanded: trueTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
})
.onAppear(){
withAnimation {
scroll.scrollTo(demo.id)
}
WifiCell(result: demo, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
withAnimation {
scroll.scrollTo(demo.id)
}
} else {
WifiCell(result: demo, isExpanded: falseTog, isConnected: $inConnectedInWifiView, bootOne: $boardBootInfo, stateBinder: $downloadState, onViewGeometryChanged: {
withAnimation {
// scroll.scrollTo(demo.id)
}
})
}
})
}
}
@ -146,9 +175,12 @@ struct WifiView: View {
.id(self.scrollViewID)
}
.foregroundColor(.black)
.environmentObject(test)
}
.onDisappear() {
print("On Disappear")
dismiss()
}
.onChange(of: viewModel.connectionStatus, perform: { newValue in
@ -157,20 +189,20 @@ struct WifiView: View {
}
})
.onChange(of: viewModel.wifiServiceManager.resolvedServices, perform: { newValue in
print("Credential Check!")
print(newValue)
if newValue.contains(where: { result in
result.hostName == userDefaults.object(forKey: kPrefix+".storeResolvedAddress.hostName") as! String
}) {
print("Matched")
} else {
print("Un-Matched")
}
})
.onChange(of: viewModel.isInvalidIP, perform: { newValue in
@ -179,7 +211,7 @@ struct WifiView: View {
showAlertMessage()
toggleViewModelIP()
}
})
@ -189,14 +221,13 @@ struct WifiView: View {
viewModel.read()
}
}
}
struct WifiView_Previews: PreviewProvider {
static var previews: some View {
WifiView()
}
}
@ -214,5 +245,5 @@ extension Notification.Name {
public static let didEncounterTransferError = Notification.Name(kPrefix+".didEncounterTransferError")
public static let downloadErrorDidOccur = Notification.Name(kPrefix+".downloadErrorDidOccur")
public static let usbInUseErrorNotification = Notification.Name(kPrefix+".usbInUseErrorNotification")
}

View file

@ -34,7 +34,9 @@ class WifiViewModel: ObservableObject {
@Published var wifiTransferService = WifiTransferService()
@Published var wifiServiceManager = WifiServiceManager()
// @ObservedObject var networkModel = NetworkService()
var circuitPythonVersion = Int()
@Published var webDirectoryInfo = [WebDirectoryModel]()
@ -43,11 +45,6 @@ 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]
@ -59,7 +56,7 @@ class WifiViewModel: ObservableObject {
var ipAddressStored = false
init() {
pdemos = dataStore.loadDefaultList()
checkIP()
registerNotifications(enabled: true)
wifiServiceManager.findService()
@ -72,7 +69,10 @@ 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,8 +92,6 @@ class WifiViewModel: ObservableObject {
}
}
private weak var invalidIPObserver: NSObjectProtocol?
private weak var testObserver: NSObjectProtocol?