From dca6640cc9519ee419ef6eb6b2c50bd781729daa Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Tue, 17 Sep 2024 14:38:17 +0100 Subject: [PATCH 1/5] Adding default configuration and refactrong Davinci configuration manager and ViewModels --- .../PingExample.xcodeproj/project.pbxproj | 18 +- .../PingExample/PingExample/AccessToken.swift | 21 +- .../PingExample/ConfigurationManager.swift | 72 +++++ .../PingExample/ConfigurationViewModel.swift | 35 +++ .../PingExample/PingExample/ContentView.swift | 284 ++++++++++-------- .../PingExample/DavinciViewModel.swift | 127 ++++---- SampleApps/PingExample/PingExample/Info.plist | 11 + .../PingExample/PingExample/LoginView.swift | 222 +++++++------- .../PingExample/LoginViewModel.swift | 2 - .../PingExample/TokenViewModel.swift | 14 +- 10 files changed, 489 insertions(+), 317 deletions(-) create mode 100644 SampleApps/PingExample/PingExample/ConfigurationManager.swift create mode 100644 SampleApps/PingExample/PingExample/ConfigurationViewModel.swift create mode 100644 SampleApps/PingExample/PingExample/Info.plist diff --git a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj index 2369cd4..8123c69 100644 --- a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj +++ b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj @@ -30,6 +30,8 @@ A588D0092BF6A1FF00F9052A /* PingLogger.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFF22BF69F1A00F9052A /* PingLogger.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A588D00A2BF6A20500F9052A /* PingStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFFF2BF69F3300F9052A /* PingStorage.framework */; }; A588D00B2BF6A20500F9052A /* PingStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFFF2BF69F3300F9052A /* PingStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + ECB75DC42C986312000517C9 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB75DC32C986312000517C9 /* ConfigurationManager.swift */; }; + ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -161,6 +163,9 @@ A588CFF92BF69F3300F9052A /* PingStorage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PingStorage.xcodeproj; path = ../../PingStorage/PingStorage.xcodeproj; sourceTree = ""; }; A588D0042BF6A0E500F9052A /* StorageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageViewModel.swift; sourceTree = ""; }; A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerViewModel.swift; sourceTree = ""; }; + ECB75DC32C986312000517C9 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; + ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationViewModel.swift; sourceTree = ""; }; + ECB75DC72C999368000517C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -220,7 +225,10 @@ 3A54412F2BCDF10900385131 /* PingExample */ = { isa = PBXGroup; children = ( + ECB75DC72C999368000517C9 /* Info.plist */, + ECB75DC32C986312000517C9 /* ConfigurationManager.swift */, 3AE945732BF27223007B3381 /* LoginUtility.swift */, + ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */, 3A5441322BCDF10900385131 /* ContentView.swift */, 3A8532322BE2D5D800F8619D /* LoginView.swift */, 3A3709952BEB038200AA7B51 /* AccessToken.swift */, @@ -546,7 +554,9 @@ A588D0062BF6A0E500F9052A /* StorageViewModel.swift in Sources */, 3A8532332BE2D5D800F8619D /* LoginView.swift in Sources */, 3A5441332BCDF10900385131 /* ContentView.swift in Sources */, + ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */, 3A8092F42BE99F0F00AD667F /* InputView.swift in Sources */, + ECB75DC42C986312000517C9 /* ConfigurationManager.swift in Sources */, 3AB1C9F42BD6C6F3003FCE3C /* LoginViewModel.swift in Sources */, 3A3709962BEB038200AA7B51 /* AccessToken.swift in Sources */, ); @@ -715,10 +725,10 @@ DEVELOPMENT_TEAM = 9QSE66762D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = Davinci; + INFOPLIST_FILE = PingExample/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = DaVinci; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -746,10 +756,10 @@ DEVELOPMENT_TEAM = 9QSE66762D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = Davinci; + INFOPLIST_FILE = PingExample/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = DaVinci; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/SampleApps/PingExample/PingExample/AccessToken.swift b/SampleApps/PingExample/PingExample/AccessToken.swift index 1cbdd75..96f22ec 100644 --- a/SampleApps/PingExample/PingExample/AccessToken.swift +++ b/SampleApps/PingExample/PingExample/AccessToken.swift @@ -12,21 +12,31 @@ import SwiftUI struct AccessTokenView: View { - @StateObject var accessToken = TokenViewModel() + @Binding var path: [String] + + @StateObject var tokenViewModel = TokenViewModel() var body: some View { VStack { - TextEditor(text: $accessToken.accessToken) + TextEditor(text: $tokenViewModel.accessToken) .foregroundStyle(.secondary) .padding(.horizontal) .navigationTitle("AccessToken") + NextButton(title: "Procced to logout") { + Task { + await ConfigurationManager.shared.davinci?.user()?.logout() + path.removeLast() + } + } } + } } struct UserInfoView: View { + @Binding var path: [String] @StateObject var vm = UserInfoViewModel() @@ -37,6 +47,11 @@ struct UserInfoView: View { .padding(.horizontal) .navigationTitle("User Info") - + NextButton(title: "Procced to logout") { + Task { + await ConfigurationManager.shared.davinci?.user()?.logout() + path.removeLast() + } + } } } diff --git a/SampleApps/PingExample/PingExample/ConfigurationManager.swift b/SampleApps/PingExample/PingExample/ConfigurationManager.swift new file mode 100644 index 0000000..c8a95a6 --- /dev/null +++ b/SampleApps/PingExample/PingExample/ConfigurationManager.swift @@ -0,0 +1,72 @@ +// +// ConfigurationManager.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +import Foundation +import PingDavinci + +class ConfigurationManager: ObservableObject { + static let shared = ConfigurationManager() + public var davinci: DaVinci? + private var currentConfigurationViewModel: ConfigurationViewModel? + + public func loadConfigurationViewModel() -> ConfigurationViewModel { + if self.currentConfigurationViewModel == nil { + self.currentConfigurationViewModel = defaultConfigurationViewModel() + } + return self.currentConfigurationViewModel! + } + + public func createDavinciWorkflow() { + if let currentConfiguration = self.currentConfigurationViewModel { + self.davinci = DaVinci.createDaVinci { config in + //config.debug = true + config.module(OidcModule.config) { oidcValue in + oidcValue.clientId = currentConfiguration.clientId + oidcValue.scopes = Set(currentConfiguration.scopes) + oidcValue.redirectUri = currentConfiguration.redirectUri + oidcValue.discoveryEndpoint = currentConfiguration.discoveryEndpoint + } + } + } + } + + public func saveConfiguration() { + if let currentConfiguration = self.currentConfigurationViewModel { + let encoder = JSONEncoder() + let configuration = Configuration(clientId: currentConfiguration.clientId, scopes: currentConfiguration.scopes, redirectUri: currentConfiguration.redirectUri, discoveryEndpoint: currentConfiguration.discoveryEndpoint) + if let encoded = try? encoder.encode(configuration) { + let defaults = UserDefaults.standard + defaults.set(encoded, forKey: "CurrentConfiguration") + } + } + } + + public func deleteSavedConfiguration() { + let defaults = UserDefaults.standard + defaults.removeObject(forKey: "CurrentConfiguration") + } + + private func defaultConfigurationViewModel() -> ConfigurationViewModel { + let defaults = UserDefaults.standard + if let savedConfiguration = defaults.object(forKey: "CurrentConfiguration") as? Data { + let decoder = JSONDecoder() + if let loadedConfiguration = try? decoder.decode(Configuration.self, from: savedConfiguration) { + return ConfigurationViewModel(clientId: loadedConfiguration.clientId, scopes: loadedConfiguration.scopes, redirectUri: loadedConfiguration.redirectUri, discoveryEndpoint: loadedConfiguration.discoveryEndpoint) + } + } + return ConfigurationViewModel( + clientId: "[CLIENT ID]", + scopes: ["openid", "email", "address", "phone", "profile"], + redirectUri: "[REDIRECT URI]", + discoveryEndpoint: "[DISCOVERY ENDPOINT]" + ) + } +} diff --git a/SampleApps/PingExample/PingExample/ConfigurationViewModel.swift b/SampleApps/PingExample/PingExample/ConfigurationViewModel.swift new file mode 100644 index 0000000..5a9ddc6 --- /dev/null +++ b/SampleApps/PingExample/PingExample/ConfigurationViewModel.swift @@ -0,0 +1,35 @@ +// +// ConfigurationViewModel.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +import Foundation + +class ConfigurationViewModel: ObservableObject { + + @Published public var clientId: String + @Published public var scopes: [String] + @Published public var redirectUri: String + @Published public var discoveryEndpoint: String + + public init(clientId: String, scopes: [String], redirectUri: String, discoveryEndpoint: String) { + self.clientId = clientId + self.scopes = scopes + self.redirectUri = redirectUri + self.discoveryEndpoint = discoveryEndpoint + } +} + +struct Configuration: Codable { + var clientId: String + var scopes: [String] + var redirectUri: String + var discoveryEndpoint: String +} + diff --git a/SampleApps/PingExample/PingExample/ContentView.swift b/SampleApps/PingExample/PingExample/ContentView.swift index 13ada42..70ea2f5 100644 --- a/SampleApps/PingExample/PingExample/ContentView.swift +++ b/SampleApps/PingExample/PingExample/ContentView.swift @@ -2,160 +2,200 @@ import SwiftUI struct ContentView: View { - - - @State private var startDavinici = false - - @State private var path: [String] = [] - - var body: some View { - NavigationStack(path: $path) { - List { - NavigationLink(value: "Davinci") { - Text("Launch Davinci") - } - NavigationLink(value: "Token") { - Text("Access Token") - } - NavigationLink(value: "User") { - Text("User Info") - } - - NavigationLink(value: "Logout") { - Text("Logout") - } - - NavigationLink(value: "Logger") { - Text("Logger") - } - NavigationLink(value: "Storage") { - Text("Storage") - } - }.navigationDestination(for: String.self) { item in - switch item { - case "Davinci": - DavinciView(path: $path) - case "Token": - AccessTokenView() - case "User": - UserInfoView() - case "Logout": - LogOutView(path: $path) - case "Logger": - LoggerView() - case "Storage": - StorageView() - default: - EmptyView() + + + @State private var startDavinici = false + + @State private var path: [String] = [] + + @State private var configurationViewModel: ConfigurationViewModel = ConfigurationManager.shared.loadConfigurationViewModel() + + var body: some View { + NavigationStack(path: $path) { + List { + NavigationLink(value: "Configuration") { + Text("Edit configuration") + } + NavigationLink(value: "DaVinci") { + Text("Launch DaVinci") + } + NavigationLink(value: "Token") { + Text("Access Token") + } + NavigationLink(value: "User") { + Text("User Info") + } + NavigationLink(value: "Logout") { + Text("Logout") + } + }.navigationDestination(for: String.self) { item in + switch item { + case "Configuration": + ConfigurationView(viewmodel: $configurationViewModel) + case "DaVinci": + DavinciView(path: $path) + case "Token": + AccessTokenView(path: $path) + case "User": + UserInfoView(path: $path) + case "Logout": + LogOutView(path: $path) + default: + EmptyView() + } + }.navigationBarTitle("Ping DaVinci") + Image(uiImage: UIImage(named: "Logo")!) + .resizable() + .frame(width: 180.0, height: 180.0).clipped() + }.onAppear{ + ConfigurationManager.shared.createDavinciWorkflow() } - }.navigationBarTitle("Ping Davinci") } - } } struct LogOutView: View { - - @Binding var path: [String] - - @StateObject private var viewmodel = LogOutViewModel() - - var body: some View { - Text("Logout") - .font(.title) - .navigationBarTitle("Logout", displayMode: .inline) + @Binding var path: [String] - NextButton(title: "Procced to logout") { - Task { - await viewmodel.logout() - path.removeLast() - path.append("Davinci") - } - } + @StateObject private var viewmodel = LogOutViewModel() - } + var body: some View { + + Text("Logout") + .font(.title) + .navigationBarTitle("Logout", displayMode: .inline) + + NextButton(title: "Procced to logout") { + Task { + await viewmodel.logout() + path.removeLast() + } + } + + } } +struct ConfigurationView: View { + @Binding var viewmodel: ConfigurationViewModel + @State private var scopes: String = "" + + var body: some View { + Form { + Section { + Text("Client Id:") + TextField("Client Id", text: $viewmodel.clientId) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Redirect URI:") + TextField("Redirect URI", text: $viewmodel.redirectUri) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Discovery Endpoint:") + TextField("Discovery Endpoint", text: $viewmodel.discoveryEndpoint) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Scopes:") + TextField("scopes:", text: $scopes) + .disableAutocorrection(true) + .autocapitalization(.none) + } + } + .navigationTitle("Edit Configuration") + .onAppear{ + scopes = $viewmodel.scopes.wrappedValue.joined(separator: ",") + } + .onDisappear{ + viewmodel.scopes = scopes.components(separatedBy: ",") + ConfigurationManager.shared.saveConfiguration() + } + } +} struct SecondTabView: View { - var body: some View { - Text("LogOut") - .font(.title) - .navigationBarTitle("LogOut", displayMode: .inline) - - } + var body: some View { + Text("LogOut") + .font(.title) + .navigationBarTitle("LogOut", displayMode: .inline) + + } } struct Register: View { - var body: some View { - Text("Register") - .font(.title) - .navigationBarTitle("Register", displayMode: .inline) - } + var body: some View { + Text("Register") + .font(.title) + .navigationBarTitle("Register", displayMode: .inline) + } } struct ForgotPassword: View { - var body: some View { - Text("ForgotPassword") - .font(.title) - .navigationBarTitle("ForgotPassword", displayMode: .inline) - } + var body: some View { + Text("ForgotPassword") + .font(.title) + .navigationBarTitle("ForgotPassword", displayMode: .inline) + } } struct LoggerView: View { - var loggerViewModel = LoggerViewModel() - var body: some View { - Text("This View is for testing Logger functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Logger", displayMode: .inline) - .onAppear() { - loggerViewModel.setupLogger() - } - } + var loggerViewModel = LoggerViewModel() + var body: some View { + Text("This View is for testing Logger functionality.\nPlease check the Console Logs") + .font(.title3) + .multilineTextAlignment(.center) + .navigationBarTitle("Logger", displayMode: .inline) + .onAppear() { + loggerViewModel.setupLogger() + } + } } struct StorageView: View { - var storageViewModel = StorageViewModel() - var body: some View { - Text("This View is for testing Storage functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Storage", displayMode: .inline) - .onAppear() { - Task { - await storageViewModel.setupMemoryStorage() - await storageViewModel.setupKeychainStorage() - } - } - } + var storageViewModel = StorageViewModel() + var body: some View { + Text("This View is for testing Storage functionality.\nPlease check the Console Logs") + .font(.title3) + .multilineTextAlignment(.center) + .navigationBarTitle("Storage", displayMode: .inline) + .onAppear() { + Task { + await storageViewModel.setupMemoryStorage() + await storageViewModel.setupKeychainStorage() + } + } + } } @main struct MyApp: App { - var body: some Scene { - WindowGroup { - ContentView() + var body: some Scene { + WindowGroup { + ContentView() + } } - } } struct ActivityIndicatorView: View { - @Binding var isAnimating: Bool - let style: UIActivityIndicatorView.Style - let color: Color - - var body: some View { - if isAnimating { - VStack { - Spacer() - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: color)) - .padding() - Spacer() - } - .background(Color.black.opacity(0.4).ignoresSafeArea()) + @Binding var isAnimating: Bool + let style: UIActivityIndicatorView.Style + let color: Color + + var body: some View { + if isAnimating { + VStack { + Spacer() + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: color)) + .padding() + Spacer() + } + .background(Color.black.opacity(0.4).ignoresSafeArea()) + } } - } } diff --git a/SampleApps/PingExample/PingExample/DavinciViewModel.swift b/SampleApps/PingExample/PingExample/DavinciViewModel.swift index ff0a023..b9f32a2 100644 --- a/SampleApps/PingExample/PingExample/DavinciViewModel.swift +++ b/SampleApps/PingExample/PingExample/DavinciViewModel.swift @@ -13,87 +13,74 @@ import PingDavinci import PingOidc import PingOrchestrate -public let davinciStage = DaVinci.createDaVinci { config in - //config.debug = true - - config.module(OidcModule.config) { oidcValue in - oidcValue.clientId = "3172d977-8fdc-4e8b-b3c5-4f3a34cb7262" - oidcValue.scopes = ["openid", "email", "address", "phone", "profile"] - oidcValue.redirectUri = "org.forgerock.demo://oauth2redirect" - oidcValue.discoveryEndpoint = "https://auth.test-one-pingone.com/0c6851ed-0f12-4c9a-a174-9b1bf8b438ae/as/.well-known/openid-configuration" - } -} - -public let davinci = DaVinci.createDaVinci { config in - //config.debug = true - - config.module(OidcModule.config) { oidcValue in - oidcValue.clientId = "c12743f9-08e8-4420-a624-71bbb08e9fe1" - oidcValue.scopes = ["openid", "email", "address", "phone", "profile"] - oidcValue.redirectUri = "org.forgerock.demo://oauth2redirect" - oidcValue.discoveryEndpoint = "https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/.well-known/openid-configuration" - } -} +//public let davinci = DaVinci.createDaVinci { config in +// //config.debug = true +// config.module(OidcModule.config) { oidcValue in +// let configViewModel = ConfigurationManager.shared.loadConfigurationViewModel() +// oidcValue.clientId = configViewModel.clientId +// oidcValue.scopes = Set(configViewModel.scopes) +// oidcValue.redirectUri = configViewModel.redirectUri +// oidcValue.discoveryEndpoint = configViewModel.discoveryEndpoint +// } +//} class DavinciViewModel: ObservableObject { - - @Published public var data: StateNode = StateNode() - - @Published public var isLoading: Bool = false - - init() { - - Task { - await startDavinci() - } - } - - - private func startDavinci() async { - await MainActor.run { - isLoading = true - } + @Published public var data: StateNode = StateNode() - let node = await davinci.start() + @Published public var isLoading: Bool = false - if let connector = node as? Connector { - let node = await connector.next() - await MainActor.run { - self.data = StateNode(currentNode: node, previousNode: node) - } - } else { - await MainActor.run { - self.data = StateNode(currentNode: node, previousNode: node) - } + init() { + Task { + await startDavinci() + } } - await MainActor.run { - isLoading = false - } - } - - public func next(node: Node) async { - await MainActor.run { - isLoading = true + private func startDavinci() async { + + await MainActor.run { + isLoading = true + } + let node = await ConfigurationManager.shared.davinci?.start() + + if let connector = node as? Connector { + let node = await connector.next() + await MainActor.run { + self.data = StateNode(currentNode: node, previousNode: node) + } + } else { + await MainActor.run { + self.data = StateNode(currentNode: node, previousNode: node) + } + } + + await MainActor.run { + isLoading = false + } + } - if let connector = node as? Connector { - let next = await connector.next() - await MainActor.run { - self.data = StateNode(currentNode: next, previousNode: node) - isLoading = false - } + + public func next(node: Node) async { + await MainActor.run { + isLoading = true + } + if let connector = node as? Connector { + let next = await connector.next() + await MainActor.run { + self.data = StateNode(currentNode: next, previousNode: node) + isLoading = false + } + } } - } } class StateNode { - var currentNode: Node? = nil - var previousNode: Node? = nil - - init(currentNode: Node? = nil, previousNode: Node? = nil) { - self.currentNode = currentNode - self.previousNode = previousNode - } + var currentNode: Node? = nil + var previousNode: Node? = nil + + init(currentNode: Node? = nil, previousNode: Node? = nil) { + self.currentNode = currentNode + self.previousNode = previousNode + } } diff --git a/SampleApps/PingExample/PingExample/Info.plist b/SampleApps/PingExample/PingExample/Info.plist new file mode 100644 index 0000000..5e86f97 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Info.plist @@ -0,0 +1,11 @@ + + + + + UILaunchScreen + + UIImageName + Logo + + + diff --git a/SampleApps/PingExample/PingExample/LoginView.swift b/SampleApps/PingExample/PingExample/LoginView.swift index a9d2e13..830bd1f 100644 --- a/SampleApps/PingExample/PingExample/LoginView.swift +++ b/SampleApps/PingExample/PingExample/LoginView.swift @@ -14,141 +14,141 @@ import PingOrchestrate import PingDavinci struct DavinciView: View { - - @StateObject private var viewmodel = DavinciViewModel() - @Binding var path: [String] - - var body: some View { - ZStack { - ScrollView { - VStack { - Spacer() - switch viewmodel.data.currentNode { - case let connector as Connector: - ConnectorView(viewmodel: viewmodel, connector: connector) - case is SuccessNode: - VStack{}.onAppear { - path.removeLast() - path.append("Token") - } - case let errorNode as ErrorNode: - if let connector = viewmodel.data.previousNode as? Connector { - ConnectorView(viewmodel: viewmodel, connector: connector) + + @StateObject var viewmodel = DavinciViewModel() + @Binding var path: [String] + + var body: some View { + ZStack { + ScrollView { + VStack { + Spacer() + switch viewmodel.data.currentNode { + case let connector as Connector: + ConnectorView(viewmodel: viewmodel, connector: connector) + case is SuccessNode: + VStack{}.onAppear { + path.removeLast() + path.append("Token") + } + case let errorNode as ErrorNode: + if let connector = viewmodel.data.previousNode as? Connector { + ConnectorView(viewmodel: viewmodel, connector: connector) + } + ErrorView(name: errorNode.cause.localizedDescription) + case let failureNode as FailureNode: + if let connector = viewmodel.data.previousNode as? Connector { + ConnectorView(viewmodel: viewmodel, connector: connector) + } + ErrorView(name: failureNode.message) + default: + EmptyView() + } + } } - ErrorView(name: errorNode.cause.localizedDescription) - case let failureNode as FailureNode: - if let connector = viewmodel.data.previousNode as? Connector { - ConnectorView(viewmodel: viewmodel, connector: connector) + + Spacer() + + // Activity indicator + if viewmodel.isLoading { + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(4) // Increase spinner size if needed + .foregroundColor(.white) // Set spinner color } - ErrorView(name: failureNode.message) - default: - EmptyView() - } } - } - - Spacer() - - // Activity indicator - if viewmodel.isLoading { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(4) // Increase spinner size if needed - .foregroundColor(.white) // Set spinner color - } } - } } struct ConnectorView: View { - - @ObservedObject var viewmodel: DavinciViewModel - public var connector: Connector - - var body: some View { - VStack { - Image("Logo").resizable().scaledToFill().frame(width: 100, height: 100) - .padding(.vertical, 32) - HeaderView(name: connector.name) - NewLoginView( - davinciViewModel: viewmodel, - connector: connector, collectorsList: connector.collectors) + + @ObservedObject var viewmodel: DavinciViewModel + public var connector: Connector + + var body: some View { + VStack { + Image("Logo").resizable().scaledToFill().frame(width: 100, height: 100) + .padding(.vertical, 32) + HeaderView(name: connector.name) + NewLoginView( + davinciViewModel: viewmodel, + connector: connector, collectorsList: connector.collectors) + } } - } } struct ErrorView: View { - var name: String = "" - - var body: some View { - VStack { - Text("Oops! Something went wrong.\(name)") - .foregroundColor(.red).padding(.top, 20) + var name: String = "" + + var body: some View { + VStack { + Text("Oops! Something went wrong.\(name)") + .foregroundColor(.red).padding(.top, 20) + } } - } } struct HeaderView: View { - var name: String = "" - var body: some View { - VStack { - Text(name) - .font(.title) + var name: String = "" + var body: some View { + VStack { + Text(name) + .font(.title) + } } - } } struct NewLoginView: View { - // MARK: - Propertiers - @ObservedObject var davinciViewModel: DavinciViewModel - - public var connector: Connector - - public var collectorsList: Collectors - - // MARK: - View - var body: some View { + // MARK: - Propertiers + @ObservedObject var davinciViewModel: DavinciViewModel + + public var connector: Connector - VStack { - - ForEach(collectorsList, id: \.id) { field in + public var collectorsList: Collectors + + // MARK: - View + var body: some View { VStack { - if let text = field as? TextCollector { - InputView(text: text.value, placeholderString: text.label, field: text) - } - - if let password = field as? PasswordCollector { - InputView(placeholderString: password.label, secureField: true, field: password) - } - - if let submitButton = field as? SubmitCollector { - InputButton(title: submitButton.label, field: submitButton) { - Task { - await davinciViewModel.next(node: connector) - } - } - } - - }.padding(.horizontal, 5).padding(.top, 20) - - - if let flowButton = field as? FlowCollector { - Button(action: { - flowButton.value = "action" - Task { - await davinciViewModel.next(node: connector) + + ForEach(collectorsList, id: \.id) { field in + + VStack { + if let text = field as? TextCollector { + InputView(text: text.value, placeholderString: text.label, field: text) + } + + if let password = field as? PasswordCollector { + InputView(placeholderString: password.label, secureField: true, field: password) + } + + if let submitButton = field as? SubmitCollector { + InputButton(title: submitButton.label, field: submitButton) { + Task { + await davinciViewModel.next(node: connector) + } + } + } + + }.padding(.horizontal, 5).padding(.top, 20) + + + if let flowButton = field as? FlowCollector { + Button(action: { + flowButton.value = "action" + Task { + await davinciViewModel.next(node: connector) + } + }) { + Text(flowButton.label) + .foregroundColor(.black) + } + } } - }) { - Text(flowButton.label) - .foregroundColor(.black) - } } - } } - } } diff --git a/SampleApps/PingExample/PingExample/LoginViewModel.swift b/SampleApps/PingExample/PingExample/LoginViewModel.swift index 614039a..cf0260b 100644 --- a/SampleApps/PingExample/PingExample/LoginViewModel.swift +++ b/SampleApps/PingExample/PingExample/LoginViewModel.swift @@ -18,10 +18,8 @@ import Observation class LoginViewModel: ObservableObject { @Published public var isLoading: Bool = false - @ObservedObject var davinciViewModel: DavinciViewModel - init( isLoading: Bool = false, davinciViewModel: DavinciViewModel) { diff --git a/SampleApps/PingExample/PingExample/TokenViewModel.swift b/SampleApps/PingExample/PingExample/TokenViewModel.swift index 8bf970d..d1f7aa7 100644 --- a/SampleApps/PingExample/PingExample/TokenViewModel.swift +++ b/SampleApps/PingExample/PingExample/TokenViewModel.swift @@ -23,7 +23,7 @@ class TokenViewModel: ObservableObject { } func accessToken() async { - let token = await davinci.user()?.token() + let token = await ConfigurationManager.shared.davinci?.user()?.token() switch token { case .success(let accessToken): await MainActor.run { @@ -36,7 +36,9 @@ class TokenViewModel: ObservableObject { } LogManager.standard.e("", error: error) case .none: - break + await MainActor.run { + self.accessToken = "Error: Token nil, need to log in" + } } } @@ -53,7 +55,7 @@ class UserInfoViewModel: ObservableObject { } func fetchUserInfo() async { - let userInfo = await davinci.user()?.userinfo(cache: false) + let userInfo = await ConfigurationManager.shared.davinci?.user()?.userinfo(cache: false) switch userInfo { case .success(let userInfoDictionary): await MainActor.run { @@ -68,7 +70,9 @@ class UserInfoViewModel: ObservableObject { } LogManager.standard.e("", error: error) case .none: - break + await MainActor.run { + self.userInfo = "Error: Userinfo nil, need to log in" + } } } } @@ -78,7 +82,7 @@ class LogOutViewModel: ObservableObject { @Published var logout: String = "" func logout() async { - await davinci.user()?.logout() + await ConfigurationManager.shared.davinci?.user()?.logout() await MainActor.run { logout = "Logout completed" } From 66a783eda909edff1dcc0700a7c0ea99397c7c22 Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Tue, 1 Oct 2024 13:10:48 +0100 Subject: [PATCH 2/5] Removing deprecated views, clean up and re-organising structure of the project --- .../PingExample.xcodeproj/project.pbxproj | 86 ++++++-- .../PingExample/PingExample/ContentView.swift | 201 ------------------ .../ConfigurationManager.swift | 16 +- .../{ => Utilities}/LoginUtility.swift | 19 +- .../ConfigurationViewModel.swift | 0 .../{ => ViewModels}/DavinciViewModel.swift | 12 -- .../{ => ViewModels}/LoggerViewModel.swift | 2 - .../{ => ViewModels}/LoginViewModel.swift | 0 .../ViewModels/LogoutViewModel.swift | 24 +++ .../{ => ViewModels}/StorageViewModel.swift | 0 .../ViewModels/TokenViewModel.swift | 44 ++++ .../UserInfoViewModel.swift} | 50 +---- .../AccessTokenView.swift} | 21 -- .../PingExample/Views/ConfigurationView.swift | 53 +++++ .../PingExample/Views/ContentView.swift | 62 ++++++ .../PingExample/Views/DavinciView.swift | 63 ++++++ .../PingExample/{ => Views}/InputView.swift | 0 .../PingExample/Views/LoggerView.swift | 24 +++ .../PingExample/{ => Views}/LoginView.swift | 60 +----- .../PingExample/Views/LogoutView.swift | 33 +++ .../PingExample/Views/StorageView.swift | 27 +++ .../PingExample/Views/UserInfoView.swift | 32 +++ 22 files changed, 474 insertions(+), 355 deletions(-) delete mode 100644 SampleApps/PingExample/PingExample/ContentView.swift rename SampleApps/PingExample/PingExample/{ => Utilities}/ConfigurationManager.swift (83%) rename SampleApps/PingExample/PingExample/{ => Utilities}/LoginUtility.swift (80%) rename SampleApps/PingExample/PingExample/{ => ViewModels}/ConfigurationViewModel.swift (100%) rename SampleApps/PingExample/PingExample/{ => ViewModels}/DavinciViewModel.swift (78%) rename SampleApps/PingExample/PingExample/{ => ViewModels}/LoggerViewModel.swift (99%) rename SampleApps/PingExample/PingExample/{ => ViewModels}/LoginViewModel.swift (100%) create mode 100644 SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift rename SampleApps/PingExample/PingExample/{ => ViewModels}/StorageViewModel.swift (100%) create mode 100644 SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift rename SampleApps/PingExample/PingExample/{TokenViewModel.swift => ViewModels/UserInfoViewModel.swift} (51%) rename SampleApps/PingExample/PingExample/{AccessToken.swift => Views/AccessTokenView.swift} (62%) create mode 100644 SampleApps/PingExample/PingExample/Views/ConfigurationView.swift create mode 100644 SampleApps/PingExample/PingExample/Views/ContentView.swift create mode 100644 SampleApps/PingExample/PingExample/Views/DavinciView.swift rename SampleApps/PingExample/PingExample/{ => Views}/InputView.swift (100%) create mode 100644 SampleApps/PingExample/PingExample/Views/LoggerView.swift rename SampleApps/PingExample/PingExample/{ => Views}/LoginView.swift (59%) create mode 100644 SampleApps/PingExample/PingExample/Views/LogoutView.swift create mode 100644 SampleApps/PingExample/PingExample/Views/StorageView.swift create mode 100644 SampleApps/PingExample/PingExample/Views/UserInfoView.swift diff --git a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj index 8123c69..7d930b0 100644 --- a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj +++ b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 3A203D852BDB1A900020C995 /* PingDavinci.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A5442152BCE1F2900385131 /* PingDavinci.framework */; }; 3A203D862BDB1A900020C995 /* PingDavinci.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3A5442152BCE1F2900385131 /* PingDavinci.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3A203D892BDB22B00020C995 /* PingOidc.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AB1CA252BD6F9AD003FCE3C /* PingOidc.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 3A3709962BEB038200AA7B51 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3709952BEB038200AA7B51 /* AccessToken.swift */; }; + 3A3709962BEB038200AA7B51 /* AccessTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3709952BEB038200AA7B51 /* AccessTokenView.swift */; }; 3A5441332BCDF10900385131 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5441322BCDF10900385131 /* ContentView.swift */; }; 3A5441352BCDF10B00385131 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A5441342BCDF10B00385131 /* Assets.xcassets */; }; 3A5441382BCDF10B00385131 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A5441372BCDF10B00385131 /* Preview Assets.xcassets */; }; @@ -32,6 +32,14 @@ A588D00B2BF6A20500F9052A /* PingStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFFF2BF69F3300F9052A /* PingStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ECB75DC42C986312000517C9 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB75DC32C986312000517C9 /* ConfigurationManager.swift */; }; ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */; }; + ECFB77CD2CAC14A400FF02BC /* DavinciView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77C22CAC149F00FF02BC /* DavinciView.swift */; }; + ECFB77D12CAC153300FF02BC /* UserInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D02CAC152A00FF02BC /* UserInfoView.swift */; }; + ECFB77D42CAC163500FF02BC /* StorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D32CAC163100FF02BC /* StorageView.swift */; }; + ECFB77D62CAC165100FF02BC /* LoggerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D52CAC164E00FF02BC /* LoggerView.swift */; }; + ECFB77D82CAC168500FF02BC /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */; }; + ECFB77DA2CAC16A400FF02BC /* LogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D92CAC169F00FF02BC /* LogoutView.swift */; }; + ECFB77DC2CAC16FC00FF02BC /* LogoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */; }; + ECFB77DE2CAC172800FF02BC /* UserInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77DD2CAC172200FF02BC /* UserInfoViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -140,7 +148,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 3A3709952BEB038200AA7B51 /* AccessToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = ""; }; + 3A3709952BEB038200AA7B51 /* AccessTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenView.swift; sourceTree = ""; }; 3A54412D2BCDF10900385131 /* PingExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PingExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3A5441322BCDF10900385131 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 3A5441342BCDF10B00385131 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -166,6 +174,14 @@ ECB75DC32C986312000517C9 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationViewModel.swift; sourceTree = ""; }; ECB75DC72C999368000517C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + ECFB77C22CAC149F00FF02BC /* DavinciView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DavinciView.swift; sourceTree = ""; }; + ECFB77D02CAC152A00FF02BC /* UserInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoView.swift; sourceTree = ""; }; + ECFB77D32CAC163100FF02BC /* StorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageView.swift; sourceTree = ""; }; + ECFB77D52CAC164E00FF02BC /* LoggerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerView.swift; sourceTree = ""; }; + ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = ""; }; + ECFB77D92CAC169F00FF02BC /* LogoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutView.swift; sourceTree = ""; }; + ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutViewModel.swift; sourceTree = ""; }; + ECFB77DD2CAC172200FF02BC /* UserInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -226,18 +242,9 @@ isa = PBXGroup; children = ( ECB75DC72C999368000517C9 /* Info.plist */, - ECB75DC32C986312000517C9 /* ConfigurationManager.swift */, - 3AE945732BF27223007B3381 /* LoginUtility.swift */, - ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */, - 3A5441322BCDF10900385131 /* ContentView.swift */, - 3A8532322BE2D5D800F8619D /* LoginView.swift */, - 3A3709952BEB038200AA7B51 /* AccessToken.swift */, - 3AB1C9F32BD6C6F3003FCE3C /* LoginViewModel.swift */, - 3ACD90E02BE586BC00DABCE6 /* DavinciViewModel.swift */, - 3A8092F52BE9BB6B00AD667F /* TokenViewModel.swift */, - 3A8092F32BE99F0F00AD667F /* InputView.swift */, - A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */, - A588D0042BF6A0E500F9052A /* StorageViewModel.swift */, + ECFB77D22CAC157700FF02BC /* Utilities */, + ECFB77CF2CAC150200FF02BC /* ViewModels */, + ECFB77CE2CAC14F600FF02BC /* Views */, 3A5441342BCDF10B00385131 /* Assets.xcassets */, 3A5441362BCDF10B00385131 /* Preview Content */, ); @@ -321,6 +328,47 @@ name = Products; sourceTree = ""; }; + ECFB77CE2CAC14F600FF02BC /* Views */ = { + isa = PBXGroup; + children = ( + ECFB77D02CAC152A00FF02BC /* UserInfoView.swift */, + 3A5441322BCDF10900385131 /* ContentView.swift */, + 3A8532322BE2D5D800F8619D /* LoginView.swift */, + ECFB77C22CAC149F00FF02BC /* DavinciView.swift */, + 3A3709952BEB038200AA7B51 /* AccessTokenView.swift */, + 3A8092F32BE99F0F00AD667F /* InputView.swift */, + ECFB77D32CAC163100FF02BC /* StorageView.swift */, + ECFB77D52CAC164E00FF02BC /* LoggerView.swift */, + ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */, + ECFB77D92CAC169F00FF02BC /* LogoutView.swift */, + ); + path = Views; + sourceTree = ""; + }; + ECFB77CF2CAC150200FF02BC /* ViewModels */ = { + isa = PBXGroup; + children = ( + ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */, + 3AB1C9F32BD6C6F3003FCE3C /* LoginViewModel.swift */, + 3ACD90E02BE586BC00DABCE6 /* DavinciViewModel.swift */, + 3A8092F52BE9BB6B00AD667F /* TokenViewModel.swift */, + A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */, + A588D0042BF6A0E500F9052A /* StorageViewModel.swift */, + ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */, + ECFB77DD2CAC172200FF02BC /* UserInfoViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + ECFB77D22CAC157700FF02BC /* Utilities */ = { + isa = PBXGroup; + children = ( + ECB75DC32C986312000517C9 /* ConfigurationManager.swift */, + 3AE945732BF27223007B3381 /* LoginUtility.swift */, + ); + path = Utilities; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -548,17 +596,25 @@ buildActionMask = 2147483647; files = ( A588D0072BF6A0E500F9052A /* LoggerViewModel.swift in Sources */, + ECFB77D12CAC153300FF02BC /* UserInfoView.swift in Sources */, 3A8092F62BE9BB6B00AD667F /* TokenViewModel.swift in Sources */, 3ACD90E12BE586BC00DABCE6 /* DavinciViewModel.swift in Sources */, + ECFB77DC2CAC16FC00FF02BC /* LogoutViewModel.swift in Sources */, + ECFB77DA2CAC16A400FF02BC /* LogoutView.swift in Sources */, 3AE945742BF27224007B3381 /* LoginUtility.swift in Sources */, A588D0062BF6A0E500F9052A /* StorageViewModel.swift in Sources */, + ECFB77D62CAC165100FF02BC /* LoggerView.swift in Sources */, + ECFB77D42CAC163500FF02BC /* StorageView.swift in Sources */, 3A8532332BE2D5D800F8619D /* LoginView.swift in Sources */, 3A5441332BCDF10900385131 /* ContentView.swift in Sources */, ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */, 3A8092F42BE99F0F00AD667F /* InputView.swift in Sources */, ECB75DC42C986312000517C9 /* ConfigurationManager.swift in Sources */, + ECFB77CD2CAC14A400FF02BC /* DavinciView.swift in Sources */, + ECFB77D82CAC168500FF02BC /* ConfigurationView.swift in Sources */, + ECFB77DE2CAC172800FF02BC /* UserInfoViewModel.swift in Sources */, 3AB1C9F42BD6C6F3003FCE3C /* LoginViewModel.swift in Sources */, - 3A3709962BEB038200AA7B51 /* AccessToken.swift in Sources */, + 3A3709962BEB038200AA7B51 /* AccessTokenView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SampleApps/PingExample/PingExample/ContentView.swift b/SampleApps/PingExample/PingExample/ContentView.swift deleted file mode 100644 index 70ea2f5..0000000 --- a/SampleApps/PingExample/PingExample/ContentView.swift +++ /dev/null @@ -1,201 +0,0 @@ -import SwiftUI - - -struct ContentView: View { - - - @State private var startDavinici = false - - @State private var path: [String] = [] - - @State private var configurationViewModel: ConfigurationViewModel = ConfigurationManager.shared.loadConfigurationViewModel() - - var body: some View { - NavigationStack(path: $path) { - List { - NavigationLink(value: "Configuration") { - Text("Edit configuration") - } - NavigationLink(value: "DaVinci") { - Text("Launch DaVinci") - } - NavigationLink(value: "Token") { - Text("Access Token") - } - NavigationLink(value: "User") { - Text("User Info") - } - NavigationLink(value: "Logout") { - Text("Logout") - } - }.navigationDestination(for: String.self) { item in - switch item { - case "Configuration": - ConfigurationView(viewmodel: $configurationViewModel) - case "DaVinci": - DavinciView(path: $path) - case "Token": - AccessTokenView(path: $path) - case "User": - UserInfoView(path: $path) - case "Logout": - LogOutView(path: $path) - default: - EmptyView() - } - }.navigationBarTitle("Ping DaVinci") - Image(uiImage: UIImage(named: "Logo")!) - .resizable() - .frame(width: 180.0, height: 180.0).clipped() - }.onAppear{ - ConfigurationManager.shared.createDavinciWorkflow() - } - } -} - - -struct LogOutView: View { - - @Binding var path: [String] - - @StateObject private var viewmodel = LogOutViewModel() - - var body: some View { - - Text("Logout") - .font(.title) - .navigationBarTitle("Logout", displayMode: .inline) - - NextButton(title: "Procced to logout") { - Task { - await viewmodel.logout() - path.removeLast() - } - } - - } -} - -struct ConfigurationView: View { - @Binding var viewmodel: ConfigurationViewModel - @State private var scopes: String = "" - - var body: some View { - Form { - Section { - Text("Client Id:") - TextField("Client Id", text: $viewmodel.clientId) - .disableAutocorrection(true) - .autocapitalization(.none) - } - Section { - Text("Redirect URI:") - TextField("Redirect URI", text: $viewmodel.redirectUri) - .disableAutocorrection(true) - .autocapitalization(.none) - } - Section { - Text("Discovery Endpoint:") - TextField("Discovery Endpoint", text: $viewmodel.discoveryEndpoint) - .disableAutocorrection(true) - .autocapitalization(.none) - } - Section { - Text("Scopes:") - TextField("scopes:", text: $scopes) - .disableAutocorrection(true) - .autocapitalization(.none) - } - } - .navigationTitle("Edit Configuration") - .onAppear{ - scopes = $viewmodel.scopes.wrappedValue.joined(separator: ",") - } - .onDisappear{ - viewmodel.scopes = scopes.components(separatedBy: ",") - ConfigurationManager.shared.saveConfiguration() - } - } -} - -struct SecondTabView: View { - var body: some View { - Text("LogOut") - .font(.title) - .navigationBarTitle("LogOut", displayMode: .inline) - - } -} - -struct Register: View { - var body: some View { - Text("Register") - .font(.title) - .navigationBarTitle("Register", displayMode: .inline) - } -} - -struct ForgotPassword: View { - var body: some View { - Text("ForgotPassword") - .font(.title) - .navigationBarTitle("ForgotPassword", displayMode: .inline) - } -} - -struct LoggerView: View { - var loggerViewModel = LoggerViewModel() - var body: some View { - Text("This View is for testing Logger functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Logger", displayMode: .inline) - .onAppear() { - loggerViewModel.setupLogger() - } - } -} - -struct StorageView: View { - var storageViewModel = StorageViewModel() - var body: some View { - Text("This View is for testing Storage functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Storage", displayMode: .inline) - .onAppear() { - Task { - await storageViewModel.setupMemoryStorage() - await storageViewModel.setupKeychainStorage() - } - } - } -} - -@main -struct MyApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} - -struct ActivityIndicatorView: View { - @Binding var isAnimating: Bool - let style: UIActivityIndicatorView.Style - let color: Color - - var body: some View { - if isAnimating { - VStack { - Spacer() - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: color)) - .padding() - Spacer() - } - .background(Color.black.opacity(0.4).ignoresSafeArea()) - } - } -} diff --git a/SampleApps/PingExample/PingExample/ConfigurationManager.swift b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift similarity index 83% rename from SampleApps/PingExample/PingExample/ConfigurationManager.swift rename to SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift index c8a95a6..bf08b6c 100644 --- a/SampleApps/PingExample/PingExample/ConfigurationManager.swift +++ b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift @@ -63,10 +63,20 @@ class ConfigurationManager: ObservableObject { } } return ConfigurationViewModel( - clientId: "[CLIENT ID]", + clientId: "[Client ID]", scopes: ["openid", "email", "address", "phone", "profile"], - redirectUri: "[REDIRECT URI]", - discoveryEndpoint: "[DISCOVERY ENDPOINT]" + redirectUri: "[Redirect URI]", + discoveryEndpoint: "[Discovery Endpoint]" ) } } + +/* + Example Values (Please create your own application as described in the documentation): + return ConfigurationViewModel( + clientId: "10a80cd7-a844-4cdf-b1c6-7dc2ccdb9769", + scopes: ["openid", "email", "address", "phone", "profile"], + redirectUri: "org.forgerock.demo://oauth2redirect", + discoveryEndpoint: "https://auth.pingone.com/5e508bc0-91e7-409b-8514-783bad6d1811/as/.well-known/openid-configuration" + ) + */ diff --git a/SampleApps/PingExample/PingExample/LoginUtility.swift b/SampleApps/PingExample/PingExample/Utilities/LoginUtility.swift similarity index 80% rename from SampleApps/PingExample/PingExample/LoginUtility.swift rename to SampleApps/PingExample/PingExample/Utilities/LoginUtility.swift index d03944e..4df0624 100644 --- a/SampleApps/PingExample/PingExample/LoginUtility.swift +++ b/SampleApps/PingExample/PingExample/Utilities/LoginUtility.swift @@ -83,4 +83,21 @@ struct RegisterNow: View { } } - +struct ActivityIndicatorView: View { + @Binding var isAnimating: Bool + let style: UIActivityIndicatorView.Style + let color: Color + + var body: some View { + if isAnimating { + VStack { + Spacer() + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: color)) + .padding() + Spacer() + } + .background(Color.black.opacity(0.4).ignoresSafeArea()) + } + } +} diff --git a/SampleApps/PingExample/PingExample/ConfigurationViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/ConfigurationViewModel.swift similarity index 100% rename from SampleApps/PingExample/PingExample/ConfigurationViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/ConfigurationViewModel.swift diff --git a/SampleApps/PingExample/PingExample/DavinciViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift similarity index 78% rename from SampleApps/PingExample/PingExample/DavinciViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift index b9f32a2..f038186 100644 --- a/SampleApps/PingExample/PingExample/DavinciViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift @@ -13,17 +13,6 @@ import PingDavinci import PingOidc import PingOrchestrate -//public let davinci = DaVinci.createDaVinci { config in -// //config.debug = true -// config.module(OidcModule.config) { oidcValue in -// let configViewModel = ConfigurationManager.shared.loadConfigurationViewModel() -// oidcValue.clientId = configViewModel.clientId -// oidcValue.scopes = Set(configViewModel.scopes) -// oidcValue.redirectUri = configViewModel.redirectUri -// oidcValue.discoveryEndpoint = configViewModel.discoveryEndpoint -// } -//} - class DavinciViewModel: ObservableObject { @Published public var data: StateNode = StateNode() @@ -36,7 +25,6 @@ class DavinciViewModel: ObservableObject { } } - private func startDavinci() async { await MainActor.run { diff --git a/SampleApps/PingExample/PingExample/LoggerViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift similarity index 99% rename from SampleApps/PingExample/PingExample/LoggerViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift index f6d2576..b2a6aca 100644 --- a/SampleApps/PingExample/PingExample/LoggerViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift @@ -14,8 +14,6 @@ import PingLogger class LoggerViewModel { func setupLogger() { - - var logger = LogManager.logger logger.d("Debug") logger.i("Info") diff --git a/SampleApps/PingExample/PingExample/LoginViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LoginViewModel.swift similarity index 100% rename from SampleApps/PingExample/PingExample/LoginViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/LoginViewModel.swift diff --git a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift new file mode 100644 index 0000000..4933269 --- /dev/null +++ b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift @@ -0,0 +1,24 @@ +// +// LogoutViewModel.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import Foundation + +class LogoutViewModel: ObservableObject { + + @Published var logout: String = "" + + func logout() async { + await ConfigurationManager.shared.davinci?.user()?.logout() + await MainActor.run { + logout = "Logout completed" + } + + } +} diff --git a/SampleApps/PingExample/PingExample/StorageViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift similarity index 100% rename from SampleApps/PingExample/PingExample/StorageViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift diff --git a/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift new file mode 100644 index 0000000..1f41717 --- /dev/null +++ b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift @@ -0,0 +1,44 @@ +// +// TokenViewModel.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import Foundation +import PingLogger + +class TokenViewModel: ObservableObject { + + @Published var accessToken: String = "" + + init() { + Task { + await accessToken() + } + } + + func accessToken() async { + let token = await ConfigurationManager.shared.davinci?.user()?.token() + switch token { + case .success(let accessToken): + await MainActor.run { + self.accessToken = String(describing: accessToken) + } + LogManager.standard.i("AccessToken: \(self.accessToken)") + case .failure(let error): + await MainActor.run { + self.accessToken = "Error: \(error.localizedDescription)" + } + LogManager.standard.e("", error: error) + case .none: + await MainActor.run { + self.accessToken = "Error: Token nil, need to log in" + } + } + + } +} diff --git a/SampleApps/PingExample/PingExample/TokenViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift similarity index 51% rename from SampleApps/PingExample/PingExample/TokenViewModel.swift rename to SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift index d1f7aa7..1c2fd42 100644 --- a/SampleApps/PingExample/PingExample/TokenViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift @@ -1,5 +1,5 @@ -// -// TokenViewModel.swift +// +// UserInfoViewModel.swift // PingExample // // Copyright (c) 2024 Ping Identity. All rights reserved. @@ -11,39 +11,6 @@ import Foundation import PingLogger -class TokenViewModel: ObservableObject { - - @Published var accessToken: String = "" - - - init() { - Task { - await accessToken() - } - } - - func accessToken() async { - let token = await ConfigurationManager.shared.davinci?.user()?.token() - switch token { - case .success(let accessToken): - await MainActor.run { - self.accessToken = String(describing: accessToken) - } - LogManager.standard.i("AccessToken: \(self.accessToken)") - case .failure(let error): - await MainActor.run { - self.accessToken = "Error: \(error.localizedDescription)" - } - LogManager.standard.e("", error: error) - case .none: - await MainActor.run { - self.accessToken = "Error: Token nil, need to log in" - } - } - - } -} - class UserInfoViewModel: ObservableObject { @Published var userInfo: String = "" @@ -76,16 +43,3 @@ class UserInfoViewModel: ObservableObject { } } } - -class LogOutViewModel: ObservableObject { - - @Published var logout: String = "" - - func logout() async { - await ConfigurationManager.shared.davinci?.user()?.logout() - await MainActor.run { - logout = "Logout completed" - } - - } -} diff --git a/SampleApps/PingExample/PingExample/AccessToken.swift b/SampleApps/PingExample/PingExample/Views/AccessTokenView.swift similarity index 62% rename from SampleApps/PingExample/PingExample/AccessToken.swift rename to SampleApps/PingExample/PingExample/Views/AccessTokenView.swift index 96f22ec..66bc05e 100644 --- a/SampleApps/PingExample/PingExample/AccessToken.swift +++ b/SampleApps/PingExample/PingExample/Views/AccessTokenView.swift @@ -34,24 +34,3 @@ struct AccessTokenView: View { } } - -struct UserInfoView: View { - @Binding var path: [String] - - @StateObject var vm = UserInfoViewModel() - - var body: some View { - - TextEditor(text: $vm.userInfo) - .foregroundStyle(.secondary) - .padding(.horizontal) - .navigationTitle("User Info") - - NextButton(title: "Procced to logout") { - Task { - await ConfigurationManager.shared.davinci?.user()?.logout() - path.removeLast() - } - } - } -} diff --git a/SampleApps/PingExample/PingExample/Views/ConfigurationView.swift b/SampleApps/PingExample/PingExample/Views/ConfigurationView.swift new file mode 100644 index 0000000..d358558 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/ConfigurationView.swift @@ -0,0 +1,53 @@ +// +// ConfigurationView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI + +struct ConfigurationView: View { + @Binding var viewmodel: ConfigurationViewModel + @State private var scopes: String = "" + + var body: some View { + Form { + Section { + Text("Client Id:") + TextField("Client Id", text: $viewmodel.clientId) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Redirect URI:") + TextField("Redirect URI", text: $viewmodel.redirectUri) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Discovery Endpoint:") + TextField("Discovery Endpoint", text: $viewmodel.discoveryEndpoint) + .disableAutocorrection(true) + .autocapitalization(.none) + } + Section { + Text("Scopes:") + TextField("scopes:", text: $scopes) + .disableAutocorrection(true) + .autocapitalization(.none) + } + } + .navigationTitle("Edit Configuration") + .onAppear{ + scopes = $viewmodel.scopes.wrappedValue.joined(separator: ",") + } + .onDisappear{ + viewmodel.scopes = scopes.components(separatedBy: ",") + ConfigurationManager.shared.saveConfiguration() + } + } +} diff --git a/SampleApps/PingExample/PingExample/Views/ContentView.swift b/SampleApps/PingExample/PingExample/Views/ContentView.swift new file mode 100644 index 0000000..2cabf46 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/ContentView.swift @@ -0,0 +1,62 @@ +import SwiftUI + + +struct ContentView: View { + + @State private var startDavinici = false + + @State private var path: [String] = [] + + @State private var configurationViewModel: ConfigurationViewModel = ConfigurationManager.shared.loadConfigurationViewModel() + + var body: some View { + NavigationStack(path: $path) { + List { + NavigationLink(value: "Configuration") { + Text("Edit configuration") + } + NavigationLink(value: "DaVinci") { + Text("Launch DaVinci") + } + NavigationLink(value: "Token") { + Text("Access Token") + } + NavigationLink(value: "User") { + Text("User Info") + } + NavigationLink(value: "Logout") { + Text("Logout") + } + }.navigationDestination(for: String.self) { item in + switch item { + case "Configuration": + ConfigurationView(viewmodel: $configurationViewModel) + case "DaVinci": + DavinciView(path: $path) + case "Token": + AccessTokenView(path: $path) + case "User": + UserInfoView(path: $path) + case "Logout": + LogoutView(path: $path) + default: + EmptyView() + } + }.navigationBarTitle("Ping DaVinci") + Image(uiImage: UIImage(named: "Logo")!) + .resizable() + .frame(width: 180.0, height: 180.0).clipped() + }.onAppear{ + ConfigurationManager.shared.createDavinciWorkflow() + } + } +} + +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/SampleApps/PingExample/PingExample/Views/DavinciView.swift b/SampleApps/PingExample/PingExample/Views/DavinciView.swift new file mode 100644 index 0000000..e3a99ea --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/DavinciView.swift @@ -0,0 +1,63 @@ +// +// DavinciView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI +import PingOrchestrate +import PingDavinci + +struct DavinciView: View { + + @StateObject var viewmodel = DavinciViewModel() + @Binding var path: [String] + + var body: some View { + ZStack { + ScrollView { + VStack { + Spacer() + switch viewmodel.data.currentNode { + case let connector as Connector: + ConnectorView(viewmodel: viewmodel, connector: connector) + case is SuccessNode: + VStack{}.onAppear { + path.removeLast() + path.append("Token") + } + case let errorNode as ErrorNode: + if let connector = viewmodel.data.previousNode as? Connector { + ConnectorView(viewmodel: viewmodel, connector: connector) + } + ErrorView(name: errorNode.cause.localizedDescription) + case let failureNode as FailureNode: + if let connector = viewmodel.data.previousNode as? Connector { + ConnectorView(viewmodel: viewmodel, connector: connector) + } + ErrorView(name: failureNode.message) + default: + EmptyView() + } + } + } + + Spacer() + + // Activity indicator + if viewmodel.isLoading { + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(4) // Increase spinner size if needed + .foregroundColor(.white) // Set spinner color + } + } + } +} diff --git a/SampleApps/PingExample/PingExample/InputView.swift b/SampleApps/PingExample/PingExample/Views/InputView.swift similarity index 100% rename from SampleApps/PingExample/PingExample/InputView.swift rename to SampleApps/PingExample/PingExample/Views/InputView.swift diff --git a/SampleApps/PingExample/PingExample/Views/LoggerView.swift b/SampleApps/PingExample/PingExample/Views/LoggerView.swift new file mode 100644 index 0000000..a55b77d --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/LoggerView.swift @@ -0,0 +1,24 @@ +// +// LoggerView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI + +struct LoggerView: View { + var loggerViewModel = LoggerViewModel() + var body: some View { + Text("This View is for testing Logger functionality.\nPlease check the Console Logs") + .font(.title3) + .multilineTextAlignment(.center) + .navigationBarTitle("Logger", displayMode: .inline) + .onAppear() { + loggerViewModel.setupLogger() + } + } +} diff --git a/SampleApps/PingExample/PingExample/LoginView.swift b/SampleApps/PingExample/PingExample/Views/LoginView.swift similarity index 59% rename from SampleApps/PingExample/PingExample/LoginView.swift rename to SampleApps/PingExample/PingExample/Views/LoginView.swift index 830bd1f..db79b61 100644 --- a/SampleApps/PingExample/PingExample/LoginView.swift +++ b/SampleApps/PingExample/PingExample/Views/LoginView.swift @@ -13,56 +13,6 @@ import SwiftUI import PingOrchestrate import PingDavinci -struct DavinciView: View { - - @StateObject var viewmodel = DavinciViewModel() - @Binding var path: [String] - - var body: some View { - ZStack { - ScrollView { - VStack { - Spacer() - switch viewmodel.data.currentNode { - case let connector as Connector: - ConnectorView(viewmodel: viewmodel, connector: connector) - case is SuccessNode: - VStack{}.onAppear { - path.removeLast() - path.append("Token") - } - case let errorNode as ErrorNode: - if let connector = viewmodel.data.previousNode as? Connector { - ConnectorView(viewmodel: viewmodel, connector: connector) - } - ErrorView(name: errorNode.cause.localizedDescription) - case let failureNode as FailureNode: - if let connector = viewmodel.data.previousNode as? Connector { - ConnectorView(viewmodel: viewmodel, connector: connector) - } - ErrorView(name: failureNode.message) - default: - EmptyView() - } - } - } - - Spacer() - - // Activity indicator - if viewmodel.isLoading { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(4) // Increase spinner size if needed - .foregroundColor(.white) // Set spinner color - } - } - } -} - struct ConnectorView: View { @ObservedObject var viewmodel: DavinciViewModel @@ -73,7 +23,7 @@ struct ConnectorView: View { Image("Logo").resizable().scaledToFill().frame(width: 100, height: 100) .padding(.vertical, 32) HeaderView(name: connector.name) - NewLoginView( + LoginView( davinciViewModel: viewmodel, connector: connector, collectorsList: connector.collectors) } @@ -101,7 +51,7 @@ struct HeaderView: View { } } -struct NewLoginView: View { +struct LoginView: View { // MARK: - Propertiers @ObservedObject var davinciViewModel: DavinciViewModel @@ -117,6 +67,9 @@ struct NewLoginView: View { ForEach(collectorsList, id: \.id) { field in VStack { + /* + Add integration to present fields + */ if let text = field as? TextCollector { InputView(text: text.value, placeholderString: text.label, field: text) } @@ -128,6 +81,9 @@ struct NewLoginView: View { if let submitButton = field as? SubmitCollector { InputButton(title: submitButton.label, field: submitButton) { Task { + /* + Call next + */ await davinciViewModel.next(node: connector) } } diff --git a/SampleApps/PingExample/PingExample/Views/LogoutView.swift b/SampleApps/PingExample/PingExample/Views/LogoutView.swift new file mode 100644 index 0000000..1565571 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/LogoutView.swift @@ -0,0 +1,33 @@ +// +// LogoutView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI + +struct LogoutView: View { + + @Binding var path: [String] + + @StateObject private var viewmodel = LogoutViewModel() + + var body: some View { + + Text("Logout") + .font(.title) + .navigationBarTitle("Logout", displayMode: .inline) + + NextButton(title: "Procced to logout") { + Task { + await viewmodel.logout() + path.removeLast() + } + } + + } +} diff --git a/SampleApps/PingExample/PingExample/Views/StorageView.swift b/SampleApps/PingExample/PingExample/Views/StorageView.swift new file mode 100644 index 0000000..f079312 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/StorageView.swift @@ -0,0 +1,27 @@ +// +// StorageView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI + +struct StorageView: View { + var storageViewModel = StorageViewModel() + var body: some View { + Text("This View is for testing Storage functionality.\nPlease check the Console Logs") + .font(.title3) + .multilineTextAlignment(.center) + .navigationBarTitle("Storage", displayMode: .inline) + .onAppear() { + Task { + await storageViewModel.setupMemoryStorage() + await storageViewModel.setupKeychainStorage() + } + } + } +} diff --git a/SampleApps/PingExample/PingExample/Views/UserInfoView.swift b/SampleApps/PingExample/PingExample/Views/UserInfoView.swift new file mode 100644 index 0000000..02d80a8 --- /dev/null +++ b/SampleApps/PingExample/PingExample/Views/UserInfoView.swift @@ -0,0 +1,32 @@ +// +// UserInfoView.swift +// PingExample +// +// Copyright (c) 2024 Ping Identity. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +import SwiftUI + +struct UserInfoView: View { + @Binding var path: [String] + + @StateObject var vm = UserInfoViewModel() + + var body: some View { + + TextEditor(text: $vm.userInfo) + .foregroundStyle(.secondary) + .padding(.horizontal) + .navigationTitle("User Info") + + NextButton(title: "Procced to logout") { + Task { + await ConfigurationManager.shared.davinci?.user()?.logout() + path.removeLast() + } + } + } +} From 8ce3209a1d434478475c485b46600c5010676df9 Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Tue, 1 Oct 2024 17:12:54 +0100 Subject: [PATCH 3/5] Adding integration steps TODOs: and removing unused files --- .../PingExample.xcodeproj/project.pbxproj | 18 +--- .../Utilities/ConfigurationManager.swift | 34 +++++-- .../ViewModels/DavinciViewModel.swift | 28 +++++- .../ViewModels/LoggerViewModel.swift | 42 --------- .../ViewModels/LogoutViewModel.swift | 9 +- .../ViewModels/StorageViewModel.swift | 40 -------- .../ViewModels/TokenViewModel.swift | 10 +- .../ViewModels/UserInfoViewModel.swift | 11 ++- .../PingExample/Views/LoggerView.swift | 24 ----- .../PingExample/Views/LoginView.swift | 93 +++++++++++-------- .../PingExample/Views/StorageView.swift | 27 ------ 11 files changed, 132 insertions(+), 204 deletions(-) delete mode 100644 SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift delete mode 100644 SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift delete mode 100644 SampleApps/PingExample/PingExample/Views/LoggerView.swift delete mode 100644 SampleApps/PingExample/PingExample/Views/StorageView.swift diff --git a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj index 7d930b0..54a18cd 100644 --- a/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj +++ b/SampleApps/PingExample/PingExample.xcodeproj/project.pbxproj @@ -24,8 +24,6 @@ 3ACD90E12BE586BC00DABCE6 /* DavinciViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACD90E02BE586BC00DABCE6 /* DavinciViewModel.swift */; }; 3AE945742BF27224007B3381 /* LoginUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE945732BF27223007B3381 /* LoginUtility.swift */; }; A588D0032BF6A00800F9052A /* PingOrchestrate.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3A5442272BCE1F3800385131 /* PingOrchestrate.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - A588D0062BF6A0E500F9052A /* StorageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A588D0042BF6A0E500F9052A /* StorageViewModel.swift */; }; - A588D0072BF6A0E500F9052A /* LoggerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */; }; A588D0082BF6A1FF00F9052A /* PingLogger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFF22BF69F1A00F9052A /* PingLogger.framework */; }; A588D0092BF6A1FF00F9052A /* PingLogger.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFF22BF69F1A00F9052A /* PingLogger.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A588D00A2BF6A20500F9052A /* PingStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A588CFFF2BF69F3300F9052A /* PingStorage.framework */; }; @@ -34,8 +32,6 @@ ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */; }; ECFB77CD2CAC14A400FF02BC /* DavinciView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77C22CAC149F00FF02BC /* DavinciView.swift */; }; ECFB77D12CAC153300FF02BC /* UserInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D02CAC152A00FF02BC /* UserInfoView.swift */; }; - ECFB77D42CAC163500FF02BC /* StorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D32CAC163100FF02BC /* StorageView.swift */; }; - ECFB77D62CAC165100FF02BC /* LoggerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D52CAC164E00FF02BC /* LoggerView.swift */; }; ECFB77D82CAC168500FF02BC /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */; }; ECFB77DA2CAC16A400FF02BC /* LogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77D92CAC169F00FF02BC /* LogoutView.swift */; }; ECFB77DC2CAC16FC00FF02BC /* LogoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */; }; @@ -169,15 +165,11 @@ 3AE945732BF27223007B3381 /* LoginUtility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginUtility.swift; sourceTree = ""; }; A588CFEA2BF69F1A00F9052A /* PingLogger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PingLogger.xcodeproj; path = ../../PingLogger/PingLogger.xcodeproj; sourceTree = ""; }; A588CFF92BF69F3300F9052A /* PingStorage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PingStorage.xcodeproj; path = ../../PingStorage/PingStorage.xcodeproj; sourceTree = ""; }; - A588D0042BF6A0E500F9052A /* StorageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageViewModel.swift; sourceTree = ""; }; - A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerViewModel.swift; sourceTree = ""; }; ECB75DC32C986312000517C9 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationViewModel.swift; sourceTree = ""; }; ECB75DC72C999368000517C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; ECFB77C22CAC149F00FF02BC /* DavinciView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DavinciView.swift; sourceTree = ""; }; ECFB77D02CAC152A00FF02BC /* UserInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoView.swift; sourceTree = ""; }; - ECFB77D32CAC163100FF02BC /* StorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageView.swift; sourceTree = ""; }; - ECFB77D52CAC164E00FF02BC /* LoggerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerView.swift; sourceTree = ""; }; ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = ""; }; ECFB77D92CAC169F00FF02BC /* LogoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutView.swift; sourceTree = ""; }; ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutViewModel.swift; sourceTree = ""; }; @@ -337,8 +329,6 @@ ECFB77C22CAC149F00FF02BC /* DavinciView.swift */, 3A3709952BEB038200AA7B51 /* AccessTokenView.swift */, 3A8092F32BE99F0F00AD667F /* InputView.swift */, - ECFB77D32CAC163100FF02BC /* StorageView.swift */, - ECFB77D52CAC164E00FF02BC /* LoggerView.swift */, ECFB77D72CAC168000FF02BC /* ConfigurationView.swift */, ECFB77D92CAC169F00FF02BC /* LogoutView.swift */, ); @@ -349,11 +339,9 @@ isa = PBXGroup; children = ( ECB75DC52C986663000517C9 /* ConfigurationViewModel.swift */, - 3AB1C9F32BD6C6F3003FCE3C /* LoginViewModel.swift */, 3ACD90E02BE586BC00DABCE6 /* DavinciViewModel.swift */, + 3AB1C9F32BD6C6F3003FCE3C /* LoginViewModel.swift */, 3A8092F52BE9BB6B00AD667F /* TokenViewModel.swift */, - A588D0052BF6A0E500F9052A /* LoggerViewModel.swift */, - A588D0042BF6A0E500F9052A /* StorageViewModel.swift */, ECFB77DB2CAC16FA00FF02BC /* LogoutViewModel.swift */, ECFB77DD2CAC172200FF02BC /* UserInfoViewModel.swift */, ); @@ -595,16 +583,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A588D0072BF6A0E500F9052A /* LoggerViewModel.swift in Sources */, ECFB77D12CAC153300FF02BC /* UserInfoView.swift in Sources */, 3A8092F62BE9BB6B00AD667F /* TokenViewModel.swift in Sources */, 3ACD90E12BE586BC00DABCE6 /* DavinciViewModel.swift in Sources */, ECFB77DC2CAC16FC00FF02BC /* LogoutViewModel.swift in Sources */, ECFB77DA2CAC16A400FF02BC /* LogoutView.swift in Sources */, 3AE945742BF27224007B3381 /* LoginUtility.swift in Sources */, - A588D0062BF6A0E500F9052A /* StorageViewModel.swift in Sources */, - ECFB77D62CAC165100FF02BC /* LoggerView.swift in Sources */, - ECFB77D42CAC163500FF02BC /* StorageView.swift in Sources */, 3A8532332BE2D5D800F8619D /* LoginView.swift in Sources */, 3A5441332BCDF10900385131 /* ContentView.swift in Sources */, ECB75DC62C986663000517C9 /* ConfigurationViewModel.swift in Sources */, diff --git a/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift index bf08b6c..8ed40f0 100644 --- a/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift +++ b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift @@ -26,15 +26,22 @@ class ConfigurationManager: ObservableObject { public func createDavinciWorkflow() { if let currentConfiguration = self.currentConfigurationViewModel { - self.davinci = DaVinci.createDaVinci { config in - //config.debug = true - config.module(OidcModule.config) { oidcValue in - oidcValue.clientId = currentConfiguration.clientId - oidcValue.scopes = Set(currentConfiguration.scopes) - oidcValue.redirectUri = currentConfiguration.redirectUri - oidcValue.discoveryEndpoint = currentConfiguration.discoveryEndpoint - } - } + //TODO: Integration Point. STEP 2 + /* + Based on the current `ConfigurationViewModel` create a DaVinci instance by calling the + `DaVinci.createDaVinci` SDK method. This will create a workflow, ready to be called + and start an Authorization/Authentication flow based on your configuration. + Example: + self.davinci = DaVinci.createDaVinci { config in + //config.debug = true + config.module(OidcModule.config) { oidcValue in + oidcValue.clientId = currentConfiguration.clientId + oidcValue.scopes = Set(currentConfiguration.scopes) + oidcValue.redirectUri = currentConfiguration.redirectUri + oidcValue.discoveryEndpoint = currentConfiguration.discoveryEndpoint + } + } + */ } } @@ -62,6 +69,15 @@ class ConfigurationManager: ObservableObject { return ConfigurationViewModel(clientId: loadedConfiguration.clientId, scopes: loadedConfiguration.scopes, redirectUri: loadedConfiguration.redirectUri, discoveryEndpoint: loadedConfiguration.discoveryEndpoint) } } + //TODO: Integration Point. STEP 1 + /* + Create and return a ConfigurationViewModel. This will serve as the defaultConfigurationViewModel for the runtime of your application. + Any changes, by editing during runtime will be saved using the `saveConfiguration` method and loaded on start up using the `loadConfigurationViewModel` Method. + The developer needs to set up a configuration that the SDK will use to communicate the Davinci. These OAuth2.0 details will be used when making + authorization calls. The required properties are: `clientId`, `scopes`, `redirectUri`, `discoveryEndpoint`. In this Sample Application, + creating a ConfigurationViewModel, acts as the SDK configuration starting point. The `ConfigurationManager` is a Singleton object that + will remain in memory for the excecution of the application. To continue edit the values below between the '" "' symbols. + */ return ConfigurationViewModel( clientId: "[Client ID]", scopes: ["openid", "email", "address", "phone", "profile"], diff --git a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift index f038186..2e14dbf 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift @@ -30,10 +30,26 @@ class DavinciViewModel: ObservableObject { await MainActor.run { isLoading = true } - let node = await ConfigurationManager.shared.davinci?.start() + //TODO: Integration Point. STEP 3 + /* + When the application is ready to start the Davinci flow, the SDK `start()` method needs to be called. + In this Sample app we have created a reference to the DaVinci Object inside the `ConfigurationManager` singleton class. + From any place in the app you can access this, by calling `ConfigurationManager.shared.davinci`. + Use the this reference and an `async/await` pattern to start the flow and receive the first node/connector. + + Starting a DaVinci flow, will return a Connector object. This will be assigned to a Published property of type: `StateNode`. + This will be consumed automatically, as this class is an `ObservableObject` from the DavinciView struct, that acts as the Flow view. + + Each connector can be either a `SuccessNode` or a `FailureNode` or an `ErrorNode` or finally a `Connector`. + A `Connector` contains the `collectors` array that is the `Inputs` and `Outputs` that developers need to present to collect the user inputs for the flow. + Notice that in order to move to the next connect in the flow we need to call `connector.next()`. + + Example: let node = await ConfigurationManager.shared.davinci?.start() + */ + if let connector = node as? Connector { - let node = await connector.next() + let node = await connector.next() // <-- In the first step, calls DaVinci and starts the flow. On next steps, submits and returns the next `Connector`. await MainActor.run { self.data = StateNode(currentNode: node, previousNode: node) } @@ -54,6 +70,14 @@ class DavinciViewModel: ObservableObject { isLoading = true } if let connector = node as? Connector { + //TODO: Integration Point. STEP 4 + /* + At this point the application is ready to submit the Node/Connector and receive the "next step". + To submit the data call `connector.next()` + + Example: + let next = await connector.next() + */ let next = await connector.next() await MainActor.run { self.data = StateNode(currentNode: next, previousNode: node) diff --git a/SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift deleted file mode 100644 index b2a6aca..0000000 --- a/SampleApps/PingExample/PingExample/ViewModels/LoggerViewModel.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// LoggerViewModel.swift -// PingExample -// -// Copyright (c) 2024 Ping Identity. All rights reserved. -// -// This software may be modified and distributed under the terms -// of the MIT license. See the LICENSE file for details. -// - -import Foundation -import PingLogger - -class LoggerViewModel { - - func setupLogger() { - var logger = LogManager.logger - logger.d("Debug") - logger.i("Info") - logger.w("Warning", error: TestError.success) - logger.e("Error", error: TestError.failure) - - logger = LogManager.standard - logger.d("Debug") - logger.i("Test log") - logger.w("Warning", error: TestError.success) - logger.e("Error", error: TestError.failure) - - logger = LogManager.warning - logger.d("Debug") - logger.i("Test log") - logger.w("Warning", error: TestError.success) - logger.e("Error", error: TestError.failure) - - } - - enum TestError: Error { - case success - case failure - } - -} diff --git a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift index 4933269..cdb3bcb 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift @@ -15,7 +15,14 @@ class LogoutViewModel: ObservableObject { @Published var logout: String = "" func logout() async { - await ConfigurationManager.shared.davinci?.user()?.logout() + //TODO: Integration Point. STEP 7 + /* + Use the ConfigurationManager shared class to retrieve the davinci class reference. + When wanting to logout the user, you need to call `user()?.logout()`. + + Example use: "await ConfigurationManager.shared.davinci?.user()?.logout()" + */ + await MainActor.run { logout = "Logout completed" } diff --git a/SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift deleted file mode 100644 index dddcf97..0000000 --- a/SampleApps/PingExample/PingExample/ViewModels/StorageViewModel.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// StorageViewModel.swift -// PingExample -// -// Copyright (c) 2024 Ping Identity. All rights reserved. -// -// This software may be modified and distributed under the terms -// of the MIT license. See the LICENSE file for details. -// - -import Foundation -import PingStorage -import PingLogger - -class StorageViewModel { - - func setupMemoryStorage() async { - do { - let memoryStorage1 = MemoryStorage() - try await memoryStorage1.save(item: "Andy") - let storedValue1 = try await memoryStorage1.get() - LogManager.standard.i("Memory Storage value: \(storedValue1!)") - } catch { - LogManager.standard.e("", error: error) - } - - } - - - func setupKeychainStorage() async { - do { - let keychainStorage = KeychainStorage(account: "token", encryptor: SecuredKeyEncryptor() ?? NoEncryptor()) - try await keychainStorage.save(item: "Jey") - let storedValue = try await keychainStorage.get() - LogManager.standard.i("Kechain Storage value: \(storedValue!)") - } catch { - LogManager.standard.e("", error: error) - } - } -} diff --git a/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift index 1f41717..882b096 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift @@ -22,7 +22,15 @@ class TokenViewModel: ObservableObject { } func accessToken() async { - let token = await ConfigurationManager.shared.davinci?.user()?.token() + //TODO: Integration Point. STEP 6 + /* + Use the ConfigurationManager shared class to retrieve the user token. + The ViewModel will save that in the `self.accessToken` variable and + the view will display the value. + + Example use: "let token = await ConfigurationManager.shared.davinci?.user()?.token()" + */ + switch token { case .success(let accessToken): await MainActor.run { diff --git a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift index 1c2fd42..b37b6c1 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift @@ -22,7 +22,16 @@ class UserInfoViewModel: ObservableObject { } func fetchUserInfo() async { - let userInfo = await ConfigurationManager.shared.davinci?.user()?.userinfo(cache: false) + /* + //TODO: Integration Point. STEP 6 + Use the ConfigurationManager shared class to retrieve the user info. + The ViewModel will save that in the `self.userInfo` variable and + the view will display the value. + + Example use: "let userInfo = await ConfigurationManager.shared.davinci?.user()?.userinfo(cache: false)" + */ + + switch userInfo { case .success(let userInfoDictionary): await MainActor.run { diff --git a/SampleApps/PingExample/PingExample/Views/LoggerView.swift b/SampleApps/PingExample/PingExample/Views/LoggerView.swift deleted file mode 100644 index a55b77d..0000000 --- a/SampleApps/PingExample/PingExample/Views/LoggerView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// LoggerView.swift -// PingExample -// -// Copyright (c) 2024 Ping Identity. All rights reserved. -// -// This software may be modified and distributed under the terms -// of the MIT license. See the LICENSE file for details. -// - -import SwiftUI - -struct LoggerView: View { - var loggerViewModel = LoggerViewModel() - var body: some View { - Text("This View is for testing Logger functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Logger", displayMode: .inline) - .onAppear() { - loggerViewModel.setupLogger() - } - } -} diff --git a/SampleApps/PingExample/PingExample/Views/LoginView.swift b/SampleApps/PingExample/PingExample/Views/LoginView.swift index db79b61..056efc1 100644 --- a/SampleApps/PingExample/PingExample/Views/LoginView.swift +++ b/SampleApps/PingExample/PingExample/Views/LoginView.swift @@ -64,46 +64,59 @@ struct LoginView: View { VStack { - ForEach(collectorsList, id: \.id) { field in - - VStack { - /* - Add integration to present fields - */ - if let text = field as? TextCollector { - InputView(text: text.value, placeholderString: text.label, field: text) - } - - if let password = field as? PasswordCollector { - InputView(placeholderString: password.label, secureField: true, field: password) - } - - if let submitButton = field as? SubmitCollector { - InputButton(title: submitButton.label, field: submitButton) { - Task { - /* - Call next - */ - await davinciViewModel.next(node: connector) - } - } - } - - }.padding(.horizontal, 5).padding(.top, 20) - - - if let flowButton = field as? FlowCollector { - Button(action: { - flowButton.value = "action" - Task { - await davinciViewModel.next(node: connector) - } - }) { - Text(flowButton.label) - .foregroundColor(.black) - } - } - } + //TODO: Integration Point. STEP 5 + /* + At this point the DaVinci flow has returned Collectors to present. + As a developer you will need to go though the returned collectors (`collectorsList` in this case) + and build your view. Notice that Flows, similar to PingAM Journeys can be dynamic and you views + will need to adapt to what the server sends. + + As mentioned on previous steps the `Connector` object contains a list of `Collectors`. Those `Collectors` + contain input and outputs. In this ViewModel, a conviniece property to access the collectors directly has been set up. + + To use this loop through the `collectorsList` and create `InputViews` for each collector based on its type. + The SDK at the moment supports the following collectors returned from DaVinci through the use of an HTML Connector: + 1. `TextCollector` + 2. `PasswordCollector` + 3. `SubmitCollector` + 4. `FlowCollector` + + Example for `TextCollector`: + if let text = field as? TextCollector { + InputView(text: text.value, placeholderString: text.label, field: text) + } + + Example for `PasswordCollector` + if let password = field as? PasswordCollector { + InputView(placeholderString: password.label, secureField: true, field: password) + } + + Example for `SubmitCollector` + if let submitButton = field as? SubmitCollector { + InputButton(title: submitButton.label, field: submitButton) { + Task { + /* + Call next + */ + await davinciViewModel.next(node: connector) + } + } + } + + If the type is `FlowCollector` you will need to submit to DaVinci by calling `.next()` + Example: + if let flowButton = field as? FlowCollector { + Button(action: { + flowButton.value = "action" + Task { + await davinciViewModel.next(node: connector) + } + }) { + Text(flowButton.label) + .foregroundColor(.black) + } + } + */ } } } diff --git a/SampleApps/PingExample/PingExample/Views/StorageView.swift b/SampleApps/PingExample/PingExample/Views/StorageView.swift deleted file mode 100644 index f079312..0000000 --- a/SampleApps/PingExample/PingExample/Views/StorageView.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// StorageView.swift -// PingExample -// -// Copyright (c) 2024 Ping Identity. All rights reserved. -// -// This software may be modified and distributed under the terms -// of the MIT license. See the LICENSE file for details. -// - -import SwiftUI - -struct StorageView: View { - var storageViewModel = StorageViewModel() - var body: some View { - Text("This View is for testing Storage functionality.\nPlease check the Console Logs") - .font(.title3) - .multilineTextAlignment(.center) - .navigationBarTitle("Storage", displayMode: .inline) - .onAppear() { - Task { - await storageViewModel.setupMemoryStorage() - await storageViewModel.setupKeychainStorage() - } - } - } -} From 4c0723e163afc1524a3a542552c5c6346bbddee4 Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Mon, 7 Oct 2024 13:24:46 +0100 Subject: [PATCH 4/5] Updated Steps --- .../PingExample/PingExample/ViewModels/DavinciViewModel.swift | 2 +- .../PingExample/PingExample/ViewModels/LogoutViewModel.swift | 2 +- .../PingExample/PingExample/ViewModels/UserInfoViewModel.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift index 2e14dbf..340633d 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift @@ -78,7 +78,7 @@ class DavinciViewModel: ObservableObject { Example: let next = await connector.next() */ - let next = await connector.next() + await MainActor.run { self.data = StateNode(currentNode: next, previousNode: node) isLoading = false diff --git a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift index cdb3bcb..246906f 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift @@ -15,7 +15,7 @@ class LogoutViewModel: ObservableObject { @Published var logout: String = "" func logout() async { - //TODO: Integration Point. STEP 7 + //TODO: Integration Point. STEP 8 /* Use the ConfigurationManager shared class to retrieve the davinci class reference. When wanting to logout the user, you need to call `user()?.logout()`. diff --git a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift index b37b6c1..809ee42 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift @@ -23,7 +23,7 @@ class UserInfoViewModel: ObservableObject { func fetchUserInfo() async { /* - //TODO: Integration Point. STEP 6 + //TODO: Integration Point. STEP 7 Use the ConfigurationManager shared class to retrieve the user info. The ViewModel will save that in the `self.userInfo` variable and the view will display the value. From 095e28295c71aa7e4db4148fd947aaad5c8761e0 Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Wed, 9 Oct 2024 12:25:06 +0100 Subject: [PATCH 5/5] Adding completed code --- .../Utilities/ConfigurationManager.swift | 9 ++ .../ViewModels/DavinciViewModel.swift | 4 +- .../ViewModels/LogoutViewModel.swift | 2 +- .../ViewModels/TokenViewModel.swift | 2 +- .../ViewModels/UserInfoViewModel.swift | 2 +- .../PingExample/Views/LoginView.swift | 137 +++++++++++------- 6 files changed, 97 insertions(+), 59 deletions(-) diff --git a/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift index 8ed40f0..1bb1cc5 100644 --- a/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift +++ b/SampleApps/PingExample/PingExample/Utilities/ConfigurationManager.swift @@ -42,6 +42,15 @@ class ConfigurationManager: ObservableObject { } } */ + self.davinci = DaVinci.createDaVinci { config in + //config.debug = true + config.module(OidcModule.config) { oidcValue in + oidcValue.clientId = currentConfiguration.clientId + oidcValue.scopes = Set(currentConfiguration.scopes) + oidcValue.redirectUri = currentConfiguration.redirectUri + oidcValue.discoveryEndpoint = currentConfiguration.discoveryEndpoint + } + } } } diff --git a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift index 340633d..fd268ac 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/DavinciViewModel.swift @@ -47,7 +47,7 @@ class DavinciViewModel: ObservableObject { Example: let node = await ConfigurationManager.shared.davinci?.start() */ - + let node = await ConfigurationManager.shared.davinci?.start() if let connector = node as? Connector { let node = await connector.next() // <-- In the first step, calls DaVinci and starts the flow. On next steps, submits and returns the next `Connector`. await MainActor.run { @@ -78,7 +78,7 @@ class DavinciViewModel: ObservableObject { Example: let next = await connector.next() */ - + let next = await connector.next() await MainActor.run { self.data = StateNode(currentNode: next, previousNode: node) isLoading = false diff --git a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift index 246906f..8907022 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/LogoutViewModel.swift @@ -22,7 +22,7 @@ class LogoutViewModel: ObservableObject { Example use: "await ConfigurationManager.shared.davinci?.user()?.logout()" */ - + await ConfigurationManager.shared.davinci?.user()?.logout() await MainActor.run { logout = "Logout completed" } diff --git a/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift index 882b096..f26bc07 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/TokenViewModel.swift @@ -30,7 +30,7 @@ class TokenViewModel: ObservableObject { Example use: "let token = await ConfigurationManager.shared.davinci?.user()?.token()" */ - + let token = await ConfigurationManager.shared.davinci?.user()?.token() switch token { case .success(let accessToken): await MainActor.run { diff --git a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift index 809ee42..e0678ca 100644 --- a/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift +++ b/SampleApps/PingExample/PingExample/ViewModels/UserInfoViewModel.swift @@ -31,7 +31,7 @@ class UserInfoViewModel: ObservableObject { Example use: "let userInfo = await ConfigurationManager.shared.davinci?.user()?.userinfo(cache: false)" */ - + let userInfo = await ConfigurationManager.shared.davinci?.user()?.userinfo(cache: false) switch userInfo { case .success(let userInfoDictionary): await MainActor.run { diff --git a/SampleApps/PingExample/PingExample/Views/LoginView.swift b/SampleApps/PingExample/PingExample/Views/LoginView.swift index 056efc1..7255c2e 100644 --- a/SampleApps/PingExample/PingExample/Views/LoginView.swift +++ b/SampleApps/PingExample/PingExample/Views/LoginView.swift @@ -63,60 +63,89 @@ struct LoginView: View { var body: some View { VStack { - - //TODO: Integration Point. STEP 5 - /* - At this point the DaVinci flow has returned Collectors to present. - As a developer you will need to go though the returned collectors (`collectorsList` in this case) - and build your view. Notice that Flows, similar to PingAM Journeys can be dynamic and you views - will need to adapt to what the server sends. - - As mentioned on previous steps the `Connector` object contains a list of `Collectors`. Those `Collectors` - contain input and outputs. In this ViewModel, a conviniece property to access the collectors directly has been set up. - - To use this loop through the `collectorsList` and create `InputViews` for each collector based on its type. - The SDK at the moment supports the following collectors returned from DaVinci through the use of an HTML Connector: - 1. `TextCollector` - 2. `PasswordCollector` - 3. `SubmitCollector` - 4. `FlowCollector` - - Example for `TextCollector`: - if let text = field as? TextCollector { - InputView(text: text.value, placeholderString: text.label, field: text) - } - - Example for `PasswordCollector` - if let password = field as? PasswordCollector { - InputView(placeholderString: password.label, secureField: true, field: password) - } - - Example for `SubmitCollector` - if let submitButton = field as? SubmitCollector { - InputButton(title: submitButton.label, field: submitButton) { - Task { - /* - Call next - */ - await davinciViewModel.next(node: connector) - } - } - } - - If the type is `FlowCollector` you will need to submit to DaVinci by calling `.next()` - Example: - if let flowButton = field as? FlowCollector { - Button(action: { - flowButton.value = "action" - Task { - await davinciViewModel.next(node: connector) - } - }) { - Text(flowButton.label) - .foregroundColor(.black) - } - } - */ + //TODO: Integration Point. STEP 5 + /* + At this point the DaVinci flow has returned Collectors to present. + As a developer you will need to go though the returned collectors (`collectorsList` in this case) + and build your view. Notice that Flows, similar to PingAM Journeys can be dynamic and you views + will need to adapt to what the server sends. + + As mentioned on previous steps the `Connector` object contains a list of `Collectors`. Those `Collectors` + contain input and outputs. In this ViewModel, a conviniece property to access the collectors directly has been set up. + + To use this loop through the `collectorsList` and create `InputViews` for each collector based on its type. + The SDK at the moment supports the following collectors returned from DaVinci through the use of an HTML Connector: + 1. `TextCollector` + 2. `PasswordCollector` + 3. `SubmitCollector` + 4. `FlowCollector` + + Example for `TextCollector`: + if let text = field as? TextCollector { + InputView(text: text.value, placeholderString: text.label, field: text) + } + + Example for `PasswordCollector` + if let password = field as? PasswordCollector { + InputView(placeholderString: password.label, secureField: true, field: password) + } + + Example for `SubmitCollector` + if let submitButton = field as? SubmitCollector { + InputButton(title: submitButton.label, field: submitButton) { + Task { + /* + Call next + */ + await davinciViewModel.next(node: connector) + } + } + } + + If the type is `FlowCollector` you will need to submit to DaVinci by calling `.next()` + Example: + if let flowButton = field as? FlowCollector { + Button(action: { + flowButton.value = "action" + Task { + await davinciViewModel.next(node: connector) + } + }) { + Text(flowButton.label) + .foregroundColor(.black) + } + } + */ + ForEach(collectorsList, id: \.id) { field in + if let text = field as? TextCollector { + InputView(text: text.value, placeholderString: text.label, field: text) + } + if let password = field as? PasswordCollector { + InputView(placeholderString: password.label, secureField: true, field: password) + } + if let submitButton = field as? SubmitCollector { + InputButton(title: submitButton.label, field: submitButton) { + Task { + /* + Call next + */ + await davinciViewModel.next(node: connector) + } + } + } + if let flowButton = field as? FlowCollector { + Button(action: { + flowButton.value = "action" + Task { + await davinciViewModel.next(node: connector) + } + }) { + Text(flowButton.label) + .foregroundColor(.black) + } + } + } + } } }