diff --git a/BraveShared/BraveStrings.swift b/BraveShared/BraveStrings.swift index d20abcefe08..4f7003c71d8 100644 --- a/BraveShared/BraveStrings.swift +++ b/BraveShared/BraveStrings.swift @@ -78,12 +78,6 @@ extension Strings { bundle: .braveShared, value: "Oops! Something went wrong. Please try again.", comment: "") } -// MARK:- BasePasscodeViewController.swift -extension Strings { - public static let passcodeConfirmMisMatchErrorText = NSLocalizedString("PasscodeConfirmMisMatchErrorText", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Passcodes didn’t match. Try again.", comment: "Error message displayed to user when their confirming passcode doesn't match the first code.") - public static let passcodeMatchOldErrorText = NSLocalizedString("PasscodeMatchOldErrorText", tableName: "BraveShared", bundle: Bundle.braveShared, value: "New passcode must be different than existing code.", comment: "Error message displayed when user tries to enter the same passcode as their existing code when changing it.") -} - // MARK:- SearchViewController.swift extension Strings { public static let searchSettingsButtonTitle = NSLocalizedString("SearchSettingsButtonTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Search Settings", comment: "Label for search settings button.") @@ -410,53 +404,23 @@ extension Strings { public static let menuItemOpenAndFillTitle = NSLocalizedString("MenuItemOpenAndFillTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Open & Fill", comment: "Open and Fill website text selection menu item") } -// MARK:- AuthenticationManagerConstants.swift +// MARK: - Passcode / Browser Lock extension Strings { - public static let authenticationPasscode = NSLocalizedString("AuthenticationPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Passcode For Logins", comment: "Label for the Passcode item in Settings") + public static let authenticationPasscode = NSLocalizedString("AuthenticationPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Passcode", comment: "Label for the Passcode item in Settings") public static let authenticationTouchIDPasscodeSetting = NSLocalizedString("AuthenticationTouchIDPasscodeSetting", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Touch ID & Passcode", comment: "Label for the Touch ID/Passcode item in Settings") public static let authenticationFaceIDPasscodeSetting = NSLocalizedString("AuthenticationFaceIDPasscodeSetting", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Face ID & Passcode", comment: "Label for the Face ID/Passcode item in Settings") - public static let authenticationRequirePasscode = NSLocalizedString("AuthenticationRequirePasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Require Passcode", comment: "Text displayed in the 'Interval' section, followed by the current interval setting, e.g. 'Immediately'") - - public static let authenticationEnterAPasscode = NSLocalizedString("AuthenticationEnterAPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Enter a passcode", comment: "Text displayed above the input field when entering a new passcode") - - public static let authenticationEnterPasscodeTitle = NSLocalizedString("AuthenticationEnterPasscodeTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Enter Passcode", comment: "Title of the dialog used to request the passcode") - - public static let authenticationEnterPasscode = NSLocalizedString("AuthenticationEnterPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Enter passcode", comment: "Text displayed above the input field when changing the existing passcode") - - public static let authenticationReenterPasscode = NSLocalizedString("AuthenticationReenterPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Re-enter passcode", comment: "Text displayed above the input field when confirming a passcode") - - public static let authenticationSetPasscode = NSLocalizedString("AuthenticationSetPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Set Passcode", comment: "Title of the dialog used to set a passcode") - - public static let authenticationTurnOffPasscode = NSLocalizedString("AuthenticationTurnOffPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Turn Passcode Off", comment: "Label used as a setting item to turn off passcode") - - public static let authenticationTurnOnPasscode = NSLocalizedString("AuthenticationTurnOnPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Turn Passcode On", comment: "Label used as a setting item to turn on passcode") - - public static let authenticationChangePasscode = NSLocalizedString("AuthenticationChangePasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Change Passcode", comment: "Label used as a setting item and title of the following screen to change the current passcode") - - public static let authenticationEnterNewPasscode = NSLocalizedString("AuthenticationEnterNewPasscode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Enter a new passcode", comment: "Text displayed above the input field when changing the existing passcode") - - public static let authenticationImmediately = NSLocalizedString("AuthenticationImmediately", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Immediately", comment: "Immediately' interval item for selecting when to require passcode") - - public static let authenticationLoginsTouchReason = NSLocalizedString("AuthenticationLoginsTouchReason", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Use your fingerprint to access Logins now.", comment: "Touch ID prompt subtitle when accessing logins") - - public static let authenticationRequirePasscodeTouchReason = NSLocalizedString("AuthenticationRequirePasscodeTouchReason", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Use your fingerprint to access configuring your required passcode interval.", comment: "Touch ID prompt subtitle when accessing the require passcode setting") - - public static let authenticationDisableTouchReason = NSLocalizedString("AuthenticationDisableTouchReason", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Use your fingerprint to disable Touch ID.", comment: "Touch ID prompt subtitle when disabling Touch ID") - - public static let authenticationWrongPasscodeError = NSLocalizedString("AuthenticationWrongPasscodeError", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Incorrect passcode. Try again.", comment: "Error message displayed when user enters incorrect passcode when trying to enter a protected section of the app") - - public static let authenticationIncorrectAttemptsRemaining = NSLocalizedString("AuthenticationIncorrectAttemptsRemaining", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Incorrect passcode. Try again (Attempts remaining: %d).", comment: "Error message displayed when user enters incorrect passcode when trying to enter a protected section of the app with attempts remaining") + public static let authenticationLoginsTouchReason = NSLocalizedString("AuthenticationLoginsTouchReason", tableName: "BraveShared", bundle: Bundle.braveShared, value: "This authenticates your access to Brave", comment: "Touch ID or PIN entry prompt subtitle when accessing Brave with the Browser Lock feature enabled") - public static let authenticationMaximumAttemptsReached = NSLocalizedString("AuthenticationMaximumAttemptsReached", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Too many failed attempts.\n Please try again in %d minutes.", comment: "Error message displayed when user enters incorrect passcode and has reached the maximum number of attempts.") + public static let browserLockMigrationTitle = NSLocalizedString("browserLockMigrationTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Open Brave with your Apple Face ID, Touch ID, or passcode", comment: "Title on the screen shown to the user when they are being migrated from the old Passcode feature to the new Browser Lock feature") - public static let authenticationMaximumAttemptsReachedOneMinute = NSLocalizedString("AuthenticationMaximumAttemptsReachedOneMinute", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Too many failed attempts.\n Please try again in 1 minute.", comment: "Error message displayed when user enters incorrect passcode and has reached the maximum number of attempts.") + public static let browserLockMigrationSubtitle = NSLocalizedString("browserLockMigrationSubtitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "We’ve simplified Brave Passcodes. You can now access Brave with the same ID or passcode you use on your phone.", comment: "Subtitle on the screen shown to the user when they are being migrated from the old Passcode feature to the new Browser Lock feature") - public static let authenticationMaximumAttemptsReachedNoTime = NSLocalizedString("AuthenticationMaximumAttemptsReachedNoTime", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Maximum attempts reached. Please try again later.", comment: "Error message displayed when user enters incorrect passcode and has reached the maximum number of attempts.") + public static let browserLockMigrationNoPasscodeSetup = NSLocalizedString("browserLockMigrationNoPasscodeSetup", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Haven't set an ID or passcode? Open Settings, then tap \"%@\"", comment: "Displayed to the user when they don't have a passcode set on their phone, therefore cannot use the Browser Lock feature. \"%@\" will be filled with either \"Touch ID & Passcode\" or \"Face ID & Passcode\"") - public static let authenticationTouchForKeyboard = NSLocalizedString("AuthenticationTouchForKeyboard", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Tap to bring up the keyboard accessibility", comment: "When the user taps on the passcode pane, the touch gesture recognizer will bring up the keyboard back on the screen when hidden on iPad.") + public static let browserLockMigrationContinueButtonTitle = NSLocalizedString("browserLockMigrationContinueButtonTitle", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Continue", comment: "A button title for the action to move to the next step of the browser lock migration process.") } // MARK:- Settings. @@ -621,6 +585,8 @@ extension Strings { public static let confirm = NSLocalizedString("Confirm", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Confirm", comment: "") public static let privacy = NSLocalizedString("Privacy", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Privacy", comment: "Settings privacy section title") public static let security = NSLocalizedString("Security", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Security", comment: "Settings security section title") + public static let browserLock = NSLocalizedString("BrowserLock", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Browser Lock", comment: "Setting to enable the browser lock privacy feature") + public static let browserLockDescription = NSLocalizedString("BrowserLockDescription", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Unlock Brave with Touch ID, Face ID or system passcode.", comment: "") public static let saveLogins = NSLocalizedString("SaveLogins", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Save Logins", comment: "Setting to enable the built-in password manager") public static let showBookmarkButtonInTopToolbar = NSLocalizedString("ShowBookmarkButtonInTopToolbar", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Show Bookmarks Shortcut", comment: "Setting to show a bookmark button on the top level menu that triggers a panel of the user's bookmarks.") public static let alwaysRequestDesktopSite = NSLocalizedString("AlwaysRequestDesktopSite", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Always Request Desktop Site", comment: "Setting to always request the desktop version of a website.") diff --git a/BraveUI/Buttons/ActionButton.swift b/BraveUI/Buttons/ActionButton.swift index 86ae78c6b8d..fb2e0ece7bb 100644 --- a/BraveUI/Buttons/ActionButton.swift +++ b/BraveUI/Buttons/ActionButton.swift @@ -5,7 +5,7 @@ import UIKit import BraveShared -open class ActionButton: Button { +open class ActionButton: BraveButton { override public init(frame: CGRect) { super.init(frame: frame) @@ -35,7 +35,7 @@ open class ActionButton: Button { } } -open class FilledActionButton: Button { +open class FilledActionButton: BraveButton { override public init(frame: CGRect) { super.init(frame: frame) diff --git a/BraveUI/Buttons/Button.swift b/BraveUI/Buttons/BraveButton.swift similarity index 99% rename from BraveUI/Buttons/Button.swift rename to BraveUI/Buttons/BraveButton.swift index 3b3abaf8552..738955ca688 100644 --- a/BraveUI/Buttons/Button.swift +++ b/BraveUI/Buttons/BraveButton.swift @@ -10,7 +10,7 @@ import SnapKit /// - Flipping the image placement /// - Applying a larger hit area without adjusting the bounds /// - Sizing correctly when adding setting `titleEdgeInsets` and `imageEdgeInsets` -open class Button: UIButton { +open class BraveButton: UIButton { // MARK: - Activity diff --git a/BraveUI/Design System/Colors/Gradients.swift b/BraveUI/Design System/Colors/Gradients.swift index 990713f39d8..06f3c85c7db 100644 --- a/BraveUI/Design System/Colors/Gradients.swift +++ b/BraveUI/Design System/Colors/Gradients.swift @@ -3,19 +3,41 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. import UIKit -import struct SwiftUI.Angle +import SwiftUI + +extension Gradient { + init(braveGradient gradient: BraveGradient) { + self.init( + stops: gradient.stops.map { stop in + .init(color: Color(stop.color), location: CGFloat(stop.position)) + } + ) + } +} + +extension LinearGradient { + /// Create a SwiftUI LinearGradient from a Brave defined Gradient + public init(braveGradient gradient: BraveGradient) { + assert(gradient.type == .axial, "Attempting to create a LinearGradient with a non-linear Brave defined gradient") + self.init( + gradient: Gradient(braveGradient: gradient), + startPoint: .init(x: gradient.startPoint.x, y: gradient.startPoint.y), + endPoint: .init(x: gradient.endPoint.x, y: gradient.endPoint.y) + ) + } +} public struct BraveGradient { public struct Stop { - var color: UIColor - var position: Double + public var color: UIColor + public var position: Double } - var type: CAGradientLayerType = .axial - var stops: [Stop] - var startPoint: CGPoint - var endPoint: CGPoint + public var type: CAGradientLayerType = .axial + public var stops: [Stop] + public var startPoint: CGPoint + public var endPoint: CGPoint - init(stops: [Stop], angle: Angle) { + public init(stops: [Stop], angle: Angle) { let alpha = angle.radians let startPoint = CGPoint( x: 0.5 * sin(alpha) + 0.5, @@ -28,7 +50,7 @@ public struct BraveGradient { self.init(stops: stops, startPoint: startPoint, endPoint: endPoint) } - init(stops: [Stop], startPoint: CGPoint, endPoint: CGPoint) { + public init(stops: [Stop], startPoint: CGPoint, endPoint: CGPoint) { self.stops = stops self.startPoint = startPoint self.endPoint = endPoint diff --git a/BraveUI/Design System/Views/BraveButtonStyle.swift b/BraveUI/Design System/Views/BraveButtonStyle.swift new file mode 100644 index 00000000000..d6cdd12716e --- /dev/null +++ b/BraveUI/Design System/Views/BraveButtonStyle.swift @@ -0,0 +1,130 @@ +// Copyright 2021 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import SwiftUI + +public struct BraveButtonSize { + public var font: Font + public var padding: EdgeInsets + + public init(font: Font, padding: EdgeInsets) { + self.font = font + self.padding = padding + } + + public static let small: Self = .init( + font: Font.caption.weight(.semibold), + padding: .init(top: 6, leading: 12, bottom: 6, trailing: 12) + ) + public static let normal: Self = .init( + font: Font.callout.weight(.semibold), + padding: .init(top: 8, leading: 14, bottom: 8, trailing: 14) + ) + public static let large: Self = .init( + font: Font.body.weight(.semibold), + padding: .init(top: 10, leading: 20, bottom: 10, trailing: 20) + ) +} + +public struct BraveFilledButtonStyle: ButtonStyle { + @Environment(\.isEnabled) private var isEnabled + + public var size: BraveButtonSize + + public init(size: BraveButtonSize) { + self.size = size + } + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .opacity(configuration.isPressed ? 0.7 : 1.0) + .font(size.font) + .foregroundColor(.white) + .padding(size.padding) + .background( + Group { + if isEnabled { + Color(.braveBlurple).opacity(configuration.isPressed ? 0.7 : 1.0) + } else { + Color(.braveDisabled) + } + } + ) + .clipShape(Capsule()) + .contentShape(Capsule()) + .animation(.linear(duration: 0.15), value: isEnabled) + } +} + +public struct BraveOutlineButtonStyle: ButtonStyle { + @Environment(\.isEnabled) private var isEnabled + + public var size: BraveButtonSize + + public init(size: BraveButtonSize) { + self.size = size + } + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .opacity(configuration.isPressed ? 0.7 : 1.0) + .font(size.font) + .foregroundColor(isEnabled ? Color(.braveLabel) : Color(.braveDisabled)) + .padding(size.padding) + .background( + Group { + if isEnabled { + Color(.secondaryButtonTint).opacity(configuration.isPressed ? 0.7 : 1.0) + } else { + Color(.braveDisabled) + } + } + .clipShape(Capsule().inset(by: 0.5).stroke()) + ) + .clipShape(Capsule()) + .contentShape(Capsule()) + .animation(.linear(duration: 0.15), value: isEnabled) + } +} + +struct BraveButtonStyle_Previews: PreviewProvider { + static let defaultSizes: [BraveButtonSize] = [ + .small, .normal, .large + ] + + static var previews: some View { + Group { + HStack { + ForEach([false, true], id: \.self) { disabled in + VStack { + ForEach(defaultSizes.indices) { index in + Button(action: { }) { + Text("Button text") + } + .buttonStyle(BraveFilledButtonStyle(size: defaultSizes[index])) + .disabled(disabled) + } + } + .padding() + } + } + HStack { + ForEach([false, true], id: \.self) { disabled in + VStack { + ForEach(defaultSizes.indices) { index in + Button(action: { }) { + Text("Button text") + } + .buttonStyle(BraveOutlineButtonStyle(size: defaultSizes[index])) + .disabled(disabled) + } + } + .padding() + } + } + } + .previewLayout(.sizeThatFits) + } +} diff --git a/BraveUI/SwiftUI/AccessibilityEmbedInScrollViewViewModifier.swift b/BraveUI/SwiftUI/AccessibilityEmbedInScrollViewViewModifier.swift new file mode 100644 index 00000000000..e25c49bf00c --- /dev/null +++ b/BraveUI/SwiftUI/AccessibilityEmbedInScrollViewViewModifier.swift @@ -0,0 +1,65 @@ +// +// AccessibilityEmbedInScrollViewViewModifier.swift +// NativeWalletUI +// +// Created by Kyle Hickinson on 2021-05-07. +// + +import Foundation +import SwiftUI + +extension ContentSizeCategory { + /// A `Bool` value indicating whether the content size category is one that + /// is associated with accessibility. + /// + /// - note: This should function identically to `isAccessibilityCategory`, however is available + /// prior to iOS 13.4. Can be removed when target is greater than 13.4 + @available(iOS, introduced: 13.0, obsoleted: 13.4) + @_disfavoredOverload + var isAccessibilityCategory: Bool { + switch self { + case .accessibilityExtraExtraExtraLarge, + .accessibilityExtraExtraLarge, + .accessibilityExtraLarge, + .accessibilityLarge, + .accessibilityMedium: + return true + default: + return false + } + } +} + +struct AccessibilityEmbedInScrollViewViewModifier: ViewModifier { + @Environment(\.sizeCategory) private var sizeCategory + + // Note: Remove @ViewBuilder when CI is Xcode 12.5+ + @ViewBuilder func body(content: Content) -> some View { + if sizeCategory.isAccessibilityCategory { + ScrollView(.vertical) { + content + .padding(.vertical) + } + } else { + content + } + } +} + +extension View { + /// Embeds the view inside a vertical axis `ScrollView` if the current size category is one that + /// is associated with accessibility. + /// + /// - note: If a user transitions between an accessibility size category and normal size category, + /// this View will be removed and re-added to the view hierarchy and thus cause possible + /// `transition`, `onAppear`, and `onDisappear` executions. + /// - warning: Use this only when you know your View can fit inside all device sizes except when + /// the size category is using accessibility sizes. + /// - warning: Embedding certain View's inside a ScrollView can cause them to layout differently, + /// such as `Spacer` and other views where a `.frame(maxHeight: .infinity)` modifier + /// is used. Ensure that your View renders correctly when an accessibility size + /// category is in use. + public func accessibilityEmbedInScrollView() -> some View { + modifier(AccessibilityEmbedInScrollViewViewModifier()) + } +} diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index 5c6ef6416bd..ad1bb63cf0b 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -274,6 +274,8 @@ 277046C22684CD2A001CB097 /* PanModal in Frameworks */ = {isa = PBXBuildFile; productRef = 277046C12684CD2A001CB097 /* PanModal */; }; 277223722469B4FB0059A7EB /* FaviconFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277223712469B4FB0059A7EB /* FaviconFetcher.swift */; }; 277223762469E7290059A7EB /* LargeFaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277223752469E7290059A7EB /* LargeFaviconView.swift */; }; + 27733E5826977EC40086799A /* PasscodeMigrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27733E5726977EC40086799A /* PasscodeMigrationView.swift */; }; + 27733E792697867D0086799A /* AccessibilityEmbedInScrollViewViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27733E6D2697867C0086799A /* AccessibilityEmbedInScrollViewViewModifier.swift */; }; 277568D425ACF91600C129AF /* SPMLibraries.h in Headers */ = {isa = PBXBuildFile; fileRef = 277568D225ACF91600C129AF /* SPMLibraries.h */; settings = {ATTRIBUTES = (Public, ); }; }; 277568D725ACF91600C129AF /* SPMLibraries.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 277568D025ACF91500C129AF /* SPMLibraries.framework */; }; 277568D825ACF91600C129AF /* SPMLibraries.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 277568D025ACF91500C129AF /* SPMLibraries.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -327,7 +329,7 @@ 278C468E23F1E6270083347F /* BraveUI.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 278C468623F1E6270083347F /* BraveUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 278C469523F1E8620083347F /* LoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECAD234CC7EF009DAC37 /* LoaderView.swift */; }; 278C469623F1E8620083347F /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECAE234CC7EF009DAC37 /* ActionButton.swift */; }; - 278C469723F1E8620083347F /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECB4234CC7EF009DAC37 /* Button.swift */; }; + 278C469723F1E8620083347F /* BraveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECB4234CC7EF009DAC37 /* BraveButton.swift */; }; 278C46A023F1ED340083347F /* LinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271DECA7234CC7EF009DAC37 /* LinkLabel.swift */; }; 278C46A323F1EDAB0083347F /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278C46A223F1EDAB0083347F /* Colors.swift */; }; 278C46A923F216CE0083347F /* AdvancedControlsBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278C46A823F216CE0083347F /* AdvancedControlsBarView.swift */; }; @@ -366,6 +368,7 @@ 27AC7CFA24C77EBC00441317 /* FeedActionAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AC7CF924C77EBC00441317 /* FeedActionAlertView.swift */; }; 27AE360D25E55AA200E795E5 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AE360C25E55AA200E795E5 /* MenuViewController.swift */; }; 27B16C5924E1D72600E0CF2C /* NewTabPageFeedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B16C5824E1D72600E0CF2C /* NewTabPageFeedOverlayView.swift */; }; + 27B33D5826556E7300F3D274 /* BraveButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B33D5726556E7300F3D274 /* BraveButtonStyle.swift */; }; 27B3DDD024AE83710006A7ED /* FeedSourceOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B3DDCF24AE83710006A7ED /* FeedSourceOverride.swift */; }; 27B3DDD524AF98EA0006A7ED /* FeedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B3DDD424AF98EA0006A7ED /* FeedItem.swift */; }; 27B68DE425C48F36002D0826 /* BraveRewards.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27B68DD725C48EE9002D0826 /* BraveRewards.xcframework */; }; @@ -374,6 +377,7 @@ 27B68DF125C48F39002D0826 /* MaterialComponents.xcframework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 27B68DD625C48EE9002D0826 /* MaterialComponents.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27B68E5D25C88CA7002D0826 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = 27B68E5C25C88CA7002D0826 /* FeedKit */; }; 27B68E9425C8911D002D0826 /* BraveNewsAddSourceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B68E9325C8911D002D0826 /* BraveNewsAddSourceViewController.swift */; }; + 27C22A9026409FF200419FC7 /* WindowProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C22A8F26409FF200419FC7 /* WindowProtection.swift */; }; 27C405E3242559AE00347246 /* Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288A2D861AB8B3260023ABC3 /* Shared.framework */; }; 27C405E924255A2B00347246 /* BraveShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DE7688420B3456C00FF5533 /* BraveShared.framework */; }; 27C461DE211B76500088A441 /* ShieldsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C461DD211B76500088A441 /* ShieldsView.swift */; }; @@ -932,12 +936,9 @@ E4ECCDAE1AB131770005E717 /* FiraSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E4ECCDAD1AB131770005E717 /* FiraSans-Medium.ttf */; }; E60138651C89EB7600DF9756 /* Shared.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 288A2D861AB8B3260023ABC3 /* Shared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E60138661C89EB7D00DF9756 /* Storage.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 2FCAE21A1ABB51F800877008 /* Storage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E6108FF91C84E91C005D25E8 /* BasePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6108FF81C84E91C005D25E8 /* BasePasscodeViewController.swift */; }; E6231C011B90A44F005ABB0D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E6231C001B90A44F005ABB0D /* libz.tbd */; }; E6231C051B90A472005ABB0D /* libxml2.2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E6231C041B90A472005ABB0D /* libxml2.2.tbd */; }; E6231C081B90A71E005ABB0D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E6231C001B90A44F005ABB0D /* libz.tbd */; }; - E640E85E1C73A45A00C5F072 /* PasscodeEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E640E85D1C73A45A00C5F072 /* PasscodeEntryViewController.swift */; }; - E640E86A1C73A47C00C5F072 /* PasscodeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = E640E8691C73A47C00C5F072 /* PasscodeViews.swift */; }; E64ED8FA1BC55AE300DAF864 /* UIAlertControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64ED8F91BC55AE300DAF864 /* UIAlertControllerExtensions.swift */; }; E650754E1E37F6AE006961AC /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E650754D1E37F6AE006961AC /* GeometryExtensions.swift */; }; E65075511E37F6D1006961AC /* NSURLExtensionsMailTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E650754F1E37F6D1006961AC /* NSURLExtensionsMailTo.swift */; }; @@ -987,15 +988,10 @@ E65075BE1E37F7AB006961AC /* TimeConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E650758F1E37F7AB006961AC /* TimeConstants.swift */; }; E65075C01E37F7AB006961AC /* WeakList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65075911E37F7AB006961AC /* WeakList.swift */; }; E653422D1C5944F90039DD9E /* BrowserPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E653422C1C5944F90039DD9E /* BrowserPrompts.swift */; }; - E65D89181C8647420006EA35 /* AppAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65D89171C8647420006EA35 /* AppAuthenticator.swift */; }; E660BDD91BB06521009AC090 /* TabsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E660BDD81BB06521009AC090 /* TabsButton.swift */; }; E683F0C21E93D4E90035D990 /* DictionaryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E683F0C11E93D4E90035D990 /* DictionaryExtensions.swift */; }; E689C7301E0C7617008BAADB /* NSAttributedStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E689C72F1E0C7617008BAADB /* NSAttributedStringExtensions.swift */; }; E68AEDB01B18F81A00133D99 /* SwipeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68AEDAF1B18F81A00133D99 /* SwipeAnimator.swift */; }; - E68E7ACB1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68E7ACA1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift */; }; - E68E7ADA1CAC207400FDCA76 /* ChangePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68E7AD91CAC207400FDCA76 /* ChangePasscodeViewController.swift */; }; - E68E7ADC1CAC208200FDCA76 /* SetupPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68E7ADB1CAC208200FDCA76 /* SetupPasscodeViewController.swift */; }; - E68E7ADE1CAC208A00FDCA76 /* RemovePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68E7ADD1CAC208A00FDCA76 /* RemovePasscodeViewController.swift */; }; E6927EC01C7B6FB800D03F75 /* ErrorToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6927EBF1C7B6FB800D03F75 /* ErrorToast.swift */; }; E693F0D91E9D64BD0086DC17 /* OptionalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E693F0D81E9D64BD0086DC17 /* OptionalExtensions.swift */; }; E696FE511C47F86E00EC007C /* AuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E696FE501C47F86E00EC007C /* AuthenticatorTests.swift */; }; @@ -1616,7 +1612,7 @@ 271DECA7234CC7EF009DAC37 /* LinkLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkLabel.swift; sourceTree = ""; }; 271DECAD234CC7EF009DAC37 /* LoaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = LoaderView.swift; sourceTree = ""; tabWidth = 2; }; 271DECAE234CC7EF009DAC37 /* ActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = ""; }; - 271DECB4234CC7EF009DAC37 /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + 271DECB4234CC7EF009DAC37 /* BraveButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BraveButton.swift; sourceTree = ""; }; 271DECB5234CC7EF009DAC37 /* BasicAnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicAnimationController.swift; sourceTree = ""; }; 271DECB8234CC7EF009DAC37 /* UITableViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewExtensions.swift; sourceTree = ""; }; 271DECBA234CC7EF009DAC37 /* UIStackViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStackViewExtensions.swift; sourceTree = ""; }; @@ -1673,6 +1669,8 @@ 276E7A3B22F21DBE00939424 /* RewardsReporting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardsReporting.swift; sourceTree = ""; }; 277223712469B4FB0059A7EB /* FaviconFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconFetcher.swift; sourceTree = ""; }; 277223752469E7290059A7EB /* LargeFaviconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeFaviconView.swift; sourceTree = ""; }; + 27733E5726977EC40086799A /* PasscodeMigrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeMigrationView.swift; sourceTree = ""; }; + 27733E6D2697867C0086799A /* AccessibilityEmbedInScrollViewViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityEmbedInScrollViewViewModifier.swift; sourceTree = ""; }; 277568D025ACF91500C129AF /* SPMLibraries.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SPMLibraries.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 277568D225ACF91600C129AF /* SPMLibraries.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPMLibraries.h; sourceTree = ""; }; 277568D325ACF91600C129AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1855,11 +1853,13 @@ 27AC7CF924C77EBC00441317 /* FeedActionAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedActionAlertView.swift; sourceTree = ""; }; 27AE360C25E55AA200E795E5 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 27B16C5824E1D72600E0CF2C /* NewTabPageFeedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageFeedOverlayView.swift; sourceTree = ""; }; + 27B33D5726556E7300F3D274 /* BraveButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BraveButtonStyle.swift; sourceTree = ""; }; 27B3DDCF24AE83710006A7ED /* FeedSourceOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedSourceOverride.swift; sourceTree = ""; }; 27B3DDD424AF98EA0006A7ED /* FeedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItem.swift; sourceTree = ""; }; 27B68DD625C48EE9002D0826 /* MaterialComponents.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MaterialComponents.xcframework; path = "node_modules/brave-core-ios/MaterialComponents.xcframework"; sourceTree = ""; }; 27B68DD725C48EE9002D0826 /* BraveRewards.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BraveRewards.xcframework; path = "node_modules/brave-core-ios/BraveRewards.xcframework"; sourceTree = ""; }; 27B68E9325C8911D002D0826 /* BraveNewsAddSourceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BraveNewsAddSourceViewController.swift; sourceTree = ""; }; + 27C22A8F26409FF200419FC7 /* WindowProtection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowProtection.swift; sourceTree = ""; }; 27C461DD211B76500088A441 /* ShieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldsView.swift; sourceTree = ""; }; 27C5AC8625D6FA6D00B8F50E /* OPML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPML.swift; sourceTree = ""; }; 27C5AE2D25D72B0A00B8F50E /* OPMLParsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLParsingTests.swift; sourceTree = ""; }; @@ -2615,13 +2615,10 @@ E4E0BB171AFBC9E4008D6260 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E4E25CCA1CA99E7400D0F088 /* HexExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HexExtensionsTests.swift; sourceTree = ""; }; E4ECCDAD1AB131770005E717 /* FiraSans-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "FiraSans-Medium.ttf"; sourceTree = ""; }; - E6108FF81C84E91C005D25E8 /* BasePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = BasePasscodeViewController.swift; sourceTree = ""; tabWidth = 4; }; E61453BD1B750A1700C3F9D7 /* RollingFileLoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RollingFileLoggerTests.swift; sourceTree = ""; }; E6231C001B90A44F005ABB0D /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; E6231C041B90A472005ABB0D /* libxml2.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.2.tbd; path = usr/lib/libxml2.2.tbd; sourceTree = SDKROOT; }; E62AC15F1E956AFC00843532 /* Dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Dev.entitlements; sourceTree = ""; }; - E640E85D1C73A45A00C5F072 /* PasscodeEntryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeEntryViewController.swift; sourceTree = ""; }; - E640E8691C73A47C00C5F072 /* PasscodeViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeViews.swift; sourceTree = ""; }; E64ED8F91BC55AE300DAF864 /* UIAlertControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtensions.swift; sourceTree = ""; }; E650754D1E37F6AE006961AC /* GeometryExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeometryExtensions.swift; sourceTree = ""; }; E650754F1E37F6D1006961AC /* NSURLExtensionsMailTo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSURLExtensionsMailTo.swift; sourceTree = ""; }; @@ -2673,15 +2670,10 @@ E650758F1E37F7AB006961AC /* TimeConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeConstants.swift; sourceTree = ""; }; E65075911E37F7AB006961AC /* WeakList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakList.swift; sourceTree = ""; }; E653422C1C5944F90039DD9E /* BrowserPrompts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserPrompts.swift; sourceTree = ""; }; - E65D89171C8647420006EA35 /* AppAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAuthenticator.swift; sourceTree = ""; }; E660BDD81BB06521009AC090 /* TabsButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsButton.swift; sourceTree = ""; }; E683F0C11E93D4E90035D990 /* DictionaryExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryExtensions.swift; sourceTree = ""; }; E689C72F1E0C7617008BAADB /* NSAttributedStringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedStringExtensions.swift; sourceTree = ""; }; E68AEDAF1B18F81A00133D99 /* SwipeAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeAnimator.swift; sourceTree = ""; }; - E68E7ACA1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingPasscodeViewController.swift; sourceTree = ""; }; - E68E7AD91CAC207400FDCA76 /* ChangePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePasscodeViewController.swift; sourceTree = ""; }; - E68E7ADB1CAC208200FDCA76 /* SetupPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupPasscodeViewController.swift; sourceTree = ""; }; - E68E7ADD1CAC208A00FDCA76 /* RemovePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePasscodeViewController.swift; sourceTree = ""; }; E6927EBF1C7B6FB800D03F75 /* ErrorToast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorToast.swift; sourceTree = ""; }; E693F0D81E9D64BD0086DC17 /* OptionalExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalExtensions.swift; sourceTree = ""; }; E696FE501C47F86E00EC007C /* AuthenticatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatorTests.swift; sourceTree = ""; }; @@ -3754,7 +3746,7 @@ 278C469423F1E84C0083347F /* Buttons */ = { isa = PBXGroup; children = ( - 271DECB4234CC7EF009DAC37 /* Button.swift */, + 271DECB4234CC7EF009DAC37 /* BraveButton.swift */, 271DECAE234CC7EF009DAC37 /* ActionButton.swift */, 271DECAD234CC7EF009DAC37 /* LoaderView.swift */, 276A7909244660B100AC9446 /* SpringButton.swift */, @@ -3804,6 +3796,7 @@ 279CC2A3260CEB4D009B3895 /* SwiftUI */ = { isa = PBXGroup; children = ( + 27733E6D2697867C0086799A /* AccessibilityEmbedInScrollViewViewModifier.swift */, 279CC2AF260CEB5D009B3895 /* ActivityIndicatorView.swift */, 279CC2C3260CEFE7009B3895 /* TableCellButtonStyle.swift */, 44A58CC72693A309005C0879 /* ActivityView.swift */, @@ -3877,6 +3870,15 @@ path = "Brave News"; sourceTree = ""; }; + 27C22A8E26409FE800419FC7 /* Passcode */ = { + isa = PBXGroup; + children = ( + 27C22A8F26409FF200419FC7 /* WindowProtection.swift */, + 27733E5726977EC40086799A /* PasscodeMigrationView.swift */, + ); + path = Passcode; + sourceTree = ""; + }; 27C5AC7A25D6FA5400B8F50E /* OPML */ = { isa = PBXGroup; children = ( @@ -4708,6 +4710,7 @@ 5E2A74CD262E14C200DBBE88 /* Views */ = { isa = PBXGroup; children = ( + 27B33D5726556E7300F3D274 /* BraveButtonStyle.swift */, 5E2A74CE262E14E500DBBE88 /* GradientView.swift */, ); path = Views; @@ -5231,29 +5234,6 @@ path = Entitlements; sourceTree = ""; }; - E68E7ADF1CAC209000FDCA76 /* PasscodeConfiguration */ = { - isa = PBXGroup; - children = ( - E640E8691C73A47C00C5F072 /* PasscodeViews.swift */, - E6108FF81C84E91C005D25E8 /* BasePasscodeViewController.swift */, - E68E7ACA1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift */, - E640E85D1C73A45A00C5F072 /* PasscodeEntryViewController.swift */, - E68E7AD91CAC207400FDCA76 /* ChangePasscodeViewController.swift */, - E68E7ADB1CAC208200FDCA76 /* SetupPasscodeViewController.swift */, - E68E7ADD1CAC208A00FDCA76 /* RemovePasscodeViewController.swift */, - ); - name = PasscodeConfiguration; - sourceTree = ""; - }; - E692E3271C46E62D009D1240 /* AuthenticationManager */ = { - isa = PBXGroup; - children = ( - E68E7ADF1CAC209000FDCA76 /* PasscodeConfiguration */, - E65D89171C8647420006EA35 /* AppAuthenticator.swift */, - ); - path = AuthenticationManager; - sourceTree = ""; - }; E699220D1B94E3EF007C480D /* About */ = { isa = PBXGroup; children = ( @@ -5504,7 +5484,6 @@ 0A4B012320D0321A004D4011 /* UX.swift */, 2816EFFF1B33E05400522243 /* UIConstants.swift */, 392ED7D51D0AEEEE009D9B62 /* Accessors */, - E692E3271C46E62D009D1240 /* AuthenticationManager */, 27A1ABF1248555E900344503 /* Brave Today */, 27829DE72548A81A007CF0B2 /* Brave Rewards */, D3A994941A368691008AD1AC /* Browser */, @@ -5514,6 +5493,7 @@ 2F44FC551A9E83E200FD20CC /* Settings */, D3972BF01C22412B00035B87 /* Share */, 0A1E84322190A4BA0042F782 /* Sync */, + 27C22A8E26409FE800419FC7 /* Passcode */, 271877FF216525FB0006036E /* Popup */, D0FCF7EE1FE44E15004A7995 /* UserContent */, D38A1BEB1A9FA2CA00F6A386 /* Widgets */, @@ -6695,11 +6675,13 @@ 279CC2C4260CEFE7009B3895 /* TableCellButtonStyle.swift in Sources */, 276A790A244660B100AC9446 /* SpringButton.swift in Sources */, 278C46A023F1ED340083347F /* LinkLabel.swift in Sources */, + 27B33D5826556E7300F3D274 /* BraveButtonStyle.swift in Sources */, 27953F7423F5B04500B4B595 /* ViewLabel.swift in Sources */, 278C46B123F4A91D0083347F /* PopoverContentComponent.swift in Sources */, 278C46AF23F4A91C0083347F /* PopoverController.swift in Sources */, 278C46B523F4A9C80083347F /* POPExtensions.swift in Sources */, 278C46B923F4AB5A0083347F /* Then.swift in Sources */, + 27733E792697867D0086799A /* AccessibilityEmbedInScrollViewViewModifier.swift in Sources */, 27EF6B8124BF6095005E034F /* UITableViewExtensions.swift in Sources */, 44A58CC82693A309005C0879 /* ActivityView.swift in Sources */, 278C46B223F4A91D0083347F /* PopoverNavigationController.swift in Sources */, @@ -6708,7 +6690,7 @@ 278C46A323F1EDAB0083347F /* Colors.swift in Sources */, 276A790C244660B500AC9446 /* UICollectionViewExtensions.swift in Sources */, 278C469523F1E8620083347F /* LoaderView.swift in Sources */, - 278C469723F1E8620083347F /* Button.swift in Sources */, + 278C469723F1E8620083347F /* BraveButton.swift in Sources */, 279CC2B0260CEB5D009B3895 /* ActivityIndicatorView.swift in Sources */, 278C469623F1E8620083347F /* ActionButton.swift in Sources */, 278C46B623F4A9CF0083347F /* BasicAnimationController.swift in Sources */, @@ -6957,11 +6939,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E640E86A1C73A47C00C5F072 /* PasscodeViews.swift in Sources */, 0ABA874320E68CF500D2694F /* SessionData.swift in Sources */, D029A04920A62DB0001DB72F /* TemporaryDocument.swift in Sources */, D38F02D11C05127100175932 /* Authenticator.swift in Sources */, - E68E7ADC1CAC208200FDCA76 /* SetupPasscodeViewController.swift in Sources */, 7B3631EA1C244FEE00D12AF9 /* Theme.swift in Sources */, 0A1E843F2190A57F0042F782 /* SyncQRCodeView.swift in Sources */, 4422D56921BFFB7F00BF1855 /* prefilter.cc in Sources */, @@ -7002,6 +6982,7 @@ 5E5E6E6325BA04730035B6A0 /* DataURIParser.swift in Sources */, 0A4B012020D02EC4004D4011 /* TabsBarViewController.swift in Sources */, 440982A526658EAA00BB37FE /* BraveSearchManager.swift in Sources */, + 27C22A9026409FF200419FC7 /* WindowProtection.swift in Sources */, 5E72D54F253657FF00F8D62D /* BraveCoreImportExportUtility.swift in Sources */, 4422D4D921BFFB7600BF1855 /* merger.cc in Sources */, 4422D4EF21BFFB7600BF1855 /* log_reader.cc in Sources */, @@ -7018,6 +6999,7 @@ 0A64384B24FD3F0F000E80A3 /* DomainUserScript.swift in Sources */, 4422D56321BFFB7F00BF1855 /* compile.cc in Sources */, D3C3696E1CC6B78800348A61 /* LocalRequestHelper.swift in Sources */, + 27733E5826977EC40086799A /* PasscodeMigrationView.swift in Sources */, 2726637324981B600056CFE1 /* FeedSectionHeaderView.swift in Sources */, 27C461DE211B76500088A441 /* ShieldsView.swift in Sources */, 0A40450C25A47BD6009EC321 /* FrecencyQuery.swift in Sources */, @@ -7100,7 +7082,6 @@ 5E5E6E3A25BA03510035B6A0 /* PlaylistViewController.swift in Sources */, 0A1E843D2190A57F0042F782 /* SyncSettingsTableViewController.swift in Sources */, 4422D56C21BFFB7F00BF1855 /* bitstate.cc in Sources */, - E68E7ADE1CAC208A00FDCA76 /* RemovePasscodeViewController.swift in Sources */, D314E7F71A37B98700426A76 /* BottomToolbarView.swift in Sources */, 4422D55321BFFB7E00BF1855 /* parse.cc in Sources */, 4422D4F421BFFB7600BF1855 /* version_edit.cc in Sources */, @@ -7110,7 +7091,6 @@ 0A39FE9C2486604D00290ABC /* GRDSubscriberCredential.m in Sources */, FA9293D41D6580E100AC8D33 /* QRCodeViewController.swift in Sources */, 27448533245B5EC2001920B5 /* NTPDataSource.swift in Sources */, - E6108FF91C84E91C005D25E8 /* BasePasscodeViewController.swift in Sources */, 39F4C10A2045DB2E00746155 /* FocusHelper.swift in Sources */, E4CD9F2D1A6DC91200318571 /* TabLocationView.swift in Sources */, 4452CAF0255412800053EFE6 /* DefaultBrowserIntroCalloutViewController.swift in Sources */, @@ -7322,7 +7302,6 @@ 2F44FCCB1A9E972E00FD20CC /* SearchEnginePicker.swift in Sources */, C6B81B8C212D989200996084 /* ImageCacheOptions.swift in Sources */, 0AEFB84922244135007AF600 /* AdblockDebugMenuTableViewController.swift in Sources */, - E68E7ACB1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift in Sources */, 27036F9925684F9F004EF6B6 /* AdSwipeButton.swift in Sources */, 0A6112BD230B4306001BBC45 /* OnboardingViewController.swift in Sources */, D04D1B92209790B60074B35F /* Toast.swift in Sources */, @@ -7402,9 +7381,7 @@ 2746D27D24A3E44900E38852 /* RewardsInternalsLogController.swift in Sources */, 0AFC8F9523D1DF6500941895 /* TranslucentBottomSheet.swift in Sources */, 5E99D01525E56BFB003F30B4 /* PlaylistManager.swift in Sources */, - E68E7ADA1CAC207400FDCA76 /* ChangePasscodeViewController.swift in Sources */, 5E1645A824ABA35B0003C3B2 /* SpinnerView.swift in Sources */, - E640E85E1C73A45A00C5F072 /* PasscodeEntryViewController.swift in Sources */, 274398E224E4827800E79605 /* FeedCard.swift in Sources */, 4422D4BF21BFFB7600BF1855 /* logging.cc in Sources */, 0A1E84462190A57F0042F782 /* SyncAddDeviceViewController.swift in Sources */, @@ -7417,7 +7394,6 @@ EB11A1052044A90E0018F749 /* TrackingProtectionPageStats.swift in Sources */, 0A93F1802264C2D200A3571B /* FolderDetailsView.swift in Sources */, 0A0A07CE247518B100591DFB /* BraveVPNContactFormViewController.swift in Sources */, - E65D89181C8647420006EA35 /* AppAuthenticator.swift in Sources */, 5E99D02025E56C15003F30B4 /* PlaylistDownloadManager.swift in Sources */, 27036F8825684F64004EF6B6 /* DeviceCheck.swift in Sources */, C400467C1CF4E43E00B08303 /* BackForwardListViewController.swift in Sources */, diff --git a/Client/Application/ClientPreferences.swift b/Client/Application/ClientPreferences.swift index daf4211f06c..3e59b9d6c8f 100644 --- a/Client/Application/ClientPreferences.swift +++ b/Client/Application/ClientPreferences.swift @@ -149,6 +149,7 @@ extension Preferences { } final class Privacy { + static let lockWithPasscode = Option(key: "privacy.lock-with-passcode", default: false) /// Forces all private tabs static let privateBrowsingOnly = Option(key: "privacy.private-only", default: false) /// Blocks all cookies and access to local storage diff --git a/Client/Application/Delegates/AppDelegate.swift b/Client/Application/Delegates/AppDelegate.swift index 8462d74664b..659bb68e48f 100644 --- a/Client/Application/Delegates/AppDelegate.swift +++ b/Client/Application/Delegates/AppDelegate.swift @@ -44,7 +44,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati var receivedURLs: [URL]? - var authenticator: AppAuthenticator? + var windowProtection: WindowProtection? var shutdownWebServer: DispatchSourceTimer? /// Object used to handle server pings @@ -169,7 +169,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati self.window!.rootViewController = rootViewController - self.updateAuthenticationInfo() + windowProtection = WindowProtection(window: window!) SystemUtils.onFirstRun() // Schedule Brave Core Priority Tasks @@ -299,8 +299,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati return .braveBlurple } window?.makeKeyAndVisible() - - authenticator = AppAuthenticator(protectedWindow: window!, promptImmediately: true, isPasscodeEntryCancellable: false) if Preferences.Rewards.isUsingBAP.value == nil { Preferences.Rewards.isUsingBAP.value = Locale.current.regionCode == "JP" @@ -454,7 +452,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati func applicationDidBecomeActive(_ application: UIApplication) { shutdownWebServer?.cancel() shutdownWebServer = nil - authenticator?.hideBackgroundedBlur() Preferences.AppState.backgroundedCleanly.value = false @@ -529,34 +526,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIViewControllerRestorati // The reason we need to call this method here instead of `applicationDidBecomeActive` // is that this method is only invoked whenever the application is entering the foreground where as // `applicationDidBecomeActive` will get called whenever the Touch ID authentication overlay disappears. - self.updateAuthenticationInfo() - - if let authInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo(), authInfo.isPasscodeRequiredImmediately { - authenticator?.willEnterForeground() - } - AdblockResourceDownloader.shared.startLoading() browserViewController.showWalletTransferExpiryPanelIfNeeded() } func applicationWillResignActive(_ application: UIApplication) { - if KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() != nil { - authenticator?.showBackgroundBlur() - } - Preferences.AppState.backgroundedCleanly.value = true } - fileprivate func updateAuthenticationInfo() { - if let authInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() { - if !LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { - authInfo.useTouchID = false - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authInfo) - } - } - } - fileprivate func setUpWebServer(_ profile: Profile) { let server = WebServer.sharedInstance if server.server.isRunning { return } diff --git a/Client/Assets/Images.xcassets/browser-lock-icon.imageset/Contents.json b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/Contents.json new file mode 100644 index 00000000000..fc8f33bc039 --- /dev/null +++ b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "key-lock.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "key-lock@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "key-lock@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock.png b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock.png new file mode 100644 index 00000000000..e815951b8b8 Binary files /dev/null and b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock.png differ diff --git a/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@2x.png b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@2x.png new file mode 100644 index 00000000000..8af11366544 Binary files /dev/null and b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@2x.png differ diff --git a/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@3x.png b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@3x.png new file mode 100644 index 00000000000..6d28a98cd4e Binary files /dev/null and b/Client/Assets/Images.xcassets/browser-lock-icon.imageset/key-lock@3x.png differ diff --git a/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/Contents.json b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/Contents.json new file mode 100644 index 00000000000..7ac6e0ae4e0 --- /dev/null +++ b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pin-migration-graphic@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "pin-migration-graphic@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@2x.png b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@2x.png new file mode 100644 index 00000000000..18482eda992 Binary files /dev/null and b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@2x.png differ diff --git a/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@3x.png b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@3x.png new file mode 100644 index 00000000000..be31a8843ea Binary files /dev/null and b/Client/Assets/Images.xcassets/pin-migration-graphic.imageset/pin-migration-graphic@3x.png differ diff --git a/Client/Extensions/AppearanceExtensions.swift b/Client/Extensions/AppearanceExtensions.swift index d576ec82d60..6c7aec9ac65 100644 --- a/Client/Extensions/AppearanceExtensions.swift +++ b/Client/Extensions/AppearanceExtensions.swift @@ -16,9 +16,6 @@ extension AppDelegate { /// - warning: Be careful adjusting colors here, and make sure impact is well known func applyAppearanceDefaults() { // important! for privacy concerns, otherwise UI can bleed through - UIView.appearance(whenContainedInInstancesOf: [BasePasscodeViewController.self]) - .backgroundColor = .braveBackground - UIToolbar.appearance().do { $0.tintColor = .braveOrange $0.standardAppearance = { diff --git a/Client/Frontend/AuthenticationManager/AppAuthenticator.swift b/Client/Frontend/AuthenticationManager/AppAuthenticator.swift deleted file mode 100644 index 971f1f5c8ef..00000000000 --- a/Client/Frontend/AuthenticationManager/AppAuthenticator.swift +++ /dev/null @@ -1,202 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import Shared -import SwiftKeychainWrapper -import LocalAuthentication -import pop -import BraveUI - -/// Displays a app-wide authentication prompt if the user has a passcode enabled -class AppAuthenticator { - /// Whether or not its currently prompting the user to authenticate - private var isPrompting = false - /// Whether or not the user can cancel entering the passcode - var isPasscodeEntryCancellable: Bool - /// A block to execute if the user cancels authenticating (assuming `isPasscodeEntryCancellable` is true) - var didCancelPasscodeEntry: (() -> Void)? - - let protectedWindow: UIWindow - - private var _window: UIWindow? - private var window: UIWindow? { - get { - if _window == nil { - let w = UIWindow() - w.backgroundColor = .clear - w.rootViewController = self.blurController - w.windowLevel = UIWindow.Level(UIWindow.Level.statusBar.rawValue - 1) - _window = w - } - return _window - } - } - - private let blurController = BlurController() - - private lazy var passcodeVC: PasscodeEntryViewController = { - return PasscodeEntryViewController() - }() - - /// Create an authenticator which protects a specific window - init(protectedWindow: UIWindow, promptImmediately: Bool, isPasscodeEntryCancellable: Bool = true) { - self.protectedWindow = protectedWindow - self.isPasscodeEntryCancellable = isPasscodeEntryCancellable - - if promptImmediately { - promptUserForAuthentication() - } - } - - /// Dismisses the authentication prompt - func dismissPrompt() { - blurController.dismiss(animated: true) - isPrompting = false - hideBackgroundedBlur() - } - - /// Show the background blur without prompting the user - func showBackgroundBlur() { - guard let window = window, !window.isKeyWindow else { return } - - window.basicAnimate(property: kPOPViewAlpha, key: "alpha") { animation, inProgress in - if !inProgress { - window.alpha = 0.0 - window.makeKeyAndVisible() - } - animation.duration = 0.1 - animation.toValue = 1.0 - animation.completionBlock = nil - } - } - - /// Hide the background blur - func hideBackgroundedBlur() { - guard let window = window, window.isKeyWindow, !isPrompting else { return } - - window.basicAnimate(property: kPOPViewAlpha, key: "alpha") { animation, _ in - animation.toValue = 0.0 - animation.duration = 0.1 - animation.completionBlock = { _, _ in - self._window = nil - self.protectedWindow.makeKeyAndVisible() - } - } - } - - func willEnterForeground() { - passcodeVC.passcodeCheckSetup() - promptUserForAuthentication() - } - - /// Prompt the user for authentication based on settings - private func promptUserForAuthentication() { - guard let authInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo(), !isPrompting else { return } - - showBackgroundBlur() - AppAuthenticator.presentAuthenticationUsingInfo( - authInfo, - touchIDReason: Strings.authenticationLoginsTouchReason, - success: { - self.dismissPrompt() - }, - cancel: { - if self.isPasscodeEntryCancellable { - self.dismissPrompt() - self.didCancelPasscodeEntry?() - } else { - self.presentPasscodeAuthentication(self.blurController, delegate: self, isCancellable: self.isPasscodeEntryCancellable) - } - }, - fallback: { - self.presentPasscodeAuthentication(self.blurController, delegate: self, isCancellable: self.isPasscodeEntryCancellable) - } - ) - isPrompting = true - } -} - -extension AppAuthenticator { - /// A basic controller to house the background blur and present the manual passcode entry - private class BlurController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - - let backgroundedBlur = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - view.addSubview(backgroundedBlur) - backgroundedBlur.snp.makeConstraints { $0.edges.equalTo(self.view) } - } - } -} - -// MARK: - Auth Presentation -extension AppAuthenticator { - private static func presentAuthenticationUsingInfo(_ authenticationInfo: AuthenticationKeychainInfo, touchIDReason: String, success: (() -> Void)?, cancel: (() -> Void)?, fallback: (() -> Void)?) { - if authenticationInfo.useTouchID { - let localAuthContext = LAContext() - localAuthContext.localizedFallbackTitle = Strings.authenticationEnterPasscode - localAuthContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: touchIDReason) { didSucceed, error in - if didSucceed { - // Update our authentication info's last validation timestamp so we don't ask again based - // on the set required interval - authenticationInfo.recordValidation() - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) - DispatchQueue.main.async { - success?() - } - return - } - - guard let authError = error, - let code = LAError.Code(rawValue: authError._code) else { - return - } - - DispatchQueue.main.async { - switch code { - case .userFallback, .biometryNotEnrolled, .biometryNotAvailable, .biometryLockout: - fallback?() - case .userCancel: - cancel?() - default: - cancel?() - } - } - } - } else { - fallback?() - } - } - - private func presentPasscodeAuthentication(_ controller: UIViewController, delegate: PasscodeEntryDelegate?, isCancellable: Bool) { - - passcodeVC.isCancellable = isCancellable - passcodeVC.delegate = delegate - let navController = UINavigationController(rootViewController: passcodeVC) - - if UIDevice.current.userInterfaceIdiom == .phone { - navController.modalPresentationStyle = .fullScreen - } else { - navController.modalPresentationStyle = .formSheet - } - - // Prevent dismissing the modal by swipe - navController.isModalInPresentation = true - - controller.present(navController, animated: true, completion: nil) - } -} - -// MARK: - PasscodeEntryDelegate -extension AppAuthenticator: PasscodeEntryDelegate { - func passcodeValidationDidSucceed() { - dismissPrompt() - } - - func userDidCancelValidation() { - dismissPrompt() - didCancelPasscodeEntry?() - } -} diff --git a/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift b/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift deleted file mode 100644 index af27fe0bb61..00000000000 --- a/Client/Frontend/AuthenticationManager/BasePasscodeViewController.swift +++ /dev/null @@ -1,103 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import Shared -import SwiftKeychainWrapper - -/// Base UIViewController subclass containing methods for displaying common error messaging -/// for the various Passcode configuration screens. -class BasePasscodeViewController: UIViewController { - var authenticationInfo: AuthenticationKeychainInfo? - var errorToast: ErrorToast? - let errorPadding: CGFloat = 10 - - init() { - self.authenticationInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var isCancellable: Bool = true { - didSet { - updateRightBarButtonItem() - } - } - - func updateRightBarButtonItem() { - navigationItem.rightBarButtonItem = isCancellable ? cancelBarButtonItem : nil - } - - private lazy var cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissAnimated)) - - override func viewDidLoad() { - super.viewDidLoad() - updateRightBarButtonItem() - } - - @objc func dismissAnimated() { - self.dismiss(animated: true, completion: nil) - } -} - -// MARK: - Error Helpers -extension BasePasscodeViewController { - fileprivate func displayError(_ text: String) { - errorToast?.removeFromSuperview() - errorToast = { - let toast = ErrorToast() - toast.textLabel.text = text - view.addSubview(toast) - toast.snp.makeConstraints { make in - make.center.equalTo(self.view) - make.left.greaterThanOrEqualTo(self.view).offset(errorPadding) - make.right.lessThanOrEqualTo(self.view).offset(-errorPadding) - } - return toast - }() - } - - func displayLockoutError() { - if let timeLeft = authenticationInfo?.lockoutTimeLeft { - let inMinutes = Int(ceil(timeLeft / 60)) - if inMinutes == 1 { - displayError(Strings.authenticationMaximumAttemptsReachedOneMinute) - } else { - displayError(String.localizedStringWithFormat(Strings.authenticationMaximumAttemptsReached, inMinutes)) - } - } else { - displayError(Strings.authenticationMaximumAttemptsReachedNoTime) - } - } - - func failMismatchPasscode() { - displayError(Strings.passcodeConfirmMisMatchErrorText) - } - - func failMustBeDifferent() { - - displayError(Strings.passcodeMatchOldErrorText) - } - - func failIncorrectPasscode(_ inputView: PasscodeInputView) { - authenticationInfo?.recordFailedAttempt() - let numberOfAttempts = authenticationInfo?.failedAttempts ?? 0 - if numberOfAttempts == AllowedPasscodeFailedAttempts { - authenticationInfo?.lockOutUser() - displayError(Strings.authenticationMaximumAttemptsReachedNoTime) - inputView.isUserInteractionEnabled = false - resignFirstResponder() - } else { - displayError(String(format: Strings.authenticationIncorrectAttemptsRemaining, (AllowedPasscodeFailedAttempts - numberOfAttempts))) - } - - inputView.resetCode() - - // Store mutations on authentication info object - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) - } -} diff --git a/Client/Frontend/AuthenticationManager/ChangePasscodeViewController.swift b/Client/Frontend/AuthenticationManager/ChangePasscodeViewController.swift deleted file mode 100644 index 8493dfed71f..00000000000 --- a/Client/Frontend/AuthenticationManager/ChangePasscodeViewController.swift +++ /dev/null @@ -1,91 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import SwiftKeychainWrapper -import Shared - -/// Displayed to the user when changing an existing passcode. -class ChangePasscodeViewController: PagingPasscodeViewController, PasscodeInputViewDelegate { - fileprivate var newPasscode: String? - fileprivate var oldPasscode: String? - - override init() { - super.init() - self.title = Strings.authenticationChangePasscode - self.panes = [ - PasscodePane(title: Strings.authenticationEnterPasscode, passcodeSize: authenticationInfo?.passcode?.count ?? 6), - PasscodePane(title: Strings.authenticationEnterNewPasscode, passcodeSize: 6), - PasscodePane(title: Strings.authenticationReenterPasscode, passcodeSize: 6), - ] - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - panes.forEach { $0.codeInputView.delegate = self } - - // Don't show the keyboard or allow typing if we're locked out. Also display the error. - if authenticationInfo?.isLocked() ?? false { - displayLockoutError() - panes.first?.codeInputView.isUserInteractionEnabled = false - } else { - panes.first?.codeInputView.becomeFirstResponder() - } - } - - func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) { - switch currentPaneIndex { - case 0: - // Constraint: We need to make sure that the first passcode they've entered matches the one stored in the keychain - if code != authenticationInfo?.passcode { - panes[currentPaneIndex].shakePasscode() - failIncorrectPasscode(inputView) - return - } - oldPasscode = code - authenticationInfo?.recordValidation() - - // Clear out any previous errors if we are allowed to proceed - errorToast?.removeFromSuperview() - scrollToNextAndSelect() - case 1: - // Constraint: The new passcode cannot match their old passcode. - if oldPasscode == code { - failMustBeDifferent() - - // Scroll back and reset the input fields - resetAllInputFields() - return - } - newPasscode = code - errorToast?.removeFromSuperview() - scrollToNextAndSelect() - case 2: - if newPasscode != code { - failMismatchPasscode() - - // Scroll back and reset input fields - resetAllInputFields() - scrollToPreviousAndSelect() - newPasscode = nil - return - } - changePasscodeToCode(code) - dismissAnimated() - default: - break - } - } - - fileprivate func changePasscodeToCode(_ code: String) { - authenticationInfo?.updatePasscode(code) - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) - let notificationCenter = NotificationCenter.default - notificationCenter.post(name: .passcodeDidChange, object: nil) - } -} diff --git a/Client/Frontend/AuthenticationManager/PagingPasscodeViewController.swift b/Client/Frontend/AuthenticationManager/PagingPasscodeViewController.swift deleted file mode 100644 index 29891f4b464..00000000000 --- a/Client/Frontend/AuthenticationManager/PagingPasscodeViewController.swift +++ /dev/null @@ -1,93 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -private let PaneSwipeDuration: TimeInterval = 0.3 - -/// Base class for implementing a Passcode configuration screen with multiple 'panes'. -class PagingPasscodeViewController: BasePasscodeViewController { - var completion: (() -> Void)? - - fileprivate lazy var pager: UIScrollView = { - let scrollView = UIScrollView() - scrollView.isPagingEnabled = true - scrollView.isUserInteractionEnabled = false - scrollView.showsHorizontalScrollIndicator = false - scrollView.showsVerticalScrollIndicator = false - scrollView.contentInsetAdjustmentBehavior = .never - return scrollView - }() - - var panes = [PasscodePane]() - var currentPaneIndex: Int = 0 - - override func viewDidLoad() { - super.viewDidLoad() - view.addSubview(pager) - panes.forEach { pager.addSubview($0) } - pager.snp.makeConstraints { make in - make.bottom.left.right.equalTo(self.view) - make.top.equalTo(view.safeArea.top) - } - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - panes.enumerated().forEach { index, pane in - pane.frame = CGRect(origin: CGPoint(x: CGFloat(index) * pager.frame.width, y: 0), size: pager.frame.size) - } - pager.contentSize = CGSize(width: CGFloat(panes.count) * pager.frame.width, height: pager.frame.height) - scrollToPaneAtIndex(currentPaneIndex) - if self.authenticationInfo?.isLocked() ?? false { - return - } - panes[currentPaneIndex].codeInputView.becomeFirstResponder() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - self.view.endEditing(true) - completion?() - } -} - -extension PagingPasscodeViewController { - @discardableResult func scrollToNextAndSelect() -> PasscodePane { - scrollToNextPane() - panes[currentPaneIndex].codeInputView.becomeFirstResponder() - return panes[currentPaneIndex] - } - - @discardableResult func scrollToPreviousAndSelect() -> PasscodePane { - scrollToPreviousPane() - panes[currentPaneIndex].codeInputView.becomeFirstResponder() - return panes[currentPaneIndex] - } - - func resetAllInputFields() { - panes.forEach { $0.codeInputView.resetCode() } - } - - func scrollToNextPane() { - guard (currentPaneIndex + 1) < panes.count else { - return - } - currentPaneIndex += 1 - scrollToPaneAtIndex(currentPaneIndex) - } - - func scrollToPreviousPane() { - guard (currentPaneIndex - 1) >= 0 else { - return - } - currentPaneIndex -= 1 - scrollToPaneAtIndex(currentPaneIndex) - } - - func scrollToPaneAtIndex(_ index: Int) { - UIView.animate(withDuration: PaneSwipeDuration, delay: 0, options: [], animations: { - self.pager.contentOffset = CGPoint(x: CGFloat(self.currentPaneIndex) * self.pager.frame.width, y: 0) - }, completion: nil) - } -} diff --git a/Client/Frontend/AuthenticationManager/PasscodeEntryViewController.swift b/Client/Frontend/AuthenticationManager/PasscodeEntryViewController.swift deleted file mode 100644 index 0bdc30322b2..00000000000 --- a/Client/Frontend/AuthenticationManager/PasscodeEntryViewController.swift +++ /dev/null @@ -1,125 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import SnapKit -import Shared -import SwiftKeychainWrapper - -/// Delegate available for PasscodeEntryViewController consumers to be notified of the validation of a passcode. -@objc protocol PasscodeEntryDelegate: AnyObject { - func passcodeValidationDidSucceed() - @objc optional func userDidCancelValidation() -} - -/// Presented to the to user when asking for their passcode to validate entry into a part of the app. -class PasscodeEntryViewController: BasePasscodeViewController { - weak var delegate: PasscodeEntryDelegate? - fileprivate var passcodePane: PasscodePane - - private var passcodeCheckTimer: Timer? - - override init() { - let authInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() - passcodePane = PasscodePane(title: nil, passcodeSize: authInfo?.passcode?.count ?? 6) - - super.init() - - passcodePane.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(passcodeCheckSetup))) - passcodePane.isUserInteractionEnabled = true - passcodePane.accessibilityLabel = Strings.authenticationTouchForKeyboard - passcodePane.accessibilityTraits = [.allowsDirectInteraction] - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .secondaryBraveBackground - title = Strings.authenticationEnterPasscodeTitle - view.addSubview(passcodePane) - passcodePane.snp.makeConstraints { make in - make.bottom.left.right.equalTo(self.view) - make.top.equalTo(view.safeArea.top) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - passcodePane.codeInputView.delegate = self - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - passcodeCheckSetup() - } - - @objc func passcodeCheckSetup() { - - if authenticationInfo?.isLocked() == true { - disableUserInteraction() - setUpTimer() - } else { - errorToast?.removeFromSuperview() - passcodePane.codeInputView.becomeFirstResponder() - } - } - - private func disableUserInteraction() { - displayLockoutError() - // Don't show the keyboard or allow typing if we're locked out. - passcodePane.codeInputView.isUserInteractionEnabled = false - passcodePane.codeInputView.resignFirstResponder() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - self.view.endEditing(true) - } - - override func dismissAnimated() { - delegate?.userDidCancelValidation?() - super.dismissAnimated() - } - - private func setUpTimer() { - passcodeCheckTimer?.invalidate() - passcodeCheckTimer = nil - - guard let authInfo = authenticationInfo, let timeLeft = authInfo.lockoutTimeLeft else { - return - } - - passcodeCheckTimer = Timer.scheduledTimer(timeInterval: timeLeft, - target: self, - selector: #selector(passcodeCheckSetup), - userInfo: nil, repeats: false) - - disableUserInteraction() - } -} - -extension PasscodeEntryViewController: PasscodeInputViewDelegate { - func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) { - if let passcode = authenticationInfo?.passcode, passcode == code { - authenticationInfo?.recordValidation() - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) - delegate?.passcodeValidationDidSucceed() - } else { - passcodePane.shakePasscode() - failIncorrectPasscode(inputView) - - setUpTimer() - - // Store mutations on authentication info object - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) - } - - passcodePane.codeInputView.resetCode() - } -} - diff --git a/Client/Frontend/AuthenticationManager/PasscodeViews.swift b/Client/Frontend/AuthenticationManager/PasscodeViews.swift deleted file mode 100644 index fb264253a8a..00000000000 --- a/Client/Frontend/AuthenticationManager/PasscodeViews.swift +++ /dev/null @@ -1,217 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import SnapKit -import BraveShared - -private struct PasscodeUX { - static let titleVerticalSpacing: CGFloat = 32 - static let digitSize: CGFloat = 30 - static let topMargin: CGFloat = 80 - static let passcodeFieldSize: CGSize = CGSize(width: 160, height: 32) -} - -@objc protocol PasscodeInputViewDelegate: AnyObject { - func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) -} - -/// A custom, keyboard-able view that displays the blank/filled digits when entrering a passcode. -class PasscodeInputView: UIView, UIKeyInput { - weak var delegate: PasscodeInputViewDelegate? - - var digitFont: UIFont = UIConstants.passcodeEntryFont - - let blankCharacter: Character = "-" - - let filledCharacter: Character = "•" - - fileprivate let passcodeSize: Int - - fileprivate var inputtedCode: String = "" - - fileprivate var blankDigitString: NSAttributedString { - return NSAttributedString(string: "\(blankCharacter)", attributes: [NSAttributedString.Key.font: digitFont]) - } - - fileprivate var filledDigitString: NSAttributedString { - return NSAttributedString(string: "\(filledCharacter)", attributes: [NSAttributedString.Key.font: digitFont]) - } - - @objc var keyboardType: UIKeyboardType = .numberPad - - init(frame: CGRect, passcodeSize: Int) { - self.passcodeSize = passcodeSize - super.init(frame: frame) - isOpaque = false - backgroundColor = .secondaryBraveBackground - } - - convenience init(passcodeSize: Int) { - self.init(frame: .zero, passcodeSize: passcodeSize) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var canBecomeFirstResponder: Bool { - return true - } - - func resetCode() { - inputtedCode = "" - setNeedsDisplay() - } - - @objc var hasText: Bool { - return !inputtedCode.isEmpty - } - - @objc func insertText(_ text: String) { - guard inputtedCode.count < passcodeSize else { - return - } - - inputtedCode += text - setNeedsDisplay() - if inputtedCode.count == passcodeSize { - delegate?.passcodeInputView(self, didFinishEnteringCode: inputtedCode) - } - } - - // Required for implementing UIKeyInput - @objc func deleteBackward() { - guard !inputtedCode.isEmpty else { - return - } - - inputtedCode.remove(at: inputtedCode.index(before: inputtedCode.endIndex)) - setNeedsDisplay() - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - setNeedsDisplay() - } - - override func draw(_ rect: CGRect) { - let circleSize = CGSize(width: 14, height: 14) - - guard let context = UIGraphicsGetCurrentContext() else { return } - - context.setLineWidth(1) - context.setStrokeColor(UIColor.secondaryButtonTint.cgColor) - context.setFillColor(UIColor.secondaryButtonTint.cgColor) - - (0.. Int { - return 1 - } - - override func accessibilityElement(at index: Int) -> Any? { - switch index { - case 0: return titleLabel - default: return nil - } - } - - init(title: String? = nil, passcodeSize: Int = 4) { - codeInputView = PasscodeInputView(passcodeSize: passcodeSize) - super.init(frame: .zero) - - backgroundColor = .secondaryBraveBackground - - titleLabel.text = title - centerContainer.addSubview(titleLabel) - centerContainer.addSubview(codeInputView) - addSubview(centerContainer) - - centerContainer.snp.makeConstraints { make in - make.centerX.equalTo(self) - containerCenterConstraint = make.centerY.equalTo(self).constraint - } - - titleLabel.snp.makeConstraints { make in - make.centerX.equalTo(centerContainer) - make.top.equalTo(centerContainer) - make.bottom.equalTo(codeInputView.snp.top).offset(-PasscodeUX.titleVerticalSpacing) - } - - codeInputView.snp.makeConstraints { make in - codeViewCenterConstraint = make.centerX.equalTo(centerContainer).constraint - make.bottom.equalTo(centerContainer) - make.size.equalTo(PasscodeUX.passcodeFieldSize) - } - layoutIfNeeded() - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - } - - func shakePasscode() { - UIView.animate(withDuration: 0.1, animations: { - self.codeViewCenterConstraint?.update(offset: -10) - self.layoutIfNeeded() - }, completion: { complete in - UIView.animate(withDuration: 0.1, animations: { - self.codeViewCenterConstraint?.update(offset: 0) - self.layoutIfNeeded() - }) - }) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc func keyboardWillShow(_ sender: Notification) { - guard let keyboardFrame = (sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue else { - return - } - - UIView.animate(withDuration: 0.1, animations: { - self.containerCenterConstraint?.update(offset: -keyboardFrame.height/2) - self.layoutIfNeeded() - }) - } - - @objc func keyboardWillHide(_ sender: Notification) { - UIView.animate(withDuration: 0.1, animations: { - self.containerCenterConstraint?.update(offset: 0) - self.layoutIfNeeded() - }) - } -} diff --git a/Client/Frontend/AuthenticationManager/RemovePasscodeViewController.swift b/Client/Frontend/AuthenticationManager/RemovePasscodeViewController.swift deleted file mode 100644 index bc776888368..00000000000 --- a/Client/Frontend/AuthenticationManager/RemovePasscodeViewController.swift +++ /dev/null @@ -1,53 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import SwiftKeychainWrapper -import Shared - -/// Displayed to the user when removing a passcode. -class RemovePasscodeViewController: PagingPasscodeViewController, PasscodeInputViewDelegate { - override init() { - super.init() - self.title = Strings.authenticationTurnOffPasscode - self.panes = [ - PasscodePane(title: Strings.authenticationEnterPasscode, passcodeSize: authenticationInfo?.passcode?.count ?? 6), - ] - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - panes.forEach { $0.codeInputView.delegate = self } - - // Don't show the keyboard or allow typing if we're locked out. Also display the error. - if authenticationInfo?.isLocked() ?? false { - displayLockoutError() - panes.first?.codeInputView.isUserInteractionEnabled = false - } else { - panes.first?.codeInputView.becomeFirstResponder() - } - } - - func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) { - if code != authenticationInfo?.passcode { - panes[currentPaneIndex].shakePasscode() - failIncorrectPasscode(inputView) - return - } - - authenticationInfo?.recordValidation() - errorToast?.removeFromSuperview() - removePasscode() - dismissAnimated() - } - - fileprivate func removePasscode() { - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(nil) - NotificationCenter.default.post(name: .passcodeDidRemove, object: nil) - } -} diff --git a/Client/Frontend/AuthenticationManager/SetupPasscodeViewController.swift b/Client/Frontend/AuthenticationManager/SetupPasscodeViewController.swift deleted file mode 100644 index 036899cc425..00000000000 --- a/Client/Frontend/AuthenticationManager/SetupPasscodeViewController.swift +++ /dev/null @@ -1,66 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import Shared -import SwiftKeychainWrapper - -/// Displayed to the user when setting up a passcode. -class SetupPasscodeViewController: PagingPasscodeViewController, PasscodeInputViewDelegate { - fileprivate var confirmCode: String? - - override init() { - super.init() - self.title = Strings.authenticationSetPasscode - self.panes = [ - PasscodePane(title: Strings.authenticationEnterAPasscode, passcodeSize: 6), - PasscodePane(title: Strings.authenticationReenterPasscode, passcodeSize: 6), - ] - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - panes.forEach { $0.codeInputView.delegate = self } - - // Don't show the keyboard or allow typing if we're locked out. Also display the error. - if authenticationInfo?.isLocked() ?? false { - displayLockoutError() - panes.first?.codeInputView.isUserInteractionEnabled = false - } else { - panes.first?.codeInputView.becomeFirstResponder() - } - } - - func passcodeInputView(_ inputView: PasscodeInputView, didFinishEnteringCode code: String) { - switch currentPaneIndex { - case 0: - confirmCode = code - scrollToNextAndSelect() - case 1: - // Constraint: The first and confirmation codes must match. - if confirmCode != code { - failMismatchPasscode() - resetAllInputFields() - scrollToPreviousAndSelect() - confirmCode = nil - return - } - - createPasscodeWithCode(code) - dismissAnimated() - default: - break - } - } - - fileprivate func createPasscodeWithCode(_ code: String) { - KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(AuthenticationKeychainInfo(passcode: code)) - - NotificationCenter.default.post(name: .passcodeDidCreate, object: nil) - } -} diff --git a/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferButton.swift b/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferButton.swift index 689c171319f..baa883094e9 100644 --- a/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferButton.swift +++ b/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferButton.swift @@ -9,7 +9,7 @@ import Shared class LegacyWalletTransferButton: UIControl { - let dismissButton = Button(type: .system).then { + let dismissButton = BraveButton(type: .system).then { $0.setImage(UIImage(imageLiteralResourceName: "close-medium").template, for: .normal) $0.tintColor = .white $0.hitTestSlop = UIEdgeInsets(equalInset: -10) diff --git a/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferStatusButton.swift b/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferStatusButton.swift index d4f7755e4a0..18448390508 100644 --- a/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferStatusButton.swift +++ b/Client/Frontend/Brave Rewards/Common/LegacyWalletTransferStatusButton.swift @@ -9,7 +9,7 @@ import BraveUI class LegacyWalletTransferStatusButton: UIControl { - let dismissButton = Button(type: .system).then { + let dismissButton = BraveButton(type: .system).then { $0.setImage(UIImage(imageLiteralResourceName: "close-medium").template, for: .normal) $0.tintColor = .white $0.hitTestSlop = UIEdgeInsets(equalInset: -10) diff --git a/Client/Frontend/Brave Rewards/Panel/BraveRewardsPublisherView.swift b/Client/Frontend/Brave Rewards/Panel/BraveRewardsPublisherView.swift index 5cd0125b00d..5a354bd9925 100644 --- a/Client/Frontend/Brave Rewards/Panel/BraveRewardsPublisherView.swift +++ b/Client/Frontend/Brave Rewards/Panel/BraveRewardsPublisherView.swift @@ -40,7 +40,7 @@ class BraveRewardsPublisherView: UIStackView { $0.textColor = .braveLabel } - let learnMoreButton = Button(type: .system).then { + let learnMoreButton = BraveButton(type: .system).then { $0.setTitle(Strings.learnMore, for: .normal) $0.tintColor = .braveBlurple $0.isHidden = true diff --git a/Client/Frontend/BraveVPN/InstallVPNView.swift b/Client/Frontend/BraveVPN/InstallVPNView.swift index e0e7dbc6929..ec211446241 100644 --- a/Client/Frontend/BraveVPN/InstallVPNView.swift +++ b/Client/Frontend/BraveVPN/InstallVPNView.swift @@ -88,7 +88,7 @@ extension InstallVPNViewController { .forEach($0.addArrangedSubview(_:)) } - let installVPNButton = Button().then { + let installVPNButton = BraveButton().then { $0.setTitle(Strings.VPN.installProfileButtonText, for: .normal) $0.backgroundColor = .braveOrange $0.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold) @@ -103,7 +103,7 @@ extension InstallVPNViewController { $0.loaderView = LoaderView(size: .small) } - let contactSupportButton = Button(type: .system).then { + let contactSupportButton = BraveButton(type: .system).then { $0.setTitle(Strings.VPN.settingsContactSupport, for: .normal) $0.titleLabel?.font = .systemFont(ofSize: 16) $0.setTitleColor(.braveLabel, for: .normal) diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index 893d7d96163..69b2e3528c1 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -932,6 +932,18 @@ class BrowserViewController: UIViewController { } override func viewDidAppear(_ animated: Bool) { + if KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() != nil { + let controller = UIHostingController(rootView: PasscodeMigrationContainerView()) + controller.rootView.dismiss = { [unowned controller] enableBrowserLock in + KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(nil) + Preferences.Privacy.lockWithPasscode.value = enableBrowserLock + controller.dismiss(animated: true) + } + controller.modalPresentationStyle = .fullScreen + // No animation to ensure we don't leak the users tabs + present(controller, animated: false) + } + presentOnboardingIntro() { [weak self] in self?.shouldShowNTPEducation = true } diff --git a/Client/Frontend/Browser/BrowserViewController/OpenSearch/OpenSearchEngineButton.swift b/Client/Frontend/Browser/BrowserViewController/OpenSearch/OpenSearchEngineButton.swift index 812474082f1..02104ee5960 100644 --- a/Client/Frontend/Browser/BrowserViewController/OpenSearch/OpenSearchEngineButton.swift +++ b/Client/Frontend/Browser/BrowserViewController/OpenSearch/OpenSearchEngineButton.swift @@ -8,7 +8,7 @@ import BraveUI import SnapKit import BraveShared -class OpenSearchEngineButton: Button { +class OpenSearchEngineButton: BraveButton { // MARK: Action diff --git a/Client/Frontend/Browser/DefaultBrowserIntroCalloutViewController.swift b/Client/Frontend/Browser/DefaultBrowserIntroCalloutViewController.swift index b374c61fcba..e67cff4a678 100644 --- a/Client/Frontend/Browser/DefaultBrowserIntroCalloutViewController.swift +++ b/Client/Frontend/Browser/DefaultBrowserIntroCalloutViewController.swift @@ -12,7 +12,7 @@ private let log = Logger.browserLogger class DefaultBrowserIntroCalloutViewController: UIViewController { - private let openSettingsButton = Button(type: .system).then { + private let openSettingsButton = BraveButton(type: .system).then { $0.setTitle(Strings.DefaultBrowserCallout.introOpenSettingsButtonText, for: .normal) $0.backgroundColor = .braveOrange $0.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) diff --git a/Client/Frontend/Passcode/PasscodeMigrationView.swift b/Client/Frontend/Passcode/PasscodeMigrationView.swift new file mode 100644 index 00000000000..905614aa8c4 --- /dev/null +++ b/Client/Frontend/Passcode/PasscodeMigrationView.swift @@ -0,0 +1,131 @@ +// Copyright 2021 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import SwiftUI +import BraveUI +import LocalAuthentication +import Shared + +struct PasscodeMigrationContainerView: View { + var dismiss: ((_ enableBrowserLock: Bool) -> Void)? + @State private var context = LAContext() + + var body: some View { + let isUserAuthenticationAvailable = context + .canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) + let biometryType = context.biometryType + PasscodeMigrationView( + isUserAuthenticationAvailable: isUserAuthenticationAvailable, + availableBiometryMethod: biometryType + ) { + if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) { + // User must authenticate to continue + context.evaluatePolicy( + .deviceOwnerAuthentication, + localizedReason: Strings.authenticationLoginsTouchReason + ) { success, error in + if success { + DispatchQueue.main.async { + dismiss?(true) + } + } + } + } else { + dismiss?(false) + } + } + .onReceive( + NotificationCenter.default.publisher( + for: UIApplication.willEnterForegroundNotification + ) + ) { _ in + // Create a new `LAContext` context if the user leaves the app during this time + context = LAContext() + } + } +} + +/// Displays information regarding the migration from the old passcode system to the new +/// version which uses the users passcode instead of a custom one +private struct PasscodeMigrationView: View { + /// Whether or not the user has at the very least a PIN set on their device + var isUserAuthenticationAvailable: Bool + /// The type of biometry authentication type is available (effecting copy) + var availableBiometryMethod: LABiometryType + /// The action performed when the user taps the continue button + var continueAction: () -> Void + + var body: some View { + VStack(spacing: 60) { + Image("pin-migration-graphic") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 343.5, maxHeight: 317) + VStack(spacing: 12) { + Text(Strings.browserLockMigrationTitle) + .font(.headline) + .foregroundColor(Color(.braveLabel)) + Text(Strings.browserLockMigrationSubtitle) + .font(.subheadline) + .foregroundColor(Color(.braveLabel)) + if !isUserAuthenticationAvailable { + Text(settingsAuthenticationString) + .font(.subheadline) + .foregroundColor(Color(.secondaryBraveLabel)) + } + Button(action: continueAction) { + Text(Strings.browserLockMigrationContinueButtonTitle) + .frame(maxWidth: .infinity) + .padding(.vertical, 4) + } + .buttonStyle(BraveFilledButtonStyle(size: .normal)) + .padding(.top) + } + .fixedSize(horizontal: false, vertical: true) + } + .multilineTextAlignment(.center) + .padding(32) + .accessibilityEmbedInScrollView() + } + + private var settingsAuthenticationString: String { + let settingsPath: String = { + switch availableBiometryMethod { + case .faceID: + return Strings.authenticationFaceIDPasscodeSetting + case .touchID: + return Strings.authenticationTouchIDPasscodeSetting + case .none: + return Strings.authenticationPasscode + @unknown default: + return Strings.authenticationPasscode + } + }() + return String(format: Strings.browserLockMigrationNoPasscodeSetup, settingsPath) + } +} + +struct PasscodeMigrationView_Previews: PreviewProvider { + static var previews: some View { + Group { + ForEach([true, false], id: \.self) { isUserAuthenticationAvailable in + ForEach([LABiometryType.none, .faceID, .touchID], id: \.self) { availableBiometryMethod in + PasscodeMigrationView( + isUserAuthenticationAvailable: isUserAuthenticationAvailable, + availableBiometryMethod: availableBiometryMethod, + continueAction: { } + ) + } + } + .previewLayout(.sizeThatFits) + PasscodeMigrationView( + isUserAuthenticationAvailable: false, + availableBiometryMethod: .faceID, + continueAction: { } + ) + .environment(\.sizeCategory, .accessibilityExtraExtraLarge) + } + } +} diff --git a/Client/Frontend/Passcode/WindowProtection.swift b/Client/Frontend/Passcode/WindowProtection.swift new file mode 100644 index 00000000000..86dda899d72 --- /dev/null +++ b/Client/Frontend/Passcode/WindowProtection.swift @@ -0,0 +1,147 @@ +// Copyright 2021 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import Foundation +import UIKit +import LocalAuthentication +import Shared +import Combine +import BraveShared +import BraveUI +import SwiftKeychainWrapper + +private let log = Logger.browserLogger + +class WindowProtection { + + private class LockedViewController: UIViewController { + let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) + let lockImageView = UIImageView(image: UIImage(imageLiteralResourceName: "browser-lock-icon")) + let unlockButton = FilledActionButton(type: .system).then { + $0.setTitle("Unlock", for: .normal) + $0.titleLabel?.font = .preferredFont(forTextStyle: .headline) + $0.titleLabel?.adjustsFontForContentSizeCategory = true + $0.backgroundColor = .braveBlurple + $0.isHidden = true + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(backgroundView) + view.addSubview(lockImageView) + view.addSubview(unlockButton) + backgroundView.snp.makeConstraints { + $0.edges.equalTo(view) + } + lockImageView.snp.makeConstraints { + $0.center.equalTo(view) + } + unlockButton.snp.makeConstraints { + $0.leading.greaterThanOrEqualToSuperview().offset(20) + $0.trailing.lessThanOrEqualToSuperview().offset(20) + $0.centerX.equalToSuperview() + $0.height.greaterThanOrEqualTo(44) + $0.width.greaterThanOrEqualTo(230) + $0.top.equalTo(lockImageView.snp.bottom).offset(60) + } + } + } + + private let lockedViewController = LockedViewController() + + private var cancellables: Set = [] + private var protectedWindow: UIWindow + private var passcodeWindow: UIWindow + private var context = LAContext() + + private var isVisible: Bool = false { + didSet { + passcodeWindow.isHidden = !isVisible + if isVisible { + passcodeWindow.makeKeyAndVisible() + } else { + protectedWindow.makeKeyAndVisible() + } + } + } + + init?(window: UIWindow) { + guard let scene = window.windowScene else { return nil } + protectedWindow = window + + passcodeWindow = UIWindow(windowScene: scene) + passcodeWindow.windowLevel = .init(UIWindow.Level.statusBar.rawValue + 1) + passcodeWindow.rootViewController = lockedViewController + + lockedViewController.unlockButton.addTarget(self, action: #selector(tappedUnlock), for: .touchUpInside) + + NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) + .sink(receiveValue: { [weak self] _ in + guard let self = self else { return } + // Update visibility when entering background + self.isVisible = Preferences.Privacy.lockWithPasscode.value && + self.context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) + }) + .store(in: &cancellables) + + NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) + .merge(with: NotificationCenter.default.publisher(for: UIApplication.didFinishLaunchingNotification)) + .sink(receiveValue: { [weak self] _ in + guard let self = self else { return } + self.context = LAContext() // Reset context for new session + self.updateVisibleStatusForForeground() + }) + .store(in: &cancellables) + } + + @available(*, unavailable) + required init(coder: NSCoder) { + fatalError() + } + + @objc private func tappedUnlock() { + presentLocalAuthentication() + } + + private func updateVisibleStatusForForeground() { + var error: NSError? + if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error), + (error as? LAError)?.code == .passcodeNotSet { + // User no longer has a passcode set so we can't evaluate the auth policy + isVisible = false + return + } + let isLocked = Preferences.Privacy.lockWithPasscode.value + isVisible = isLocked + if isLocked { + presentLocalAuthentication() + } + } + + private func presentLocalAuthentication() { + if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) { + return + } + lockedViewController.unlockButton.isHidden = true + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: Strings.authenticationLoginsTouchReason) { success, error in + DispatchQueue.main.async { [self] in + if success { + UIView.animate(withDuration: 0.1, animations: { + lockedViewController.view.alpha = 0.0 + }, completion: { _ in + isVisible = false + lockedViewController.view.alpha = 1.0 + }) + } else { + lockedViewController.unlockButton.isHidden = false + if let error = error { + log.error("Failed to unlock browser using local authentication: \(error.localizedDescription)") + } + } + } + } + } +} diff --git a/Client/Frontend/Settings/PasscodeSettingsViewController.swift b/Client/Frontend/Settings/PasscodeSettingsViewController.swift index d848c63d018..78d35044afe 100644 --- a/Client/Frontend/Settings/PasscodeSettingsViewController.swift +++ b/Client/Frontend/Settings/PasscodeSettingsViewController.swift @@ -8,6 +8,7 @@ import Static import SwiftKeychainWrapper import Shared import BraveUI +import BraveShared class PasscodeSettingsViewController: TableViewController { @@ -23,7 +24,7 @@ class PasscodeSettingsViewController: TableViewController { override func viewDidLoad() { super.viewDidLoad() - updateTitleForTouchIDState() + navigationItem.title = titleForTouchIDState tableView.accessibilityIdentifier = "PasscodeSettingsViewController.tableView" tableView.separatorColor = .braveSeparator @@ -35,68 +36,11 @@ class PasscodeSettingsViewController: TableViewController { } func reloadSections() { - if let authenticationInfo = KeychainWrapper.sharedAppContainerKeychain.authenticationInfo() { - // Passcode - dataSource.sections = [ - Section(rows: [ - Row(text: Strings.authenticationTurnOffPasscode, - selection: { [unowned self] in - let setupPasscodeController = RemovePasscodeViewController() - setupPasscodeController.completion = self.reloadSections - let container = UINavigationController(rootViewController: setupPasscodeController) - self.present(container, animated: true) - }, - cellClass: ButtonCell.self - ), - Row(text: Strings.authenticationChangePasscode, - selection: { [unowned self] in - let changePasscodeController = ChangePasscodeViewController() - changePasscodeController.completion = self.reloadSections - let container = UINavigationController(rootViewController: changePasscodeController) - self.present(container, animated: true) - } - ) - ]) - ] - - var otherSection = Section(rows: [ - // TODO: Need localized copy of this - Row(text: "Require Passcode Immediately", - accessory: .switchToggle( - value: authenticationInfo.isPasscodeRequiredImmediately, - // TODO: Make a new option "infinite" instead of using time intervals - { on in authenticationInfo.isPasscodeRequiredImmediately = on; KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) } - ) - ) - ]) - - if deviceBiometryType != .none { - let title = deviceBiometryType == .faceID ? Strings.useFaceID : Strings.useTouchID - otherSection.rows.append(Row(text: title, accessory: .switchToggle(value: authenticationInfo.useTouchID, { authenticationInfo.useTouchID = $0; KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(authenticationInfo) }))) - } - - dataSource.sections.append(otherSection) - } else { - // No Passcode - dataSource.sections = [ - Section(rows: [ - Row(text: Strings.authenticationTurnOnPasscode, - selection: { [unowned self] in - let setupPasscodeController = SetupPasscodeViewController() - setupPasscodeController.completion = self.reloadSections - let container = UINavigationController(rootViewController: setupPasscodeController) - self.present(container, animated: true) - }, - cellClass: ButtonCell.self - ), - Row(text: Strings.authenticationChangePasscode, cellClass: DisabledCell.self) - ]), - Section(rows: [ - // TODO: Need localized copy of this - Row(text: "Require Passcode Immediately", accessory: .switchToggle(value: false, { _ in }), cellClass: DisabledCell.self), - ]) - ] - } + dataSource.sections = [ + .init( + rows: [ .boolRow(title: titleForTouchIDState, option: Preferences.Privacy.lockWithPasscode) ] + ) + ] } private var deviceBiometryType: LABiometryType { @@ -107,16 +51,16 @@ class PasscodeSettingsViewController: TableViewController { return .none } - func updateTitleForTouchIDState() { + var titleForTouchIDState: String { switch deviceBiometryType { case .faceID: - navigationItem.title = Strings.authenticationFaceIDPasscodeSetting + return Strings.authenticationFaceIDPasscodeSetting case .touchID: - navigationItem.title = Strings.authenticationTouchIDPasscodeSetting + return Strings.authenticationTouchIDPasscodeSetting case .none: - navigationItem.title = Strings.authenticationPasscode + return Strings.authenticationPasscode @unknown default: - assertionFailure() + return Strings.authenticationPasscode } } } diff --git a/Client/Frontend/Settings/SettingsViewController.swift b/Client/Frontend/Settings/SettingsViewController.swift index 7148f31da8e..964f6ccac5c 100644 --- a/Client/Frontend/Settings/SettingsViewController.swift +++ b/Client/Frontend/Settings/SettingsViewController.swift @@ -401,27 +401,10 @@ class SettingsViewController: TableViewController { } private lazy var securitySection: Static.Section = { - let passcodeTitle: String = { - let localAuthContext = LAContext() - if localAuthContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { - let title: String - if localAuthContext.biometryType == .faceID { - return Strings.authenticationFaceIDPasscodeSetting - } else { - return Strings.authenticationTouchIDPasscodeSetting - } - } else { - return Strings.authenticationPasscode - } - }() - - return Static.Section( + return Section( header: .title(Strings.security), rows: [ - Row(text: passcodeTitle, selection: { [unowned self] in - let passcodeSettings = PasscodeSettingsViewController() - self.navigationController?.pushViewController(passcodeSettings, animated: true) - }, image: #imageLiteral(resourceName: "settings-passcode").template, accessory: .disclosureIndicator, cellClass: MultilineValue1Cell.self), + .boolRow(title: Strings.browserLock, detailText: Strings.browserLockDescription, option: Preferences.Privacy.lockWithPasscode, image: #imageLiteral(resourceName: "settings-passcode").template), .boolRow(title: Strings.saveLogins, option: Preferences.General.saveLogins, image: #imageLiteral(resourceName: "settings-save-logins").template) ] ) diff --git a/Client/Frontend/Shields/SimpleShieldsView.swift b/Client/Frontend/Shields/SimpleShieldsView.swift index fb3e6a59e2b..157dae8c3ff 100644 --- a/Client/Frontend/Shields/SimpleShieldsView.swift +++ b/Client/Frontend/Shields/SimpleShieldsView.swift @@ -94,7 +94,7 @@ class SimpleShieldsView: UIView { $0.textColor = .braveLabel } - let infoButton = Button().then { + let infoButton = BraveButton().then { $0.setImage(#imageLiteral(resourceName: "shields-help").template, for: .normal) $0.hitTestSlop = UX.hitBoxEdgeInsets $0.imageEdgeInsets = .zero @@ -107,7 +107,7 @@ class SimpleShieldsView: UIView { $0.tintColor = .bravePrimary } - let shareButton = Button().then { + let shareButton = BraveButton().then { $0.setImage(#imageLiteral(resourceName: "shields-share").withRenderingMode(.alwaysTemplate), for: .normal) $0.hitTestSlop = UX.hitBoxEdgeInsets $0.imageEdgeInsets = .zero diff --git a/Client/Info.plist b/Client/Info.plist index 92a31fe180f..792ed2c8bc3 100644 --- a/Client/Info.plist +++ b/Client/Info.plist @@ -86,7 +86,7 @@ NSCameraUsageDescription This lets you take and upload photos. NSFaceIDUsageDescription - Brave requires Face ID to access your saved logins. + This authenticates your access to Brave NSLocationWhenInUseUsageDescription Websites you visit may request your location. NSMicrophoneUsageDescription