From 948990614b9f8998116a6d0166bdbb14d386a7b4 Mon Sep 17 00:00:00 2001 From: Shantanu Bala Date: Thu, 11 May 2023 10:38:27 -0400 Subject: [PATCH 1/3] Add support for Apple Shortcuts --- .../MobileMinter.xcodeproj/project.pbxproj | 42 ++++++++----------- .../Shared/Shortcuts/MintProjectToken.swift | 20 +++++++++ .../Shared/Shortcuts/OpenProject.swift | 23 ++++++++++ .../MobileMinter/Shared/TXLess_MintApp.swift | 2 + .../MobileMinter/TXLess-Mint--iOS--Info.plist | 4 ++ 5 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 Sources/MobileMinter/Shared/Shortcuts/MintProjectToken.swift create mode 100644 Sources/MobileMinter/Shared/Shortcuts/OpenProject.swift diff --git a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj index 4e43496..9682b16 100644 --- a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj +++ b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 8B44221029B25A2B00157A65 /* DelegateAnnouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B4421F329B25A2B00157A65 /* DelegateAnnouncer.swift */; }; 8B7699C92947A435005EEF2B /* web3.swift in Frameworks */ = {isa = PBXBuildFile; productRef = 8B7699C82947A435005EEF2B /* web3.swift */; }; 8BA24F5129254FAF00A72F31 /* BlockLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA24F5029254FAF00A72F31 /* BlockLoadingView.swift */; }; + 8BB82C6B2A0D224300121CF3 /* MintProjectToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB82C6A2A0D224300121CF3 /* MintProjectToken.swift */; }; + 8BB82C6D2A0D2E8B00121CF3 /* OpenProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB82C6C2A0D2E8B00121CF3 /* OpenProject.swift */; }; 8BDA6A572925E378006261E6 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A643D891286EFD8200C7208E /* Haptics.swift */; }; 8BDA6A5D2925E389006261E6 /* ConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A643D88B286EEDF600C7208E /* ConfirmationView.swift */; }; 8BF9606229B7AF4D008FFAE9 /* PaymentViewControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF9606129B7AF4D008FFAE9 /* PaymentViewControllerView.swift */; }; @@ -121,6 +123,8 @@ 8B4421F229B25A2B00157A65 /* DiscoveryMethodViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryMethodViewController.swift; sourceTree = ""; }; 8B4421F329B25A2B00157A65 /* DelegateAnnouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegateAnnouncer.swift; sourceTree = ""; }; 8BA24F5029254FAF00A72F31 /* BlockLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockLoadingView.swift; sourceTree = ""; }; + 8BB82C6A2A0D224300121CF3 /* MintProjectToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MintProjectToken.swift; sourceTree = ""; }; + 8BB82C6C2A0D2E8B00121CF3 /* OpenProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenProject.swift; sourceTree = ""; }; 8BF9606129B7AF4D008FFAE9 /* PaymentViewControllerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentViewControllerView.swift; sourceTree = ""; }; A643D888286EEB5300C7208E /* KeychainSwiftDistrib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainSwiftDistrib.swift; sourceTree = ""; }; A643D88B286EEDF600C7208E /* ConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationView.swift; sourceTree = ""; }; @@ -129,12 +133,9 @@ A64ECE6B27E0DF890064CB4D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; A64ECE6C27E0DF8B0064CB4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A64ECE7127E0DF8B0064CB4D /* TXLess Mint.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TXLess Mint.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - A64ECE7927E0DF8B0064CB4D /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; A64ECE7E27E0DF8B0064CB4D /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; A64ECE8227E0DF8B0064CB4D /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; A64ECE8427E0DF8B0064CB4D /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = ""; }; - A64ECE8E27E0DF8B0064CB4D /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; - A64ECE9027E0DF8B0064CB4D /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; }; A6AFC80D28401C3D005BFC1A /* TXLess-Mint--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "TXLess-Mint--iOS--Info.plist"; sourceTree = ""; }; A6AFC80E284022A8005BFC1A /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = ""; }; A6B47DCD27E3ED3D009919E0 /* MintingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MintingView.swift; sourceTree = ""; }; @@ -206,6 +207,15 @@ path = Stripe; sourceTree = ""; }; + 8BB82C672A0D1C8100121CF3 /* Shortcuts */ = { + isa = PBXGroup; + children = ( + 8BB82C6A2A0D224300121CF3 /* MintProjectToken.swift */, + 8BB82C6C2A0D2E8B00121CF3 /* OpenProject.swift */, + ); + path = Shortcuts; + sourceTree = ""; + }; 8BF9606029B7AF3B008FFAE9 /* Payments */ = { isa = PBXGroup; children = ( @@ -219,9 +229,7 @@ children = ( A6AFC80D28401C3D005BFC1A /* TXLess-Mint--iOS--Info.plist */, A64ECE6927E0DF890064CB4D /* Shared */, - A64ECE7827E0DF8B0064CB4D /* macOS */, A64ECE8127E0DF8B0064CB4D /* Tests iOS */, - A64ECE8D27E0DF8B0064CB4D /* Tests macOS */, A64ECE7227E0DF8B0064CB4D /* Products */, A6B47DC527E3EA8F009919E0 /* Frameworks */, ); @@ -230,6 +238,7 @@ A64ECE6927E0DF890064CB4D /* Shared */ = { isa = PBXGroup; children = ( + 8BB82C672A0D1C8100121CF3 /* Shortcuts */, 8BF9606029B7AF3B008FFAE9 /* Payments */, 8B4421CB29B2543600157A65 /* Stripe */, A64ECE6A27E0DF890064CB4D /* TXLess_MintApp.swift */, @@ -257,14 +266,6 @@ name = Products; sourceTree = ""; }; - A64ECE7827E0DF8B0064CB4D /* macOS */ = { - isa = PBXGroup; - children = ( - A64ECE7927E0DF8B0064CB4D /* macOS.entitlements */, - ); - path = macOS; - sourceTree = ""; - }; A64ECE8127E0DF8B0064CB4D /* Tests iOS */ = { isa = PBXGroup; children = ( @@ -274,15 +275,6 @@ path = "Tests iOS"; sourceTree = ""; }; - A64ECE8D27E0DF8B0064CB4D /* Tests macOS */ = { - isa = PBXGroup; - children = ( - A64ECE8E27E0DF8B0064CB4D /* Tests_macOS.swift */, - A64ECE9027E0DF8B0064CB4D /* Tests_macOSLaunchTests.swift */, - ); - path = "Tests macOS"; - sourceTree = ""; - }; A6B47DC527E3EA8F009919E0 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -421,6 +413,7 @@ 8B44220429B25A2B00157A65 /* Value1MultilineCell.swift in Sources */, 8B44220629B25A2B00157A65 /* EventDisplayingViewController.swift in Sources */, 8B44220329B25A2B00157A65 /* StartSetReaderDisplayViewController.swift in Sources */, + 8BB82C6B2A0D224300121CF3 /* MintProjectToken.swift in Sources */, 8B4421CD29B2553300157A65 /* ReaderViewController.swift in Sources */, 8B44220E29B25A2B00157A65 /* ReaderRegistrationViewController.swift in Sources */, 8B44220529B25A2B00157A65 /* StartRefundViewController.swift in Sources */, @@ -453,6 +446,7 @@ 8B44220B29B25A2B00157A65 /* CustomViews.swift in Sources */, 8BA24F5129254FAF00A72F31 /* BlockLoadingView.swift in Sources */, 8B44220229B25A2B00157A65 /* TableViewController+StripeTerminal.swift in Sources */, + 8BB82C6D2A0D2E8B00121CF3 /* OpenProject.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -619,7 +613,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -659,7 +653,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Sources/MobileMinter/Shared/Shortcuts/MintProjectToken.swift b/Sources/MobileMinter/Shared/Shortcuts/MintProjectToken.swift new file mode 100644 index 0000000..ef3dacc --- /dev/null +++ b/Sources/MobileMinter/Shared/Shortcuts/MintProjectToken.swift @@ -0,0 +1,20 @@ +// +// MintProjectToken.swift +// MobileMinter +// +// Created by Shantanu Bala on 5/11/23. +// + +import AppIntents +import Foundation + +struct MintProjectToken: AppIntent { + static var title: LocalizedStringResource = "Mint Project" + static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication + + @MainActor + func perform() async throws -> some IntentResult { + AppState.shared.intentMintProject = true + return .result() + } +} diff --git a/Sources/MobileMinter/Shared/Shortcuts/OpenProject.swift b/Sources/MobileMinter/Shared/Shortcuts/OpenProject.swift new file mode 100644 index 0000000..59dd700 --- /dev/null +++ b/Sources/MobileMinter/Shared/Shortcuts/OpenProject.swift @@ -0,0 +1,23 @@ +// +// MintProjectToken.swift +// MobileMinter +// +// Created by Shantanu Bala on 5/11/23. +// + +import AppIntents +import Foundation + +struct OpenProject: AppIntent { + static var title: LocalizedStringResource = "Open Project" + static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication + + @Parameter(title: "Project Name") + var projectName: String + + @MainActor + func perform() async throws -> some IntentResult { + AppState.shared.intentOpenProject = projectName + return .result() + } +} diff --git a/Sources/MobileMinter/Shared/TXLess_MintApp.swift b/Sources/MobileMinter/Shared/TXLess_MintApp.swift index 2f8b419..e2bd441 100644 --- a/Sources/MobileMinter/Shared/TXLess_MintApp.swift +++ b/Sources/MobileMinter/Shared/TXLess_MintApp.swift @@ -28,6 +28,8 @@ class AppState: ObservableObject { static let shared = AppState() @Published var sessionID = UUID() + @Published var intentMintProject = false + @Published var intentOpenProject = "" } @main diff --git a/Sources/MobileMinter/TXLess-Mint--iOS--Info.plist b/Sources/MobileMinter/TXLess-Mint--iOS--Info.plist index 8207c11..69a503a 100644 --- a/Sources/MobileMinter/TXLess-Mint--iOS--Info.plist +++ b/Sources/MobileMinter/TXLess-Mint--iOS--Info.plist @@ -6,6 +6,10 @@ ITSAppUsesNonExemptEncryption + NSUserActivityTypes + + MintProjectTokenIntent + UIBackgroundModes bluetooth-central From 47af5565c3fd33257026559750f3714a8656d950 Mon Sep 17 00:00:00 2001 From: Shantanu Bala Date: Thu, 11 May 2023 12:07:29 -0400 Subject: [PATCH 2/3] refactor code --- .../MobileMinter.xcodeproj/project.pbxproj | 8 +- .../Shared/ConfirmationView.swift | 2 +- Sources/MobileMinter/Shared/ContentView.swift | 51 +++--- Sources/MobileMinter/Shared/LoginView.swift | 96 +++++------ ...ss_MintApp.swift => MobileMinterApp.swift} | 46 ++--- .../MobileMinter/Shared/ProjectsView.swift | 160 +++++++++++------- 6 files changed, 199 insertions(+), 164 deletions(-) rename Sources/MobileMinter/Shared/{TXLess_MintApp.swift => MobileMinterApp.swift} (76%) diff --git a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj index 9682b16..c900b73 100644 --- a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj +++ b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj @@ -51,7 +51,7 @@ A643D892286EFD8200C7208E /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A643D891286EFD8200C7208E /* Haptics.swift */; }; A64ECE8327E0DF8B0064CB4D /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64ECE8227E0DF8B0064CB4D /* Tests_iOS.swift */; }; A64ECE8527E0DF8B0064CB4D /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64ECE8427E0DF8B0064CB4D /* Tests_iOSLaunchTests.swift */; }; - A64ECE9227E0DF8B0064CB4D /* TXLess_MintApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64ECE6A27E0DF890064CB4D /* TXLess_MintApp.swift */; }; + A64ECE9227E0DF8B0064CB4D /* MobileMinterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64ECE6A27E0DF890064CB4D /* MobileMinterApp.swift */; }; A64ECE9427E0DF8B0064CB4D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64ECE6B27E0DF890064CB4D /* ContentView.swift */; }; A64ECE9627E0DF8B0064CB4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A64ECE6C27E0DF8B0064CB4D /* Assets.xcassets */; }; A6562FFA2846B483008EC448 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = A6562FF92846B483008EC448 /* QRCode */; }; @@ -129,7 +129,7 @@ A643D888286EEB5300C7208E /* KeychainSwiftDistrib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainSwiftDistrib.swift; sourceTree = ""; }; A643D88B286EEDF600C7208E /* ConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationView.swift; sourceTree = ""; }; A643D891286EFD8200C7208E /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = ""; }; - A64ECE6A27E0DF890064CB4D /* TXLess_MintApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TXLess_MintApp.swift; sourceTree = ""; }; + A64ECE6A27E0DF890064CB4D /* MobileMinterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileMinterApp.swift; sourceTree = ""; }; A64ECE6B27E0DF890064CB4D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; A64ECE6C27E0DF8B0064CB4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A64ECE7127E0DF8B0064CB4D /* TXLess Mint.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TXLess Mint.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -241,7 +241,7 @@ 8BB82C672A0D1C8100121CF3 /* Shortcuts */, 8BF9606029B7AF3B008FFAE9 /* Payments */, 8B4421CB29B2543600157A65 /* Stripe */, - A64ECE6A27E0DF890064CB4D /* TXLess_MintApp.swift */, + A64ECE6A27E0DF890064CB4D /* MobileMinterApp.swift */, A6B47DD327E3F3AF009919E0 /* ProjectsView.swift */, A64ECE6B27E0DF890064CB4D /* ContentView.swift */, A6B47DD027E3EE42009919E0 /* LoginView.swift */, @@ -421,7 +421,7 @@ 8B44220129B25A2B00157A65 /* StartPaymentViewController.swift in Sources */, 8B4421F429B25A2B00157A65 /* RefundViewController.swift in Sources */, A6B47DD127E3EE42009919E0 /* LoginView.swift in Sources */, - A64ECE9227E0DF8B0064CB4D /* TXLess_MintApp.swift in Sources */, + A64ECE9227E0DF8B0064CB4D /* MobileMinterApp.swift in Sources */, A6B47DE127E53C8F009919E0 /* MintingEmbedView.swift in Sources */, 8B44220C29B25A2B00157A65 /* PaymentViewController.swift in Sources */, 8B4421FF29B25A2B00157A65 /* UIView+Layout.swift in Sources */, diff --git a/Sources/MobileMinter/Shared/ConfirmationView.swift b/Sources/MobileMinter/Shared/ConfirmationView.swift index 065be89..45e52d0 100644 --- a/Sources/MobileMinter/Shared/ConfirmationView.swift +++ b/Sources/MobileMinter/Shared/ConfirmationView.swift @@ -52,7 +52,7 @@ struct ConfirmationView: View { } var body: some View { - NavigationView { + NavigationStack { Group { if isConfirmed && (paidWithCard) { VStack { diff --git a/Sources/MobileMinter/Shared/ContentView.swift b/Sources/MobileMinter/Shared/ContentView.swift index 4b3b0b6..51a21cd 100644 --- a/Sources/MobileMinter/Shared/ContentView.swift +++ b/Sources/MobileMinter/Shared/ContentView.swift @@ -1,10 +1,3 @@ -// -// ContentView.swift -// Shared -// -// Created by Shantanu Bala on 3/15/22. -// - import SwiftUI enum ScreenID { @@ -14,32 +7,42 @@ enum ScreenID { } struct ContentView: View { - // the current authentication token for the user @State var currentToken: String? = nil - - // the current PBAB project being minted @State var currentProject: Project? = nil - - // the current screen displayed to the user @State var currentScreen: ScreenID = .login var body: some View { - NavigationView { - if currentScreen == .login { - LoginView(currentToken: $currentToken, currentScreen: $currentScreen).navigationTitle( - "Login") - } else if currentScreen == .projects { + NavigationStack { + currentView.navigationTitle(currentViewTitle) + } + } + + private var currentView: some View { + switch currentScreen { + case .login: + return AnyView(LoginView(currentToken: $currentToken, currentScreen: $currentScreen)) + case .projects: + return AnyView( ProjectsView( currentToken: $currentToken, currentProject: $currentProject, - currentScreen: $currentScreen - ).navigationTitle("Projects") - } else if currentScreen == .minting { + currentScreen: $currentScreen)) + case .minting: + return AnyView( MintingView( currentToken: $currentToken, currentScreen: $currentScreen, - currentProject: $currentProject - ).navigationTitle(currentProject?.title ?? "Minting") - } - }.navigationViewStyle(StackNavigationViewStyle()) + currentProject: $currentProject)) + } + } + + private var currentViewTitle: String { + switch currentScreen { + case .login: + return "Login" + case .projects: + return "Projects" + case .minting: + return currentProject?.title ?? "Minting" + } } } diff --git a/Sources/MobileMinter/Shared/LoginView.swift b/Sources/MobileMinter/Shared/LoginView.swift index 77d3ade..b03013c 100644 --- a/Sources/MobileMinter/Shared/LoginView.swift +++ b/Sources/MobileMinter/Shared/LoginView.swift @@ -1,10 +1,3 @@ -// -// LoginView.swift -// TXLess Mint -// -// Created by Shantanu Bala on 3/17/22. -// - import BetterSafariView import LocalAuthentication import MintingKit @@ -15,66 +8,69 @@ struct LoginView: View { @Binding var currentToken: String? @Binding var currentScreen: ScreenID let keychain = KeychainSwift() + var body: some View { Form { Button("Sign in") { startingWebAuthenticationSession = true } - }.onAppear { - DispatchQueue.main.async { - guard let lastOpened = UserDefaults.standard.object(forKey: "LastOpened") as? Date else { - return - } - guard - let elapsed = Calendar.current.dateComponents([.day], from: lastOpened, to: Date()).day - else { - return - } - if elapsed >= 6 { - return - } - if let t = keychain.get("authToken") { - self.currentToken = t - let context = LAContext() - var error: NSError? - - // check whether biometric authentication is possible - if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { - // it's possible, so go ahead and use it - let reason = "We need to unlock your data." - - context.evaluatePolicy( - .deviceOwnerAuthenticationWithBiometrics, localizedReason: reason - ) { success, authenticationError in - // authentication has now completed - if success { - currentScreen = .projects - } else { - // there was a problem - } - } - } else { - // no biometrics - } - } - } } + .onAppear(perform: retrieveSavedAuthenticationToken) .webAuthenticationSession(isPresented: $startingWebAuthenticationSession) { WebAuthenticationSession( url: URL(string: "https://minting-api.artblocks.io/app/?appauth=true")!, callbackURLScheme: "txlessauth" ) { callbackURL, error in - currentToken = callbackURL?.host - if let t = currentToken { + handleWebAuthentication(callbackURL: callbackURL) + } + } + } + + private func retrieveSavedAuthenticationToken() { + guard let lastOpened = UserDefaults.standard.object(forKey: "LastOpened") as? Date else { + return + } + + let elapsed = daysElapsed(from: lastOpened, to: Date()) + + if elapsed < 6, let savedToken = keychain.get("authToken") { + currentToken = savedToken + authenticateUser() + } + } + + private func daysElapsed(from startDate: Date, to endDate: Date) -> Int { + Calendar.current.dateComponents([.day], from: startDate, to: endDate).day ?? 0 + } + + private func authenticateUser() { + let context = LAContext() + var error: NSError? + + if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { + context.evaluatePolicy( + .deviceOwnerAuthenticationWithBiometrics, + localizedReason: "We need to unlock your data." + ) { success, authenticationError in + if success { DispatchQueue.main.async { - keychain.set(t, forKey: "authToken") - UserDefaults.standard.set(Date(), forKey: "LastOpened") + currentScreen = .projects } - currentScreen = .projects } } } } + + private func handleWebAuthentication(callbackURL: URL?) { + currentToken = callbackURL?.host + if let t = currentToken { + DispatchQueue.main.async { + keychain.set(t, forKey: "authToken") + UserDefaults.standard.set(Date(), forKey: "LastOpened") + currentScreen = .projects + } + } + } } struct LoginView_Previews: PreviewProvider { diff --git a/Sources/MobileMinter/Shared/TXLess_MintApp.swift b/Sources/MobileMinter/Shared/MobileMinterApp.swift similarity index 76% rename from Sources/MobileMinter/Shared/TXLess_MintApp.swift rename to Sources/MobileMinter/Shared/MobileMinterApp.swift index e2bd441..aa86947 100644 --- a/Sources/MobileMinter/Shared/TXLess_MintApp.swift +++ b/Sources/MobileMinter/Shared/MobileMinterApp.swift @@ -1,29 +1,44 @@ -// -// TXLess_MintApp.swift -// Shared -// -// Created by Shantanu Bala on 3/15/22. -// - import StripeTerminal import SwiftUI import UIKit +@main +struct MobileMinterApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @StateObject var appState = AppState.shared + + var body: some Scene { + WindowGroup { + ContentView().id(appState.sessionID) + } + } +} + +// MARK: - AppDelegate class AppDelegate: NSObject, UIApplicationDelegate { static var apiClient: APIClient? - public let defaultCurrency = "USD" + let defaultCurrency = "USD" + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { + configureTerminal() + return true + } +} + +// MARK: - AppDelegate (Private) +extension AppDelegate { + fileprivate func configureTerminal() { let apiClient = APIClient() Terminal.setTokenProvider(apiClient) Terminal.shared.delegate = TerminalDelegateAnnouncer.shared - AppDelegate.apiClient = apiClient - return true + Self.apiClient = apiClient } } +// MARK: - AppState class AppState: ObservableObject { static let shared = AppState() @@ -31,14 +46,3 @@ class AppState: ObservableObject { @Published var intentMintProject = false @Published var intentOpenProject = "" } - -@main -struct TXLess_MintApp: App { - @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - @StateObject var appState = AppState.shared - var body: some Scene { - WindowGroup { - ContentView().id(appState.sessionID) - } - } -} diff --git a/Sources/MobileMinter/Shared/ProjectsView.swift b/Sources/MobileMinter/Shared/ProjectsView.swift index 0997314..256c407 100644 --- a/Sources/MobileMinter/Shared/ProjectsView.swift +++ b/Sources/MobileMinter/Shared/ProjectsView.swift @@ -1,10 +1,3 @@ -// -// ProjectsView.swift -// TXLess Mint -// -// Created by Shantanu Bala on 3/17/22. -// - import Alamofire import SwiftUI import SwiftyJSON @@ -25,84 +18,123 @@ struct ProjectsView: View { @State private var showTerminal = false var body: some View { - if projects.count == 0 { - ProgressView().onAppear { - fetchProjects() + Group { + if projects.isEmpty { + ProgressView().onAppear(perform: fetchProjects) + } else { + projectList } - } else { - List { - Section { - ForEach( - projects, id: \.id - ) { project in - Button(project.title) { - withAnimation { - self.currentProject = project - self.currentScreen = .minting - } - } - } - } + } + } + + private var projectList: some View { + List { + projectSection + paymentSection + } + .sheet(isPresented: $showTerminal) { + stripeTerminalView + } + .onAppear { + AppDelegate.apiClient?.currentToken = self.currentToken + } + } - Section("Payments") { - Button { - showTerminal = true - } label: { - HStack { - Image(systemName: "creditcard") - Text("Stripe Terminal") - } + private var projectSection: some View { + Section { + ForEach(projects, id: \.id) { project in + Button(project.title) { + withAnimation { + self.currentProject = project + self.currentScreen = .minting } + } + } + } + } + private var paymentSection: some View { + Section("Payments") { + Button { + showTerminal = true + } label: { + HStack { + Image(systemName: "creditcard") + Text("Stripe Terminal") } + } + } + } - }.sheet(isPresented: $showTerminal) { - RootViewControllerView().toolbar { - ToolbarItem(placement: .navigation) { - Button("Done") { - showTerminal = false - } - } - }.navigationBarTitle("Stripe Terminal") - }.onAppear { - AppDelegate.apiClient?.currentToken = self.currentToken + private var stripeTerminalView: some View { + RootViewControllerView().toolbar { + ToolbarItem(placement: .navigation) { + Button("Done") { + showTerminal = false + } } } + .navigationBarTitle("Stripe Terminal") } - func fetchProjects() { + private func fetchProjects() { + guard let token = currentToken else { return } + let headers: HTTPHeaders = [ - "Authorization": "Token " + currentToken!, + "Authorization": "Token " + token, "Accept": "application/json", ] - DispatchQueue.main.async { - AF.request("https://minting-api.artblocks.io/project", method: .get, headers: headers) - .validate().responseJSON { response in - switch response.result { - case .success(let value): - let json = JSON(value)["results"].arrayValue - projects.append( - contentsOf: json.map { project in - return Project( - id: project["id"].stringValue, title: project["title"].stringValue, - priceAmountCents: project["price_amount_cents"].intValue, - paymentEthAddress: project["payment_details"]["eth_address"].string) - }) - case .failure(_): - DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { - fetchProjects() - } - } + AF.request("https://minting-api.artblocks.io/project", method: .get, headers: headers) + .validate().responseJSON { response in + handleResponse(response) + } + } + + private func handleResponse(_ response: AFDataResponse) { + switch response.result { + case .success(let value): + parseJSON(value) + case .failure: + DispatchQueue.main.asyncAfter(deadline: .now() + 2.5, execute: fetchProjects) + } + } + + private func parseJSON(_ value: Any) { + let json = JSON(value)["results"].arrayValue + let newProjects = json.map { project in + Project( + id: project["id"].stringValue, + title: project["title"].stringValue, + priceAmountCents: project["price_amount_cents"].intValue, + paymentEthAddress: project["payment_details"]["eth_address"].string + ) + } + updateProjects(newProjects) + } + + private func updateProjects(_ newProjects: [Project]) { + if AppState.shared.intentOpenProject != "" { + if let project = newProjects.first(where: { + $0.title.lowercased() == AppState.shared.intentOpenProject.lowercased() + }) { + withAnimation { + self.currentProject = project + self.currentScreen = .minting } + return + } } + projects.append(contentsOf: newProjects) } } struct ProjectsView_Previews: PreviewProvider { static var previews: some View { ProjectsView( - currentToken: .constant(nil), currentProject: .constant(nil), - currentScreen: .constant(.projects)) + currentToken: .constant(nil), + currentProject: .constant(nil), + currentScreen: .constant(.projects) + ) } } From 93012fbb77d3ddd2ffe13f0ebaf1b1be2a7bd18d Mon Sep 17 00:00:00 2001 From: Shantanu Bala Date: Wed, 24 May 2023 10:02:54 -0500 Subject: [PATCH 3/3] rev --- Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj index c900b73..0cd1e98 100644 --- a/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj +++ b/Sources/MobileMinter/MobileMinter.xcodeproj/project.pbxproj @@ -595,7 +595,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 61; + CURRENT_PROJECT_VERSION = 62; DEVELOPMENT_TEAM = VR4Q2LK67K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -635,7 +635,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 61; + CURRENT_PROJECT_VERSION = 62; DEVELOPMENT_TEAM = VR4Q2LK67K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES;