diff --git a/.gitignore b/.gitignore index bf07cdae3..29e885d84 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ Auth0.plist fastlane/report.xml fastlane/test_output/ .env +fastlane/screenshots/ #AppCode .idea/ diff --git a/.swiftlint.yml b/.swiftlint.yml index 34b007640..a18a16ee4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -28,8 +28,8 @@ type_body_length: - 400 # error # or they can set both explicitly file_length: - warning: 500 - error: 1200 + warning: 1024 + error: 2048 # naming rules can set warnings/errors for min_length and max_length # additionally they can set excluded names type_name: diff --git a/App/Assets.xcassets/icn_phantom_back.imageset/Contents.json b/App/Assets.xcassets/icn_phantom_back.imageset/Contents.json new file mode 100644 index 000000000..3d3f97004 --- /dev/null +++ b/App/Assets.xcassets/icn_phantom_back.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icn_phantom_back.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/App/Assets.xcassets/icn_phantom_back.imageset/icn_phantom_back.pdf b/App/Assets.xcassets/icn_phantom_back.imageset/icn_phantom_back.pdf new file mode 100644 index 000000000..b66efb526 Binary files /dev/null and b/App/Assets.xcassets/icn_phantom_back.imageset/icn_phantom_back.pdf differ diff --git a/App/Assets.xcassets/icn_phantom_exit.imageset/Contents.json b/App/Assets.xcassets/icn_phantom_exit.imageset/Contents.json new file mode 100644 index 000000000..c49085b2c --- /dev/null +++ b/App/Assets.xcassets/icn_phantom_exit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icn_phantom_exit.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/App/Assets.xcassets/icn_phantom_exit.imageset/icn_phantom_exit.pdf b/App/Assets.xcassets/icn_phantom_exit.imageset/icn_phantom_exit.pdf new file mode 100644 index 000000000..ef428064c Binary files /dev/null and b/App/Assets.xcassets/icn_phantom_exit.imageset/icn_phantom_exit.pdf differ diff --git a/App/ViewController.swift b/App/ViewController.swift index 6ebda384b..6f59973a3 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -54,6 +54,7 @@ class ViewController: UIViewController { CustomTextField(name: "first_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)), CustomTextField(name: "last_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)) ] + $0.enterpriseConnectionUsingActiveAuth = ["testAD"] } .withStyle { $0.oauth2["slack"] = AuthStyle( @@ -69,33 +70,35 @@ class ViewController: UIViewController { .withOptions { applyDefaultOptions(&$0) } - .withStyle { - $0.oauth2["slack"] = AuthStyle( - name: "Slack", - color: UIColor ( red: 0.4118, green: 0.8078, blue: 0.6588, alpha: 1.0 ), - withImage: LazyImage(name: "ic_slack") - ) - } }, - actionButton(withTitle: "LOGIN WITH CUSTOM STYLE") { + actionButton(withTitle: "LOGIN WITH CDN CUSTOM STYLE") { return Lock .classic() .withOptions { applyDefaultOptions(&$0) + $0.customSignupFields = [ + CustomTextField(name: "first_name", placeholder: "First Name"), + CustomTextField(name: "last_name", placeholder: "Last Name") + ] + $0.enterpriseConnectionUsingActiveAuth = ["testAD"] } .withStyle { - $0.title = "Phantom Inc." - $0.headerBlur = .extraLight - $0.logo = LazyImage(name: "icn_phantom") - $0.primaryColor = UIColor ( red: 0.6784, green: 0.5412, blue: 0.7333, alpha: 1.0 ) + applyPhantomStyle(&$0) + } + }, + actionButton(withTitle: "LOGIN WITH CDN PASSWORDLESS CUSTOM STYLE") { + return Lock + .passwordless() + .withOptions { + applyDefaultOptions(&$0) + } + .withStyle { + applyPhantomStyle(&$0) } - .withConnections { connections in - connections.database(name: "Username-Password-Authentication", requiresUsername: true) - } }, actionButton(withTitle: "LOGIN WITH DB") { return Lock - .classic() + .passwordless() .withOptions { applyDefaultOptions(&$0) $0.customSignupFields = [ @@ -129,7 +132,7 @@ class ViewController: UIViewController { .withConnections { connections in connections.social(name: "facebook", style: .Facebook) connections.social(name: "google-oauth2", style: .Google) - connections.database(name: "Username-Password-Authentication", requiresUsername: true) + connections.database(name: "Username-Password-Authentication", requiresUsername: false) } }, actionButton(withTitle: "LOGIN WITH SOCIAL") { @@ -148,8 +151,7 @@ class ViewController: UIViewController { connections.social(name: "dropbox", style: .Dropbox) connections.social(name: "bitbucket", style: .Bitbucket) } - }, - + } ] let stack = UIStackView(arrangedSubviews: actions.map { wrap($0) }) @@ -209,6 +211,52 @@ func applyDefaultOptions(_ options: inout OptionBuildable) { options.logHttpRequest = true } +func applyPhantomStyle(_ style: inout Style) { + let lightPurple = UIColor(red: 0.949, green: 0.910, blue: 0.973, alpha: 1.00) + let mediumPurple = UIColor(red: 0.659, green: 0.553, blue: 0.722, alpha: 1.00) + let darkPurple = UIColor(red: 0.192, green: 0.200, blue: 0.302, alpha: 1.00) + + // Lock + style.backgroundColor = lightPurple + style.textColor = darkPurple + + // Primary Button + style.buttonTintColor = UIColor.black + + // Header + style.title = "Phantom Inc." + style.headerBlur = .extraLight + style.logo = LazyImage(name: "icn_phantom") + style.headerCloseIcon = LazyImage(name: "icn_phantom_exit") + style.headerBackIcon = LazyImage(name: "icn_phantom_back") + style.primaryColor = UIColor ( red: 0.6784, green: 0.5412, blue: 0.7333, alpha: 1.0 ) + + // Social + style.seperatorTextColor = darkPurple + + // Input Field + style.inputTextColor = darkPurple + style.inputPlaceholderTextColor = mediumPurple + style.inputBorderColor = darkPurple + style.inputBorderColorError = UIColor(red: 0.545, green: 0.016, blue: 0.000, alpha: 1.00) + style.inputIconBackgroundColor = darkPurple + style.inputBackgroundColor = UIColor(red: 0.980, green: 0.980, blue: 0.980, alpha: 1.00) + style.inputIconColor = UIColor.white + + // Secondary Button + style.secondaryButtonColor = darkPurple + + // Database Tabs + style.tabTintColor = darkPurple + style.tabTextColor = mediumPurple + + // Status Bar + style.statusBarHidden = true + + // Table View + style.searchBarStyle = .minimal +} + class CleanroomLockLogger: LoggerOutput { func message(_ message: String, level: LoggerLevel, filename: String, line: Int) { diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index 2b4f5df14..cdfe34ef4 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 5B0CF2D61DE9AE0F00F82BF4 /* InputValidationErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CF2D41DE9ADF900F82BF4 /* InputValidationErrorSpec.swift */; }; 5B0CF2D91DE9B1DB00F82BF4 /* DatabaseAuthenticatableErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CF2D71DE9B14000F82BF4 /* DatabaseAuthenticatableErrorSpec.swift */; }; 5B0CF2DE1DE9B4AF00F82BF4 /* DatabaseUserCreatorErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CF2DC1DE9B47C00F82BF4 /* DatabaseUserCreatorErrorSpec.swift */; }; + 5B124ED01EA8D15E0050E567 /* LockSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B124ECF1EA8D15E0050E567 /* LockSnapshot.swift */; }; + 5B124ED81EA8D17A0050E567 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B124ED71EA8D17A0050E567 /* SnapshotHelper.swift */; }; 5B1FD96D1E4E2B6B0055C1AC /* PasswordlessActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD96C1E4E2B6B0055C1AC /* PasswordlessActivity.swift */; }; 5B1FD9701E4E2F170055C1AC /* PasswordlessActivitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD96E1E4E2E670055C1AC /* PasswordlessActivitySpec.swift */; }; 5B1FD9721E546F500055C1AC /* PasswordlessInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9711E546F500055C1AC /* PasswordlessInteractorSpec.swift */; }; @@ -178,6 +180,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 5B124ED21EA8D15E0050E567 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5FEAE1BD1D1A5154005C0028 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5FEADCF31D1A7EBC0032D810; + remoteInfo = LockApp; + }; 5FEADD081D1A7F010032D810 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5FEAE1BD1D1A5154005C0028 /* Project object */; @@ -227,6 +236,10 @@ 5B0CF2D41DE9ADF900F82BF4 /* InputValidationErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputValidationErrorSpec.swift; sourceTree = ""; }; 5B0CF2D71DE9B14000F82BF4 /* DatabaseAuthenticatableErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseAuthenticatableErrorSpec.swift; sourceTree = ""; }; 5B0CF2DC1DE9B47C00F82BF4 /* DatabaseUserCreatorErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUserCreatorErrorSpec.swift; sourceTree = ""; }; + 5B124ECD1EA8D15D0050E567 /* LockSnapshot.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LockSnapshot.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B124ECF1EA8D15E0050E567 /* LockSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockSnapshot.swift; sourceTree = ""; }; + 5B124ED11EA8D15E0050E567 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5B124ED71EA8D17A0050E567 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = ""; }; 5B1FD96C1E4E2B6B0055C1AC /* PasswordlessActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordlessActivity.swift; sourceTree = ""; }; 5B1FD96E1E4E2E670055C1AC /* PasswordlessActivitySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordlessActivitySpec.swift; sourceTree = ""; }; 5B1FD9711E546F500055C1AC /* PasswordlessInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordlessInteractorSpec.swift; sourceTree = ""; }; @@ -392,6 +405,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5B124ECA1EA8D15D0050E567 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5FEADCF11D1A7EBC0032D810 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -443,6 +463,16 @@ name = Validators; sourceTree = ""; }; + 5B124ECE1EA8D15E0050E567 /* LockSnapshot */ = { + isa = PBXGroup; + children = ( + 5B124ED71EA8D17A0050E567 /* SnapshotHelper.swift */, + 5B124ECF1EA8D15E0050E567 /* LockSnapshot.swift */, + 5B124ED11EA8D15E0050E567 /* Info.plist */, + ); + path = LockSnapshot; + sourceTree = ""; + }; 5BE978191E82CFA00090EC07 /* Controllers */ = { isa = PBXGroup; children = ( @@ -701,6 +731,7 @@ 5FEAE1C81D1A5154005C0028 /* Lock */, 5FEAE1D41D1A5154005C0028 /* LockTests */, 5FEADCF51D1A7EBC0032D810 /* App */, + 5B124ECE1EA8D15E0050E567 /* LockSnapshot */, 5FEAE2111D1A5716005C0028 /* Frameworks */, 5FEAE1C71D1A5154005C0028 /* Products */, ); @@ -712,6 +743,7 @@ 5FEAE1C61D1A5154005C0028 /* Lock.framework */, 5FEAE1D01D1A5154005C0028 /* LockTests.iOS.xctest */, 5FEADCF41D1A7EBC0032D810 /* LockApp.app */, + 5B124ECD1EA8D15D0050E567 /* LockSnapshot.xctest */, ); name = Products; sourceTree = ""; @@ -841,6 +873,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 5B124ECC1EA8D15D0050E567 /* LockSnapshot */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5B124ED61EA8D15E0050E567 /* Build configuration list for PBXNativeTarget "LockSnapshot" */; + buildPhases = ( + 5B124EC91EA8D15D0050E567 /* Sources */, + 5B124ECA1EA8D15D0050E567 /* Frameworks */, + 5B124ECB1EA8D15D0050E567 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5B124ED31EA8D15E0050E567 /* PBXTargetDependency */, + ); + name = LockSnapshot; + productName = LockSnapshot; + productReference = 5B124ECD1EA8D15D0050E567 /* LockSnapshot.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 5FEADCF31D1A7EBC0032D810 /* LockApp */ = { isa = PBXNativeTarget; buildConfigurationList = 5FEADD031D1A7EBC0032D810 /* Build configuration list for PBXNativeTarget "LockApp" */; @@ -906,10 +956,15 @@ 5FEAE1BD1D1A5154005C0028 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; + LastSwiftUpdateCheck = 0830; LastUpgradeCheck = 0800; ORGANIZATIONNAME = Auth0; TargetAttributes = { + 5B124ECC1EA8D15D0050E567 = { + CreatedOnToolsVersion = 8.3; + ProvisioningStyle = Manual; + TestTargetID = 5FEADCF31D1A7EBC0032D810; + }; 5FEADCF31D1A7EBC0032D810 = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = 86WQXF56BC; @@ -949,11 +1004,19 @@ 5FEAE1C51D1A5154005C0028 /* Lock.iOS */, 5FEAE1CF1D1A5154005C0028 /* LockTests.iOS */, 5FEADCF31D1A7EBC0032D810 /* LockApp */, + 5B124ECC1EA8D15D0050E567 /* LockSnapshot */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 5B124ECB1EA8D15D0050E567 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5FEADCF21D1A7EBC0032D810 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1031,6 +1094,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 5B124EC91EA8D15D0050E567 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B124ED01EA8D15E0050E567 /* LockSnapshot.swift in Sources */, + 5B124ED81EA8D17A0050E567 /* SnapshotHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5FEADCF01D1A7EBC0032D810 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1204,6 +1276,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 5B124ED31EA8D15E0050E567 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5FEADCF31D1A7EBC0032D810 /* LockApp */; + targetProxy = 5B124ED21EA8D15E0050E567 /* PBXContainerItemProxy */; + }; 5FEADD091D1A7F010032D810 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5FEAE1C51D1A5154005C0028 /* Lock.iOS */; @@ -1246,6 +1323,43 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 5B124ED41EA8D15E0050E567 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = LockSnapshot/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.LockSnapshot; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = LockApp; + }; + name = Debug; + }; + 5B124ED51EA8D15E0050E567 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = LockSnapshot/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.LockSnapshot; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = LockApp; + }; + name = Release; + }; 5FEADD041D1A7EBC0032D810 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1475,6 +1589,14 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 5B124ED61EA8D15E0050E567 /* Build configuration list for PBXNativeTarget "LockSnapshot" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5B124ED41EA8D15E0050E567 /* Debug */, + 5B124ED51EA8D15E0050E567 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; 5FEADD031D1A7EBC0032D810 /* Build configuration list for PBXNativeTarget "LockApp" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Lock/AuthStyle.swift b/Lock/AuthStyle.swift index 4a27ca927..7c2693aa1 100644 --- a/Lock/AuthStyle.swift +++ b/Lock/AuthStyle.swift @@ -253,7 +253,7 @@ public extension AuthStyle { withImage: LazyImage(name: "ic_auth_paypal", bundle: bundleForLock()) ) } - + /// Planning Center style for AuthButton public static var PlanningCenter: AuthStyle { return AuthStyle( diff --git a/Lock/CountryTableViewController.swift b/Lock/CountryTableViewController.swift index a5ba51b29..2c3f760d0 100644 --- a/Lock/CountryTableViewController.swift +++ b/Lock/CountryTableViewController.swift @@ -22,7 +22,7 @@ import UIKit -class CountryTableViewController: UITableViewController { +class CountryTableViewController: UITableViewController, Stylable { var dataStore: CountryCodes let cellReuseIdentifier = "CountryCodeCell" @@ -30,6 +30,9 @@ class CountryTableViewController: UITableViewController { var onDidSelect: (CountryCode) -> Void = { _ in } + private var cellTextColor: UIColor = Style.Auth0.inputTextColor + private var cellBackgroundColor: UIColor = Style.Auth0.backgroundColor + init(withData dataStore: CountryCodes, onSelect: @escaping (CountryCode) -> Void) { self.dataStore = dataStore self.search = UISearchController(searchResultsController: nil) @@ -67,6 +70,9 @@ class CountryTableViewController: UITableViewController { let cellData = dataStore.filteredData()[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) ?? UITableViewCell(style: .value1, reuseIdentifier: cellReuseIdentifier) + cell.backgroundColor = self.cellBackgroundColor + cell.textLabel?.textColor = self.cellTextColor + cell.detailTextLabel?.textColor = self.cellTextColor.withAlphaComponent(0.50) cell.textLabel?.text = String(format: "%1$@".i18n(key: "com.auth0.passwordless.sms.country.cell.label", comment: "CountryCodes TableViewCell label %@{localizedName}"), cellData.localizedName) cell.detailTextLabel?.text = String(format: "%1$@".i18n(key: "com.auth0.passwordless.sms.country.cell.detail", comment: "CountryCodes TableViewCell detail %@{phoneCode}"), @@ -80,6 +86,16 @@ class CountryTableViewController: UITableViewController { self.search.dismiss(animated: false) self.dismiss(animated: true) } + + func apply(style: Style) { + self.navigationController!.navigationBar.barTintColor = style.headerColor + self.navigationController!.navigationBar.tintColor = style.titleColor + self.search.searchBar.searchBarStyle = style.searchBarStyle + self.tableView.separatorColor = style.inputBorderColor + self.cellTextColor = style.inputTextColor + self.tableView.backgroundColor = style.backgroundColor + self.cellBackgroundColor = style.backgroundColor + } } extension CountryTableViewController: UISearchResultsUpdating { diff --git a/Lock/CustomTextField.swift b/Lock/CustomTextField.swift index d700de944..a9698d240 100644 --- a/Lock/CustomTextField.swift +++ b/Lock/CustomTextField.swift @@ -26,13 +26,13 @@ public struct CustomTextField { let name: String let placeholder: String - let icon: LazyImage + let icon: LazyImage? let keyboardType: UIKeyboardType let autocorrectionType: UITextAutocorrectionType let secure: Bool let validation: (String?) -> Error? - public init(name: String, placeholder: String, icon: LazyImage, keyboardType: UIKeyboardType = .default, autocorrectionType: UITextAutocorrectionType = .default, secure: Bool = false, validation: @escaping (String?) -> Error? = nonEmpty) { + public init(name: String, placeholder: String, icon: LazyImage? = nil, keyboardType: UIKeyboardType = .default, autocorrectionType: UITextAutocorrectionType = .default, secure: Bool = false, validation: @escaping (String?) -> Error? = nonEmpty) { self.name = name self.placeholder = placeholder self.icon = icon diff --git a/Lock/DatabaseForgotPasswordView.swift b/Lock/DatabaseForgotPasswordView.swift index 518bfb40c..96b91735f 100644 --- a/Lock/DatabaseForgotPasswordView.swift +++ b/Lock/DatabaseForgotPasswordView.swift @@ -69,6 +69,5 @@ class DatabaseForgotPasswordView: UIView, View { } func apply(style: Style) { - self.primaryButton?.apply(style: style) } } diff --git a/Lock/DatabaseModeSwitcher.swift b/Lock/DatabaseModeSwitcher.swift index bbb311e1d..f3195f7e5 100644 --- a/Lock/DatabaseModeSwitcher.swift +++ b/Lock/DatabaseModeSwitcher.swift @@ -94,18 +94,17 @@ class DatabaseModeSwitcher: UIView { segmented.setBackgroundImage(image(named: "ic_switcher_selected", compatibleWithTraitCollection: self.traitCollection), for: .highlighted, barMetrics: .default) segmented.setBackgroundImage(image(named: "ic_switcher_normal", compatibleWithTraitCollection: self.traitCollection), for: UIControlState(), barMetrics: .default) segmented.setTitleTextAttributes([ - NSForegroundColorAttributeName: UIColor ( red: 0.3608, green: 0.4, blue: 0.4353, alpha: 0.6 ), + NSForegroundColorAttributeName: Style.Auth0.tabTextColor, NSFontAttributeName: mediumSystemFont(size: 15) ], for: UIControlState()) segmented.setTitleTextAttributes([ - NSForegroundColorAttributeName: UIColor ( red: 0.3608, green: 0.4, blue: 0.4353, alpha: 1.0 ), + NSForegroundColorAttributeName: Style.Auth0.tabTextColor, NSFontAttributeName: semiBoldSystemFont(size: 15) ], for: .selected) - segmented.tintColor = UIColor ( red: 0.3608, green: 0.4, blue: 0.4353, alpha: 1.0 ) + segmented.tintColor = Style.Auth0.tabTintColor segmented.addTarget(self, action: #selector(selectedIndex), for: .valueChanged) self.segmentedControl = segmented - self.selected = .login } @@ -119,3 +118,18 @@ class DatabaseModeSwitcher: UIView { self.onSelectionChange(self) } } + +extension DatabaseModeSwitcher: Stylable { + + func apply(style: Style) { + self.segmentedControl?.tintColor = style.tabTintColor + self.segmentedControl?.setTitleTextAttributes([ + NSForegroundColorAttributeName: style.tabTextColor, + NSFontAttributeName: mediumSystemFont(size: 15) + ], for: UIControlState()) + self.segmentedControl?.setTitleTextAttributes([ + NSForegroundColorAttributeName: style.tabTextColor, + NSFontAttributeName: semiBoldSystemFont(size: 15) + ], for: .selected) + } +} diff --git a/Lock/DatabaseOnlyView.swift b/Lock/DatabaseOnlyView.swift index 7418fde5a..16e3b3d47 100644 --- a/Lock/DatabaseOnlyView.swift +++ b/Lock/DatabaseOnlyView.swift @@ -33,6 +33,7 @@ class DatabaseOnlyView: UIView, DatabaseView { weak var secondaryStrut: UIView? weak var ssoBar: InfoBarView? weak var spacer: UIView? + private var style: Style? // FIXME: Remove this from the view since it should not even know it exists var navigator: Navigable? @@ -107,7 +108,6 @@ class DatabaseOnlyView: UIView, DatabaseView { layoutInStack(form, authCollectionView: authCollectionView) self.layoutSecondaryButton(self.allowedModes.contains(.ResetPassword)) self.form = form - } func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView? = nil, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator? = nil) { @@ -226,7 +226,6 @@ class DatabaseOnlyView: UIView, DatabaseView { let label = UILabel() label.text = "or".i18n(key: "com.auth0.lock.database.separator", comment: "Social separator") label.font = mediumSystemFont(size: 13.75) - label.textColor = UIColor ( red: 0.0, green: 0.0, blue: 0.0, alpha: 0.54 ) label.textAlignment = .center self.container?.insertArrangedSubview(social, at: socialIndex) self.container?.insertArrangedSubview(label, at: separatorIndex) @@ -236,11 +235,14 @@ class DatabaseOnlyView: UIView, DatabaseView { } else { self.container?.insertArrangedSubview(view, at: formOnlyIndex) } + if let style = self.style { + self.separator?.textColor = style.seperatorTextColor + self.container?.styleSubViews(style: style) + } } - // MARK: - Styling - func apply(style: Style) { - primaryButton?.apply(style: style) + self.style = style + self.separator?.textColor = style.seperatorTextColor } } diff --git a/Lock/DatabaseView.swift b/Lock/DatabaseView.swift index fb0baf1b8..fe70526f5 100644 --- a/Lock/DatabaseView.swift +++ b/Lock/DatabaseView.swift @@ -22,7 +22,7 @@ import UIKit -protocol DatabaseView: View, NSObjectProtocol { +protocol DatabaseView: class, View { weak var form: Form? { get } weak var secondaryButton: SecondaryButton? { get } weak var primaryButton: PrimaryButton? { get } diff --git a/Lock/EnterpriseActiveAuthView.swift b/Lock/EnterpriseActiveAuthView.swift index 390c7707a..183f4bc2b 100644 --- a/Lock/EnterpriseActiveAuthView.swift +++ b/Lock/EnterpriseActiveAuthView.swift @@ -28,6 +28,7 @@ class EnterpriseActiveAuthView: UIView, View { weak var primaryButton: PrimaryButton? private weak var container: UIStackView? + private weak var titleView: UILabel? init(identifier: String?, identifierAttribute: UserAttribute, domain: String? = nil) { let primaryButton = PrimaryButton() @@ -37,6 +38,7 @@ class EnterpriseActiveAuthView: UIView, View { self.primaryButton = primaryButton self.form = credentialView + self.titleView = titleView super.init(frame: CGRect.zero) @@ -96,7 +98,7 @@ class EnterpriseActiveAuthView: UIView, View { } func apply(style: Style) { - self.primaryButton?.apply(style: style) + self.titleView?.textColor = style.textColor } } diff --git a/Lock/EnterpriseDomainView.swift b/Lock/EnterpriseDomainView.swift index 738a08fec..455c5d8f6 100644 --- a/Lock/EnterpriseDomainView.swift +++ b/Lock/EnterpriseDomainView.swift @@ -31,6 +31,8 @@ class EnterpriseDomainView: UIView, View { weak var container: UIStackView? weak var authButton: AuthButton? + private weak var seperatorLabel: UILabel? + init(email: String?, authCollectionView: AuthCollectionView? = nil) { let primaryButton = PrimaryButton() let domainView = SingleInputView() @@ -61,8 +63,8 @@ class EnterpriseDomainView: UIView, View { let label = UILabel() label.text = "or".i18n(key: "com.auth0.lock.database.separator", comment: "Social separator") label.font = mediumSystemFont(size: 13.75) - label.textColor = UIColor ( red: 0.0, green: 0.0, blue: 0.0, alpha: 0.54 ) label.textAlignment = .center + self.seperatorLabel = label container.addArrangedSubview(label) } container.addArrangedSubview(domainView) @@ -96,7 +98,7 @@ class EnterpriseDomainView: UIView, View { } func apply(style: Style) { - self.primaryButton?.apply(style: style) + self.seperatorLabel?.textColor = style.seperatorTextColor } } diff --git a/Lock/Extensions.swift b/Lock/Extensions.swift index 9000400e7..636079ca3 100644 --- a/Lock/Extensions.swift +++ b/Lock/Extensions.swift @@ -32,5 +32,16 @@ extension Optional { return "" } } +} + +extension UIView { + func styleSubViews(style: Style) { + self.subviews.forEach { view in + if let view = view as? Stylable { + view.apply(style: style) + } + view.styleSubViews(style: style) + } + } } diff --git a/Lock/HeaderView.swift b/Lock/HeaderView.swift index 3c8b5b8e2..e2058ae34 100644 --- a/Lock/HeaderView.swift +++ b/Lock/HeaderView.swift @@ -151,24 +151,24 @@ public class HeaderView: UIView { logoView.translatesAutoresizingMaskIntoConstraints = false constraintEqual(anchor: closeButton.centerYAnchor, toAnchor: self.topAnchor, constant: 45) - constraintEqual(anchor: closeButton.rightAnchor, toAnchor: self.rightAnchor) - closeButton.widthAnchor.constraint(equalToConstant: 50).isActive = true - closeButton.heightAnchor.constraint(equalToConstant: 50).isActive = true + constraintEqual(anchor: closeButton.rightAnchor, toAnchor: self.rightAnchor, constant: -10) + closeButton.widthAnchor.constraint(equalToConstant: 25).isActive = true + closeButton.heightAnchor.constraint(equalToConstant: 25).isActive = true closeButton.translatesAutoresizingMaskIntoConstraints = false constraintEqual(anchor: backButton.centerYAnchor, toAnchor: self.topAnchor, constant: 45) - constraintEqual(anchor: backButton.leftAnchor, toAnchor: self.leftAnchor) - backButton.widthAnchor.constraint(equalToConstant: 50).isActive = true - backButton.heightAnchor.constraint(equalToConstant: 50).isActive = true + constraintEqual(anchor: backButton.leftAnchor, toAnchor: self.leftAnchor, constant: 10) + backButton.widthAnchor.constraint(equalToConstant: 25).isActive = true + backButton.heightAnchor.constraint(equalToConstant: 25).isActive = true backButton.translatesAutoresizingMaskIntoConstraints = false self.applyBackground() self.apply(style: Style.Auth0) titleView.font = regularSystemFont(size: 20) logoView.image = image(named: "ic_auth0", compatibleWithTraitCollection: self.traitCollection) - closeButton.setImage(image(named: "ic_close", compatibleWithTraitCollection: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: UIControlState()) + closeButton.setBackgroundImage(image(named: "ic_close", compatibleWithTraitCollection: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: UIControlState()) closeButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) - backButton.setImage(image(named: "ic_back", compatibleWithTraitCollection: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: UIControlState()) + backButton.setBackgroundImage(image(named: "ic_back", compatibleWithTraitCollection: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: UIControlState()) backButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) self.titleView = titleView @@ -249,5 +249,7 @@ extension HeaderView: Stylable { self.titleColor = style.titleColor self.logo = style.logo.image(compatibleWithTraits: self.traitCollection) self.maskImage = style.headerMask + self.backButton?.setBackgroundImage(style.headerBackIcon.image(compatibleWithTraits: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: .normal) + self.closeButton?.setBackgroundImage(style.headerCloseIcon.image(compatibleWithTraits: self.traitCollection)?.withRenderingMode(.alwaysOriginal), for: .normal) } } diff --git a/Lock/InputField.swift b/Lock/InputField.swift index c27adc725..a2055d79d 100644 --- a/Lock/InputField.swift +++ b/Lock/InputField.swift @@ -22,18 +22,20 @@ import UIKit -class InputField: UIView, UITextFieldDelegate { +class InputField: UIView, UITextFieldDelegate, Stylable { weak var containerView: UIView? weak var textField: UITextField? weak var iconView: UIImageView? + weak var iconContainer: UIView? weak var errorLabel: UILabel? - weak var nextField: InputField? private weak var errorLabelTopPadding: NSLayoutConstraint? - + private weak var textFieldLeftAnchor: NSLayoutConstraint? private(set) var state: State = .invalid(nil) + private weak var borderColor: UIColor? + private weak var borderColorError: UIColor? private lazy var debounceShowError: () -> Void = debounce(0.8, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated), action: { [weak self] in self?.needsToUpdateState() }) @@ -53,7 +55,12 @@ class InputField: UIView, UITextFieldDelegate { self.textField?.autocorrectionType = .no self.textField?.autocapitalizationType = .none self.textField?.keyboardType = type.keyboardType - self.iconView?.image = type.icon.image(compatibleWithTraits: self.traitCollection) + if let icon = type.icon { + self.iconView?.image = icon.image(compatibleWithTraits: self.traitCollection) + } else if let textField = self.textField, let container = self.containerView { + self.iconContainer?.removeFromSuperview() + textFieldLeftAnchor = constraintEqual(anchor: textField.leftAnchor, toAnchor: container.leftAnchor, constant: 16) + } } } @@ -110,8 +117,13 @@ class InputField: UIView, UITextFieldDelegate { func needsToUpdateState() { Queue.main.async { self.errorLabel?.text = self.state.text - self.containerView?.layer.borderColor = self.state.color.cgColor self.errorLabelTopPadding?.constant = self.state.padding + switch self.state { + case .valid: + self.containerView?.layer.borderColor = self.borderColor?.cgColor + case .invalid: + self.containerView?.layer.borderColor = self.borderColorError?.cgColor + } } } @@ -149,7 +161,7 @@ class InputField: UIView, UITextFieldDelegate { constraintEqual(anchor: iconContainer.heightAnchor, toAnchor: iconContainer.widthAnchor) iconContainer.translatesAutoresizingMaskIntoConstraints = false - constraintEqual(anchor: textField.leftAnchor, toAnchor: iconContainer.rightAnchor, constant: 16) + self.textFieldLeftAnchor = constraintEqual(anchor: textField.leftAnchor, toAnchor: iconContainer.rightAnchor, constant: 16) constraintEqual(anchor: textField.topAnchor, toAnchor: container.topAnchor) constraintEqual(anchor: textField.rightAnchor, toAnchor: container.rightAnchor, constant: -16) constraintEqual(anchor: textField.bottomAnchor, toAnchor: container.bottomAnchor) @@ -160,17 +172,17 @@ class InputField: UIView, UITextFieldDelegate { constraintEqual(anchor: iconView.centerYAnchor, toAnchor: iconContainer.centerYAnchor) iconView.translatesAutoresizingMaskIntoConstraints = false - iconContainer.backgroundColor = UIColor ( red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0 ) - iconView.tintColor = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) + iconContainer.backgroundColor = Style.Auth0.inputIconBackgroundColor + iconView.tintColor = Style.Auth0.inputIconColor textField.addTarget(self, action: #selector(textChanged), for: .editingChanged) textField.delegate = self textField.font = UIFont.systemFont(ofSize: 17) - errorLabel.textColor = .red errorLabel.text = nil errorLabel.numberOfLines = 0 self.textField = textField self.iconView = iconView + self.iconContainer = iconContainer self.containerView = container self.errorLabel = errorLabel @@ -180,7 +192,7 @@ class InputField: UIView, UITextFieldDelegate { self.containerView?.layer.borderWidth = 1 self.type = .email self.errorLabel?.text = State.valid.text - self.containerView?.layer.borderColor = State.valid.color.cgColor + self.containerView?.layer.borderColor = Style.Auth0.inputBorderColor.cgColor } override var intrinsicContentSize: CGSize { @@ -202,15 +214,6 @@ class InputField: UIView, UITextFieldDelegate { } } - var color: UIColor { - switch self { - case .valid: - return UIColor ( red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0 ) - case .invalid: - return UIColor.red - } - } - var padding: CGFloat { switch self { case .invalid where self.text != nil: @@ -255,7 +258,7 @@ class InputField: UIView, UITextFieldDelegate { case password case phone case oneTimePassword - case custom(name: String, placeholder: String, icon: LazyImage, keyboardType: UIKeyboardType, autocorrectionType: UITextAutocorrectionType, secure: Bool) + case custom(name: String, placeholder: String, icon: LazyImage?, keyboardType: UIKeyboardType, autocorrectionType: UITextAutocorrectionType, secure: Bool) var placeholder: String? { switch self { @@ -287,7 +290,7 @@ class InputField: UIView, UITextFieldDelegate { return false } - var icon: LazyImage { + var icon: LazyImage? { switch self { case .email: return lazyImage(named: "ic_mail") @@ -334,4 +337,19 @@ class InputField: UIView, UITextFieldDelegate { } } } + + // MARK: - Styable + + func apply(style: Style) { + self.borderColor = style.inputBorderColor + self.borderColorError = style.inputBorderColorError + self.textField?.textColor = style.inputTextColor + self.textField?.attributedPlaceholder = NSAttributedString(string: self.textField?.placeholder ?? "", + attributes: [NSForegroundColorAttributeName: style.inputPlaceholderTextColor]) + self.containerView?.backgroundColor = style.inputBackgroundColor + self.containerView?.layer.borderColor = style.inputBorderColor.cgColor + self.errorLabel?.textColor = style.inputBorderColorError + self.iconContainer?.backgroundColor = style.inputIconBackgroundColor + self.iconView?.tintColor = style.inputIconColor + } } diff --git a/Lock/InternationalPhoneInputView.swift b/Lock/InternationalPhoneInputView.swift index d6bc943bd..58662b64c 100644 --- a/Lock/InternationalPhoneInputView.swift +++ b/Lock/InternationalPhoneInputView.swift @@ -22,7 +22,7 @@ import UIKit -class InternationalPhoneInputView: UIView, Form { +class InternationalPhoneInputView: UIView, Form, Stylable { var container: UIView var countryLabel: UILabel @@ -31,6 +31,11 @@ class InternationalPhoneInputView: UIView, Form { var stackView: UIStackView var countryStore: CountryCodes var onPresent: (UIViewController) -> Void = { _ in } + var style: Style? + + private var iconContainer: UIView? + private var iconView: UIImageView? + private var actionIconView: UIImageView? init(withCountryData data: CountryCodes) { self.container = UIView() @@ -118,6 +123,10 @@ class InternationalPhoneInputView: UIView, Form { actionIconContainer.addSubview(actionIconView) self.addSubview(stackView) + self.iconView = iconView + self.actionIconView = actionIconView + self.iconContainer = iconContainer + stackView.addArrangedSubview(container) stackView.addArrangedSubview(inputField) @@ -173,18 +182,18 @@ class InternationalPhoneInputView: UIView, Form { iconView.image = lazyImage(named: "ic_globe").image() actionIconView.image = lazyImage(named: "ic_chevron_right").image() - countryLabel.textColor = UIColor(red:0.73, green:0.73, blue:0.73, alpha:1.0) - codeLabel.textColor = countryLabel.textColor + countryLabel.textColor = Style.Auth0.inputPlaceholderTextColor + codeLabel.textColor = Style.Auth0.inputPlaceholderTextColor codeLabel.textAlignment = .right - iconContainer.backgroundColor = UIColor ( red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0 ) - iconView.tintColor = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) - actionIconView.tintColor = UIColor.black + iconContainer.backgroundColor = Style.Auth0.inputIconBackgroundColor + iconView.tintColor = Style.Auth0.inputIconColor + actionIconView.tintColor = Style.Auth0.inputIconBackgroundColor - container.backgroundColor = UIColor(red:0.98, green:0.98, blue:0.98, alpha:1.0) + container.backgroundColor = Style.Auth0.inputBackgroundColor container.layer.cornerRadius = 3.67 container.layer.masksToBounds = true container.layer.borderWidth = 1 - container.layer.borderColor = UIColor ( red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0 ).cgColor + container.layer.borderColor = Style.Auth0.inputBorderColor.cgColor } func launchCountryTable(_ sender: UITapGestureRecognizer) { @@ -192,6 +201,19 @@ class InternationalPhoneInputView: UIView, Form { self.updateCountry($0) } let navigationController = UINavigationController(rootViewController: countryTableView) + navigationController.modalPresentationStyle = .overFullScreen self.onPresent(navigationController) + if let style = self.style { countryTableView.apply(style: style) } + } + + func apply(style: Style) { + self.style = style + self.countryLabel.textColor = style.inputPlaceholderTextColor + self.codeLabel.textColor = style.inputPlaceholderTextColor + self.iconContainer?.backgroundColor = style.inputIconBackgroundColor + self.iconView?.tintColor = style.inputIconColor + self.actionIconView?.tintColor = style.inputIconBackgroundColor + self.container.backgroundColor = style.inputBackgroundColor + self.container.layer.borderColor = style.inputBorderColor.cgColor } } diff --git a/Lock/LoadingView.swift b/Lock/LoadingView.swift index 7256195c7..331cdb631 100644 --- a/Lock/LoadingView.swift +++ b/Lock/LoadingView.swift @@ -45,7 +45,7 @@ class LoadingView: UIView, View { init() { super.init(frame: CGRect.zero) - self.backgroundColor = .white + self.backgroundColor = Style.Auth0.backgroundColor let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) apply(style: Style.Auth0) @@ -63,6 +63,7 @@ class LoadingView: UIView, View { } func apply(style: Style) { + self.backgroundColor = style.backgroundColor self.indicator?.color = style.disabledTextColor } } diff --git a/Lock/LockViewController.swift b/Lock/LockViewController.swift index b1e0112f4..afbe67605 100644 --- a/Lock/LockViewController.swift +++ b/Lock/LockViewController.swift @@ -48,12 +48,29 @@ public class LockViewController: UIViewController { fatalError("Storyboard currently not supported") } + public override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return self.lock.style.statusBarUpdateAnimation + } + + public override var prefersStatusBarHidden: Bool { + return self.lock.style.statusBarHidden + } + + public override var preferredStatusBarStyle: UIStatusBarStyle { + return self.lock.style.statusBarStyle + } + public override func loadView() { let root = UIView() let style = self.lock.style root.backgroundColor = style.backgroundColor self.view = root + if let backgroundImage = style.backgroundImage?.image(compatibleWithTraits: self.traitCollection) { + let bgImageView = UIImageView(image: backgroundImage) + self.view.addSubview(bgImageView) + } + let scrollView = UIScrollView() scrollView.bounces = false scrollView.keyboardDismissMode = .interactive @@ -97,7 +114,7 @@ public class LockViewController: UIViewController { guard var presenter = presentable else { return } self.current?.remove() let view = presenter.view - view.apply(style: self.lock.style) + view.applyAll(withStyle: self.lock.style) self.anchorConstraint = view.layout(inView: self.scrollView, below: self.headerView) presenter.messagePresenter = self.messagePresenter self.current = view @@ -131,7 +148,7 @@ public class LockViewController: UIViewController { let value = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue, let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, let curveValue = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber - else { return } + else { return } let frame = value.cgRectValue let insets = UIEdgeInsets(top: 0, left: 0, bottom: frame.height, right: 0) @@ -144,7 +161,7 @@ public class LockViewController: UIViewController { options: options, animations: { self.anchorConstraint?.isActive = false - }, + }, completion: nil) } @@ -163,7 +180,7 @@ public class LockViewController: UIViewController { options: options, animations: { self.traitCollectionDidChange(nil) - }, + }, completion: nil) } diff --git a/Lock/MultifactorCodeView.swift b/Lock/MultifactorCodeView.swift index a20d2bf73..ea63873d3 100644 --- a/Lock/MultifactorCodeView.swift +++ b/Lock/MultifactorCodeView.swift @@ -68,6 +68,5 @@ class MultifactorCodeView: UIView, View { } func apply(style: Style) { - self.primaryButton?.apply(style: style) } } diff --git a/Lock/PasswordlessView.swift b/Lock/PasswordlessView.swift index 0bc98823b..acc726d2f 100644 --- a/Lock/PasswordlessView.swift +++ b/Lock/PasswordlessView.swift @@ -31,6 +31,7 @@ class PasswordlessView: UIView, View { private weak var container: UIStackView? private weak var centerGuide: UILayoutGuide? + private weak var messageLabel: UILabel? init() { let container = UIStackView() @@ -96,6 +97,7 @@ class PasswordlessView: UIView, View { self.form = formView self.countrySelector = formView + self.messageLabel = messageView self.container?.addArrangedSubview(strutView(withHeight: 25)) if let authView = authCollectionView { @@ -164,6 +166,7 @@ class PasswordlessView: UIView, View { self.secondaryButton = secondaryButton self.primaryButton?.isHidden = true + self.messageLabel = messageLabel self.container?.addArrangedSubview(strutView(withHeight: 75)) self.container?.addArrangedSubview(imageView) @@ -185,7 +188,7 @@ class PasswordlessView: UIView, View { messageLabel.numberOfLines = 2 messageLabel.textAlignment = .center messageLabel.font = .systemFont(ofSize: 16, weight: UIFontWeightSemibold) - messageLabel.textColor = .black + messageLabel.textColor = Style.Auth0.textColor messageLabel.text = String(format: "We sent you a link to sign in to %1$@".i18n(key: "com.auth0.passwordless.link.sent", comment: "Passwordless link sent to %@{identifier}"), displayIdentifier) secondaryButton.title = "Did not receive the link?".i18n(key: "com.auth0.passwordless.link.reminder", comment: "Passwordless link reminder action") @@ -196,6 +199,6 @@ class PasswordlessView: UIView, View { } func apply(style: Style) { - self.primaryButton?.apply(style: style) + self.messageLabel?.textColor = style.textColor } } diff --git a/Lock/PrimaryButton.swift b/Lock/PrimaryButton.swift index bcc8ccc6f..a452d4dd3 100644 --- a/Lock/PrimaryButton.swift +++ b/Lock/PrimaryButton.swift @@ -22,11 +22,13 @@ import UIKit -class PrimaryButton: UIView { +class PrimaryButton: UIView, Stylable { weak var button: UIButton? weak var indicator: UIActivityIndicatorView? + private weak var textColor: UIColor? + var hideTitle: Bool = false { didSet { guard let button = self.button else { return } @@ -118,14 +120,14 @@ class PrimaryButton: UIView { attributedText.append(NSAttributedString( string: "\(title) ", attributes: [ - NSForegroundColorAttributeName: UIColor.white, + NSForegroundColorAttributeName: self.textColor ?? Style.Auth0.buttonTintColor, NSFontAttributeName: font ] )) attributedText.append(NSAttributedString(attachment: attachment)) button.setAttributedTitle(attributedText, for: .normal) button.setAttributedTitle(NSAttributedString(), for: .disabled) -} + } override var intrinsicContentSize: CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 95) @@ -134,14 +136,12 @@ class PrimaryButton: UIView { func pressed(_ sender: Any) { self.onPress(self) } -} - -extension PrimaryButton: Stylable { func apply(style: Style) { self.button?.setBackgroundImage(image(withColor: style.primaryColor), for: UIControlState()) self.button?.setBackgroundImage(image(withColor: style.primaryColor.a0_darker(0.20)), for: .highlighted) self.button?.setBackgroundImage(image(withColor: style.disabledColor), for: .disabled) + self.textColor = style.buttonTintColor self.button?.tintColor = style.buttonTintColor self.indicator?.color = style.disabledTextColor self.hideTitle = style.hideButtonTitle diff --git a/Lock/SecondaryButton.swift b/Lock/SecondaryButton.swift index d5016c1ef..6dff212f7 100644 --- a/Lock/SecondaryButton.swift +++ b/Lock/SecondaryButton.swift @@ -71,7 +71,7 @@ class SecondaryButton: UIView { constraintEqual(anchor: button.centerYAnchor, toAnchor: self.centerYAnchor) button.translatesAutoresizingMaskIntoConstraints = false - button.tintColor = .black + button.tintColor = Style.Auth0.secondaryButtonColor button.titleLabel?.font = regularSystemFont(size: 15) button.titleLabel?.lineBreakMode = .byWordWrapping button.titleLabel?.textAlignment = .center @@ -88,3 +88,10 @@ class SecondaryButton: UIView { self.onPress(self) } } + +extension SecondaryButton: Stylable { + + func apply(style: Style) { + self.button?.tintColor = style.secondaryButtonColor + } +} diff --git a/Lock/SingleInputView.swift b/Lock/SingleInputView.swift index 38f0b7912..d8b163804 100644 --- a/Lock/SingleInputView.swift +++ b/Lock/SingleInputView.swift @@ -22,7 +22,7 @@ import UIKit -class SingleInputView: UIView, Form { +class SingleInputView: UIView, Form, Stylable { private var inputField: InputField private var titleView: UILabel private var messageView: UILabel @@ -118,7 +118,12 @@ class SingleInputView: UIView, Form { messageView.numberOfLines = 4 messageView.textAlignment = .center messageView.font = regularSystemFont(size: 15) + messageView.textColor = Style.Auth0.textColor inputField.type = self.type inputField.returnKey = self.returnKey } + + func apply(style: Style) { + self.messageView.textColor = style.textColor + } } diff --git a/Lock/Style.swift b/Lock/Style.swift index 90d2c17f8..cc82e7f02 100644 --- a/Lock/Style.swift +++ b/Lock/Style.swift @@ -36,11 +36,14 @@ public struct Style { /// Lock background color public var backgroundColor = UIColor.white + /// Lock background image + public var backgroundImage: LazyImage? + /// Lock disabled component color - public var disabledColor = UIColor ( red: 0.8902, green: 0.898, blue: 0.9059, alpha: 1.0 ) + public var disabledColor = UIColor(red: 0.8902, green: 0.898, blue: 0.9059, alpha: 1.0 ) /// Lock disabled component text color - public var disabledTextColor = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) + public var disabledTextColor = UIColor(red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) /// Primary button tint color public var buttonTintColor = UIColor.white @@ -51,6 +54,12 @@ public struct Style { /// Blur effect style used. It can be any value defined in `UIBlurEffectStyle` public var headerBlur: UIBlurEffectStyle = .light + /// Header close button image + public var headerCloseIcon: LazyImage = lazyImage(named: "ic_close") + + /// Header back button image + public var headerBackIcon: LazyImage = lazyImage(named: "ic_back") + /// Header title color public var titleColor = UIColor.black @@ -61,6 +70,9 @@ public struct Style { } } + /// Main body text color + public var textColor = UIColor.black + /// Hide primary bytton title (show only icon). By default is false public var hideButtonTitle = false @@ -70,6 +82,51 @@ public struct Style { /// OAuth2 custom connection styles by mapping a connection name with an `AuthStyle` public var oauth2: [String: AuthStyle] = [:] + /// Social seperator label + public var seperatorTextColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.54) + + /// Input field text color + public var inputTextColor = UIColor.black + + /// Input field placeholder text color + public var inputPlaceholderTextColor = UIColor(red: 0.780, green: 0.780, blue: 0.804, alpha: 1.00) + + /// Input field border color default + public var inputBorderColor = UIColor(red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0) + + /// Input field border color invalid + public var inputBorderColorError = UIColor.red + + /// Input field background color + public var inputBackgroundColor = UIColor.white + + /// Input field icon background color + public var inputIconBackgroundColor = UIColor(red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0) + + /// Input field icon color + public var inputIconColor = UIColor(red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0) + + /// Secondary button color + public var secondaryButtonColor = UIColor.black + + /// Database login Tab Text Color + public var tabTextColor = UIColor(red: 0.3608, green: 0.4, blue: 0.4353, alpha: 0.6) + + /// Database login Tab Tint Color + public var tabTintColor = UIColor(red: 0.3608, green: 0.4, blue: 0.4353, alpha: 0.6) + + /// Lock Controller Status bar update animation + public var statusBarUpdateAnimation: UIStatusBarAnimation = .none + + /// Lock Controller Status bar hidden + public var statusBarHidden = false + + /// Lock Controller Status bar style + public var statusBarStyle: UIStatusBarStyle = .default + + /// Passwordless search bar style + public var searchBarStyle: UISearchBarStyle = .default + var headerMask: UIImage? { let image = self.logo.image(compatibleWithTraits: nil) if Style.Auth0.logo == self.logo { diff --git a/Lock/UnrecoverableErrorView.swift b/Lock/UnrecoverableErrorView.swift index ceba97a17..a6574f6cf 100644 --- a/Lock/UnrecoverableErrorView.swift +++ b/Lock/UnrecoverableErrorView.swift @@ -27,6 +27,8 @@ class UnrecoverableErrorView: UIView, View { weak var secondaryButton: SecondaryButton? weak var messageLabel: UILabel? + private weak var titleLabel: UILabel? + init(canRetry: Bool) { let center = UILayoutGuide() let titleLabel = UILabel() @@ -35,6 +37,7 @@ class UnrecoverableErrorView: UIView, View { let actionButton = SecondaryButton() self.secondaryButton = actionButton self.messageLabel = messageLabel + self.titleLabel = titleLabel super.init(frame: CGRect.zero) @@ -71,9 +74,10 @@ class UnrecoverableErrorView: UIView, View { titleLabel.textAlignment = .center titleLabel.font = lightSystemFont(size: 22) titleLabel.numberOfLines = 1 + titleLabel.textColor = Style.Auth0.textColor messageLabel.textAlignment = .center messageLabel.font = regularSystemFont(size: 15) - messageLabel.textColor = UIColor(red: 0.408, green: 0.408, blue: 0.408, alpha: 1.00) + messageLabel.textColor = Style.Auth0.textColor.withAlphaComponent(0.50) messageLabel.numberOfLines = 3 actionButton.button?.setTitleColor(UIColor(red:0.04, green:0.53, blue:0.69, alpha:1.0), for: .normal) @@ -96,5 +100,7 @@ class UnrecoverableErrorView: UIView, View { } func apply(style: Style) { + self.titleLabel?.textColor = style.textColor + self.messageLabel?.textColor = style.textColor.withAlphaComponent(0.50) } } diff --git a/Lock/View.swift b/Lock/View.swift index de2020b23..ec645a209 100644 --- a/Lock/View.swift +++ b/Lock/View.swift @@ -25,6 +25,7 @@ import UIKit protocol View: Stylable { func layout(inView root: UIView, below view: UIView) -> NSLayoutConstraint? func remove() + func applyAll(withStyle style: Style) } extension View where Self: UIView { @@ -45,4 +46,9 @@ extension View where Self: UIView { func remove() { self.removeFromSuperview() } + + func applyAll(withStyle style: Style) { + self.apply(style: style) + self.styleSubViews(style: style) + } } diff --git a/LockSnapshot/Info.plist b/LockSnapshot/Info.plist new file mode 100644 index 000000000..6c6c23c43 --- /dev/null +++ b/LockSnapshot/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/LockSnapshot/LockSnapshot.swift b/LockSnapshot/LockSnapshot.swift new file mode 100644 index 000000000..e926646d8 --- /dev/null +++ b/LockSnapshot/LockSnapshot.swift @@ -0,0 +1,125 @@ +// LockSnapshot.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import XCTest +@testable import Lock + +class LockSnapshot: XCTestCase { + + let app = XCUIApplication() + + override func setUp() { + super.setUp() + setupSnapshot(app) + app.launch() + } + + override func tearDown() { + super.tearDown() + } + + func testClassic() { + + let app = XCUIApplication() + + app.buttons["LOGIN WITH CDN CLASSIC"].tap() + snapshot("A1-Lock-Classic-Database-Social-Login") + + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo") + snapshot("A2-Lock-Classic-Database-Social-Login-Input-Error") + + app.scrollViews.otherElements.buttons["Sign Up"].tap() + snapshot("A3-Lock-Classic-Database-Social-Signup") + + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo") + snapshot("A4-Lock-Classic-Database-Social-Signup-Input-Error") + + app.scrollViews.otherElements.buttons["Log In"].tap() + app.scrollViews.otherElements.buttons["Don’t remember your password?"].tap() + snapshot("A5-Lock-Classic-Database-Social-Forgot-Password") + + app.scrollViews.otherElements.containing(.staticText, identifier:"Reset Password").children(matching: .button).element(boundBy: 0).tap() + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo@bar.com") + snapshot("A6-Lock-Classic-Database-Social-Enterprise") + + app.scrollViews.otherElements.buttons["LOG IN "].tap() + snapshot("A7-Lock-Classic-Database-Social-Enterprise-ActiveAuth") + + } + + func testClassicCustom() { + + let app = XCUIApplication() + + app.buttons["LOGIN WITH CDN CUSTOM STYLE"].tap() + snapshot("A1B-Lock-Classic-Custom-Database-Social-Login") + + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo") + snapshot("A2B-Lock-Classic-Custom-Database-Social-Login-Input-Error") + + app.scrollViews.otherElements.buttons["Sign Up"].tap() + snapshot("A3B-Lock-Classic-Custom-Database-Social-Signup") + + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo") + snapshot("A4B-Lock-Classic-Custom-Database-Social-Signup-Input-Error") + + app.scrollViews.otherElements.buttons["Log In"].tap() + app.scrollViews.otherElements.buttons["Don’t remember your password?"].tap() + snapshot("A5B-Lock-Classic-Custom-Database-Social-Forgot-Password") + + app.scrollViews.otherElements.containing(.staticText, identifier:"Reset Password").children(matching: .button).element(boundBy: 0).tap() + app.textFields["Email"].tap() + app.textFields["Email"].typeText("foo@bar.com") + snapshot("A6B-Lock-Classic-Custom-Database-Social-Enterprise") + + app.scrollViews.otherElements.buttons["LOG IN "].tap() + snapshot("A7B-Lock-Classic-Custom-Database-Social-Enterprise-ActiveAuth") + } + + func testPasswordless() { + + let app = XCUIApplication() + + app.buttons["LOGIN WITH CDN PASSWORDLESS"].tap() + snapshot("B1-Lock-Passwordless") + + app.scrollViews.otherElements.staticTexts["United States"].tap() + snapshot("B2-Lock-Passwordless-Country") + } + + func testPasswordlessCustom() { + + let app = XCUIApplication() + + app.buttons["LOGIN WITH CDN PASSWORDLESS CUSTOM STYLE"].tap() + snapshot("B1C-Lock-Passwordless-Custom") + + app.scrollViews.otherElements.staticTexts["United States"].tap() + snapshot("B2C-Lock-Passwordless-Country") + } + +} diff --git a/LockSnapshot/SnapshotHelper.swift b/LockSnapshot/SnapshotHelper.swift new file mode 100644 index 000000000..719e3fd7a --- /dev/null +++ b/LockSnapshot/SnapshotHelper.swift @@ -0,0 +1,166 @@ +// +// SnapshotHelper.swift +// Example +// +// Created by Felix Krause on 10/8/15. +// Copyright © 2015 Felix Krause. All rights reserved. +// + +import Foundation +import XCTest + +var deviceLanguage = "" +var locale = "" + +@available(*, deprecated, message: "use setupSnapshot: instead") +func setLanguage(_ app: XCUIApplication) { + setupSnapshot(app) +} + +func setupSnapshot(_ app: XCUIApplication) { + Snapshot.setupSnapshot(app) +} + +func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { + Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator) +} + +open class Snapshot: NSObject { + + open class func setupSnapshot(_ app: XCUIApplication) { + setLanguage(app) + setLocale(app) + setLaunchArguments(app) + } + + class func setLanguage(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("language.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) + app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] + } catch { + print("Couldn't detect/set language...") + } + } + + class func setLocale(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("locale.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) + } catch { + print("Couldn't detect/set locale...") + } + if locale.isEmpty { + locale = Locale(identifier: deviceLanguage).identifier + } + app.launchArguments += ["-AppleLocale", "\"\(locale)\""] + } + + class func setLaunchArguments(_ app: XCUIApplication) { + guard let prefix = pathPrefix() else { + return + } + + let path = prefix.appendingPathComponent("snapshot-launch_arguments.txt") + app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] + + do { + let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) + let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) + let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location:0, length:launchArguments.characters.count)) + let results = matches.map { result -> String in + (launchArguments as NSString).substring(with: result.range) + } + app.launchArguments += results + } catch { + print("Couldn't detect/set launch_arguments...") + } + } + + open class func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) { + if waitForLoadingIndicator { + waitForLoadingIndicatorToDisappear() + } + + print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work + + sleep(1) // Waiting for the animation to be finished (kind of) + + #if os(tvOS) + XCUIApplication().childrenMatchingType(.Browser).count + #elseif os(OSX) + XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) + #else + XCUIDevice.shared().orientation = .unknown + #endif + } + + class func waitForLoadingIndicatorToDisappear() { + #if os(tvOS) + return + #endif + + let query = XCUIApplication().statusBars.children(matching: .other).element(boundBy: 1).children(matching: .other) + + while (0.. URL? { + let homeDir: URL + //on OSX config is stored in /Users//Library + //and on iOS/tvOS/WatchOS it's in simulator's home dir + #if os(OSX) + guard let user = ProcessInfo().environment["USER"] else { + print("Couldn't find Snapshot configuration files - can't detect current user ") + return nil + } + + guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else { + print("Couldn't find Snapshot configuration files - can't detect `Users` dir") + return nil + } + + homeDir = usersDir.appendingPathComponent(user) + #else + guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { + print("Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable.") + return nil + } + guard let homeDirUrl = URL(string: simulatorHostHome) else { + print("Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?") + return nil + } + homeDir = homeDirUrl + #endif + return homeDir.appendingPathComponent("Library/Caches/tools.fastlane") + } +} + +extension XCUIElement { + var isLoadingIndicator: Bool { + let whiteListedLoaders = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] + if whiteListedLoaders.contains(self.identifier) { + return false + } + return self.frame.size == CGSize(width: 10, height: 20) + } +} + +// Please don't remove the lines below +// They are used to detect outdated configuration files +// SnapshotHelperVersion [1.3] diff --git a/LockTests/Interactors/PasswordlessInteractorSpec.swift b/LockTests/Interactors/PasswordlessInteractorSpec.swift index eeaa4d80c..56e177c74 100644 --- a/LockTests/Interactors/PasswordlessInteractorSpec.swift +++ b/LockTests/Interactors/PasswordlessInteractorSpec.swift @@ -486,7 +486,7 @@ class PasswordlessInteractorSpec: QuickSpec { } it("should store passwordless transaction on sending link") { - waitUntil(timeout: 2) { done in + waitUntil(timeout: 4) { done in interactor.request(connection.name) { error in expect(error).to(beNil()) done() diff --git a/LockTests/StyleSpec.swift b/LockTests/StyleSpec.swift index e4f505150..c3b44cd2d 100644 --- a/LockTests/StyleSpec.swift +++ b/LockTests/StyleSpec.swift @@ -49,6 +49,81 @@ class StyleSpec: QuickSpec { expect(style.hideButtonTitle) == false } + it("should have text color") { + expect(style.textColor) == UIColor.black + } + + it("should have logo") { + expect(style.logo) == lazyImage(named: "ic_auth0") + } + + it("should have social seperator text color") { + expect(style.seperatorTextColor) == UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.54) + } + + it("should have input field text color") { + expect(style.inputTextColor) == UIColor.black + } + + it("should have input field placeholder text color") { + expect(style.inputPlaceholderTextColor) == UIColor(red: 0.780, green: 0.780, blue: 0.804, alpha: 1.00) + } + + it("should have input field border color default") { + expect(style.inputBorderColor) == UIColor(red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0) + } + + it("should have input field border color invalid") { + expect(style.inputBorderColorError) == UIColor.red + } + + it("should have input field background color") { + expect(style.inputBackgroundColor) == UIColor.white + } + + it("should have input field icon background color") { + expect(style.inputIconBackgroundColor) == UIColor(red: 0.9333, green: 0.9333, blue: 0.9333, alpha: 1.0) + } + + it("should have input field icon color") { + expect(style.inputIconColor) == UIColor(red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) + } + + it("should have secondary button color") { + expect(style.secondaryButtonColor) == UIColor.black + } + + it("should have database login tab text color") { + expect(style.tabTextColor) == UIColor(red: 0.3608, green: 0.4, blue: 0.4353, alpha: 0.6) + } + + it("should have database login tab tint color") { + expect(style.tabTintColor) == UIColor(red: 0.3608, green: 0.4, blue: 0.4353, alpha: 0.6) + } + + it("should have lock controller status bar update animation") { + expect(style.statusBarUpdateAnimation.rawValue) == 0 + } + + it("should have lock controller status bar hidden") { + expect(style.statusBarHidden) == false + } + + it("should have lock controller status bar style") { + expect(style.statusBarStyle.rawValue) == 0 + } + + it("should have search status bar style") { + expect(style.searchBarStyle.rawValue) == 0 + } + + it("should have header close button image") { + expect(style.headerCloseIcon) == lazyImage(named: "ic_close") + } + + it("should have header back button image") { + expect(style.headerBackIcon) == lazyImage(named: "ic_back") + } } describe("custom style") { diff --git a/codecov.yml b/codecov.yml index 8470b5d7e..883c8ef4d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,10 +8,12 @@ coverage: status: patch: default: + threshold: 50 if_no_uploads: error changes: true project: default: target: auto + threshold: 2 if_no_uploads: error -comment: false \ No newline at end of file +comment: false diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 498d29f3d..36e104ca1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,3 +1,4 @@ +skip_docs opt_out_usage fastlane_version "2.27.0" diff --git a/fastlane/README.md b/fastlane/README.md index dcefc5451..882aa9815 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -60,6 +60,11 @@ Runs all the tests in a CI environment fastlane ios i18n ``` +### ios screenshots +``` +fastlane ios screenshots +``` +UI Screenshots ### ios release ``` fastlane ios release diff --git a/fastlane/Snapfile b/fastlane/Snapfile new file mode 100644 index 000000000..262124bf4 --- /dev/null +++ b/fastlane/Snapfile @@ -0,0 +1,36 @@ +# Uncomment the lines below you want to change by removing the # in the beginning + +# A list of devices you want to take the screenshots from +devices([ + "iPhone 7" +# "iPhone 6 Plus", +# "iPhone 5", +# "iPad Pro (12.9 inch)", +# "iPad Pro (9.7 inch)", +# "Apple TV 1080p" +]) + +languages([ + "en-US" + #"de-DE", + #"it-IT", + #["pt", "pt_BR"] # Portuguese with Brazilian locale +]) + +# The name of the scheme which contains the UI Tests +scheme "LockApp" + +# Where should the resulting screenshots be stored? +# output_directory "./screenshots" + +clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones + +# Choose which project/workspace to use +# project "./Project.xcodeproj" +# workspace "./Project.xcworkspace" + +# Arguments to pass to the app on launch. See https://github.com/fastlane/fastlane/tree/master/snapshot#launch-arguments +# launch_arguments(["-favColor red"]) + +# For more information about all available options run +# fastlane snapshot --help