diff --git a/RowndFramework.xcodeproj/project.pbxproj b/RowndFramework.xcodeproj/project.pbxproj index af52d0d..110da9a 100644 --- a/RowndFramework.xcodeproj/project.pbxproj +++ b/RowndFramework.xcodeproj/project.pbxproj @@ -33,6 +33,9 @@ B374E5662855873100955719 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = B374E5652855872F00955719 /* Color.swift */; }; B38A4085287E16EE00FD0813 /* LoggingExcluded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A4084287E16EE00FD0813 /* LoggingExcluded.swift */; }; B38A4087287E720E00FD0813 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A4086287E720E00FD0813 /* Constants.swift */; }; + B38A4089287F28D000FD0813 /* AccountManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A4088287F28D000FD0813 /* AccountManagerView.swift */; }; + B38A4090287F5B4900FD0813 /* Encodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A408F287F5B4900FD0813 /* Encodable.swift */; }; + B38A4097287FC7BB00FD0813 /* AccountManagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38A4096287FC7BB00FD0813 /* AccountManagerViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -61,6 +64,9 @@ B374E5652855872F00955719 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = SOURCE_ROOT; }; B38A4084287E16EE00FD0813 /* LoggingExcluded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingExcluded.swift; sourceTree = ""; }; B38A4086287E720E00FD0813 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + B38A4088287F28D000FD0813 /* AccountManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerView.swift; sourceTree = ""; }; + B38A408F287F5B4900FD0813 /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = ""; }; + B38A4096287FC7BB00FD0813 /* AccountManagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerViewController.swift; sourceTree = ""; }; B3F08945286FD85300057DC2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; /* End PBXFileReference section */ @@ -160,6 +166,7 @@ B347C24E2877DAB600935115 /* Encryption.swift */, B38A4084287E16EE00FD0813 /* LoggingExcluded.swift */, B38A4086287E720E00FD0813 /* Constants.swift */, + B38A408F287F5B4900FD0813 /* Encodable.swift */, ); path = framework; sourceTree = ""; @@ -167,14 +174,32 @@ B374E56228545A2900955719 /* Views */ = { isa = PBXGroup; children = ( - B302E2822863A8D0005FD927 /* HubWebView.swift */, - B302E2812863A8D0005FD927 /* HubWebViewController.swift */, + B38A4095287FC0DC00FD0813 /* HubWebView */, B35E1ED02874C9C9007E989F /* HubViewController.swift */, B35E1ED22874DBEC007E989F /* SpinnerViewController.swift.bak */, + B38A4094287FC0CA00FD0813 /* AccountManager */, ); path = Views; sourceTree = ""; }; + B38A4094287FC0CA00FD0813 /* AccountManager */ = { + isa = PBXGroup; + children = ( + B38A4088287F28D000FD0813 /* AccountManagerView.swift */, + B38A4096287FC7BB00FD0813 /* AccountManagerViewController.swift */, + ); + path = AccountManager; + sourceTree = ""; + }; + B38A4095287FC0DC00FD0813 /* HubWebView */ = { + isa = PBXGroup; + children = ( + B302E2822863A8D0005FD927 /* HubWebView.swift */, + B302E2812863A8D0005FD927 /* HubWebViewController.swift */, + ); + path = HubWebView; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -242,6 +267,7 @@ B302E2C52865012F005FD927 /* XCRemoteSwiftPackageReference "ReSwift-Thunk" */, B347C2762879358200935115 /* XCRemoteSwiftPackageReference "AnyCodable" */, B347C279287CB46900935115 /* XCRemoteSwiftPackageReference "JWTDecode" */, + B38A408A287F2B5900FD0813 /* XCRemoteSwiftPackageReference "swift-collections" */, ); productRefGroup = B374E55928545A1C00955719 /* Products */; projectDirPath = ""; @@ -268,7 +294,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B38A4090287F5B4900FD0813 /* Encodable.swift in Sources */, B35E1ED12874C9C9007E989F /* HubViewController.swift in Sources */, + B38A4089287F28D000FD0813 /* AccountManagerView.swift in Sources */, B38A4085287E16EE00FD0813 /* LoggingExcluded.swift in Sources */, B302E2CD2866C75A005FD927 /* Auth.swift in Sources */, B302E2C028641F41005FD927 /* AppConfig.swift in Sources */, @@ -282,6 +310,7 @@ B302E2C928650C1D005FD927 /* APIClient.swift in Sources */, B302E2862863A8D0005FD927 /* HubWebViewController.swift in Sources */, B347C24F2877DAB600935115 /* Encryption.swift in Sources */, + B38A4097287FC7BB00FD0813 /* AccountManagerViewController.swift in Sources */, B374E5662855873100955719 /* Color.swift in Sources */, B302E2BD28641AA9005FD927 /* Context.swift in Sources */, B302E2962863A95A005FD927 /* Storage.swift in Sources */, @@ -419,7 +448,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 34CNMPW6U9; DYLIB_COMPATIBILITY_VERSION = 1; @@ -433,7 +462,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = io.rownd.framework; PRODUCT_NAME = Rownd; SKIP_INSTALL = YES; @@ -449,7 +478,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 34CNMPW6U9; DYLIB_COMPATIBILITY_VERSION = 1; @@ -463,7 +492,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = io.rownd.framework; PRODUCT_NAME = Rownd; SKIP_INSTALL = YES; @@ -529,6 +558,14 @@ minimumVersion = 2.0.0; }; }; + B38A408A287F2B5900FD0813 /* XCRemoteSwiftPackageReference "swift-collections" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-collections.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ diff --git a/RowndFramework.xcodeproj/xcuserdata/mhamann.xcuserdatad/xcschemes/xcschememanagement.plist b/RowndFramework.xcodeproj/xcuserdata/mhamann.xcuserdatad/xcschemes/xcschememanagement.plist index ecf6372..db0387a 100644 --- a/RowndFramework.xcodeproj/xcuserdata/mhamann.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/RowndFramework.xcodeproj/xcuserdata/mhamann.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,7 @@ RowndSDK.xcscheme_^#shared#^_ orderHint - 35 - - framework.xcscheme_^#shared#^_ - - orderHint - 1 + 37 diff --git a/Sources/Rownd/Models/Context/AppConfig.swift b/Sources/Rownd/Models/Context/AppConfig.swift index 2be6caf..4def9ca 100644 --- a/Sources/Rownd/Models/Context/AppConfig.swift +++ b/Sources/Rownd/Models/Context/AppConfig.swift @@ -20,7 +20,7 @@ public struct AppConfigState: Hashable { extension AppConfigState: Codable { enum CodingKeys: String, CodingKey { - case id, icon + case id, icon, schema case userVerificationFields = "user_verification_fields" } } diff --git a/Sources/Rownd/Models/RowndConfig.swift b/Sources/Rownd/Models/RowndConfig.swift index b131ba6..e9afd74 100644 --- a/Sources/Rownd/Models/RowndConfig.swift +++ b/Sources/Rownd/Models/RowndConfig.swift @@ -15,6 +15,7 @@ public struct RowndConfig: Hashable, Codable { public var baseUrl = "https://hub.rownd.io" public var appKey = "" public var forceDarkMode = false + public var postSignInRedirect: String? = nil func toJson() -> String { let encoder = JSONEncoder() diff --git a/Sources/Rownd/Rownd.swift b/Sources/Rownd/Rownd.swift index 3202dc7..fc48fc4 100644 --- a/Sources/Rownd/Rownd.swift +++ b/Sources/Rownd/Rownd.swift @@ -51,7 +51,11 @@ public class Rownd: NSObject { } public static func requestSignIn() { - let _ = inst.displayHub(.signIn) + requestSignIn(nil) + } + + public static func requestSignIn(_ signInOptions: RowndSignInOptions?) { + let _ = inst.displayHub(.signIn, jsFnOptions: signInOptions ?? RowndSignInOptions() ) } public static func signOut() { @@ -59,6 +63,10 @@ public class Rownd: NSObject { store.dispatch(SetAuthState(payload: AuthState())) } + public static func manageUser() { + inst.displayViewControllerOnTop(AccountManagerViewController()) + } + public static func getAccessToken() async -> String? { return await store.state.auth.getAccessToken() } @@ -67,14 +75,14 @@ public class Rownd: NSObject { return store } -// public func state(type: RowndStateType) -> StateObject { -// switch(type) { -// case .auth: -// return state().subscribe { $0.auth } -// case .none -// return nil -// } -// } + // public func state(type: RowndStateType) -> StateObject { + // switch(type) { + // case .auth: + // return state().subscribe { $0.auth } + // case .none + // return nil + // } + // } // MARK: Internal methods private func loadAppConfig() { @@ -86,20 +94,36 @@ public class Rownd: NSObject { } private func displayHub(_ page: HubPageSelector) -> HubViewController { - let rootViewController = UIApplication.shared.connectedScenes - .filter({$0.activationState == .foregroundActive}) - .compactMap({$0 as? UIWindowScene}) - .first?.windows - .filter({$0.isKeyWindow}).first?.rootViewController - + return displayHub(page, jsFnOptions: nil) + } + + private func displayHub(_ page: HubPageSelector, jsFnOptions: Encodable?) -> HubViewController { let hubController = HubViewController() hubController.targetPage = page - rootViewController?.present(hubController, animated: true) + displayViewControllerOnTop(hubController) + + if let jsFnOptions = jsFnOptions { + do { + hubController.hubWebController.jsFunctionArgsAsJson = try jsFnOptions.asJsonString() + } catch { + logger.error("Failed to encode JS options to pass to function: \(String(describing: error))") + } + } return hubController } + private func displayViewControllerOnTop(_ viewController: UIViewController) { + let rootViewController = UIApplication.shared.connectedScenes + .filter({$0.activationState == .foregroundActive}) + .compactMap({$0 as? UIWindowScene}) + .first?.windows + .filter({$0.isKeyWindow}).first?.rootViewController + + rootViewController?.present(viewController, animated: true) + } + } public class UserPropAccess { @@ -137,3 +161,15 @@ public enum RowndStateType { public enum UserFieldAccessType { case string, int, float, dictionary, array } + +public struct RowndSignInOptions: Encodable { + public init(postSignInRedirect: String? = Rownd.config.postSignInRedirect) { + self.postSignInRedirect = postSignInRedirect + } + + public var postSignInRedirect: String? = Rownd.config.postSignInRedirect + + enum CodingKeys: String, CodingKey { + case postSignInRedirect = "post_login_redirect" + } +} diff --git a/Sources/Rownd/Views/AccountManager.swift b/Sources/Rownd/Views/AccountManager.swift deleted file mode 100644 index cbc1f8a..0000000 --- a/Sources/Rownd/Views/AccountManager.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// AccountManager.swift -// RowndSDK -// -// Created by Matt Hamann on 7/13/22. -// - -import SwiftUI - -struct AccountManager: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -struct AccountManager_Previews: PreviewProvider { - static var previews: some View { - AccountManager() - } -} diff --git a/Sources/Rownd/Views/AccountManager/AccountManagerView.swift b/Sources/Rownd/Views/AccountManager/AccountManagerView.swift new file mode 100644 index 0000000..3d73aed --- /dev/null +++ b/Sources/Rownd/Views/AccountManager/AccountManagerView.swift @@ -0,0 +1,29 @@ +// +// AccountManager.swift +// RowndSDK +// +// Created by Matt Hamann on 7/13/22. +// + +import SwiftUI + +struct AccountManager: View { + + @StateObject var appConfig = Rownd.getInstance().state().subscribe { $0.appConfig } + @StateObject var userData = Rownd.getInstance().state().subscribe { $0.user.data } + + var body: some View { + VStack { + ForEach(appConfig.current.schema?.keys.sorted() ?? [], id: \.self) { field in + Text(appConfig.current.schema?[field]?.displayName ?? "unknown field") + } + } + } +} + + +struct AccountManager_Previews: PreviewProvider { + static var previews: some View { + AccountManager() + } +} diff --git a/Sources/Rownd/Views/AccountManager/AccountManagerViewController.swift b/Sources/Rownd/Views/AccountManager/AccountManagerViewController.swift new file mode 100644 index 0000000..2aaeeee --- /dev/null +++ b/Sources/Rownd/Views/AccountManager/AccountManagerViewController.swift @@ -0,0 +1,49 @@ +// +// AccountManagerViewController.swift +// RowndSDK +// +// Created by Matt Hamann on 7/13/22. +// + +import Foundation +import SwiftUI +import UIKit + +public class AccountManagerViewController: UIViewController { + + let accountView = UIHostingController(rootView: AccountManager()) + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let presentation = sheetPresentationController { + presentation.detents = [.medium(), .large()] + presentation.prefersGrabberVisible = true + } + } + + public override func loadView() { + view = UIView() + addChild(accountView) + view.addSubview(accountView.view) + setupConstraints() + + if Rownd.config.forceDarkMode { + self.overrideUserInterfaceStyle = .dark + } + } + + func hide() { + self.dismiss(animated: true) + } + + fileprivate func setupConstraints() { + accountView.view.translatesAutoresizingMaskIntoConstraints = false + accountView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + accountView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + accountView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + accountView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + } + +} + diff --git a/Sources/Rownd/Views/HubWebView.swift b/Sources/Rownd/Views/HubWebView/HubWebView.swift similarity index 100% rename from Sources/Rownd/Views/HubWebView.swift rename to Sources/Rownd/Views/HubWebView/HubWebView.swift diff --git a/Sources/Rownd/Views/HubWebViewController.swift b/Sources/Rownd/Views/HubWebView/HubWebViewController.swift similarity index 92% rename from Sources/Rownd/Views/HubWebViewController.swift rename to Sources/Rownd/Views/HubWebView/HubWebViewController.swift index b547d0f..8843502 100644 --- a/Sources/Rownd/Views/HubWebViewController.swift +++ b/Sources/Rownd/Views/HubWebView/HubWebViewController.swift @@ -24,6 +24,7 @@ public class HubWebViewController: UIViewController, WKUIDelegate { var webView: WKWebView! var url = URL(string: "https://api.rownd.io/mobile_app")! var hubViewController: HubViewProtocol? + var jsFunctionArgsAsJson: String = "{}" func setUrl(url: URL) { self.url = url @@ -86,15 +87,15 @@ extension HubWebViewController: WKScriptMessageHandler, WKNavigationDelegate { case .signOut: webView.evaluateJavaScript("rownd.signOut()") { (result, error) in if error != nil { - print(error) + logger.error("Failed to request sign out from Rownd: \(String(describing: error))") } } case .signIn, .unknown: - let idfv = UIDevice.current.identifierForVendor - webView.evaluateJavaScript("rownd.requestSignIn({ fingerprint: '\(idfv?.uuidString ?? "")' })") { (result, error) in + print("rownd.requestSignIn(JSON.parse(\"\(jsFunctionArgsAsJson)\"))") + webView.evaluateJavaScript("rownd.requestSignIn(\(jsFunctionArgsAsJson))") { (result, error) in if error != nil { - print(error) + logger.error("Failed to request sign in from Rownd: \(String(describing: error))") } } case .none: diff --git a/Sources/Rownd/framework/Encodable.swift b/Sources/Rownd/framework/Encodable.swift index 720b927..f543d50 100644 --- a/Sources/Rownd/framework/Encodable.swift +++ b/Sources/Rownd/framework/Encodable.swift @@ -6,3 +6,12 @@ // import Foundation + +extension Encodable { + func asJsonString() throws -> String { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + let data = try encoder.encode(self) + return String(decoding: data, as: UTF8.self) + } +}