Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PM-10278 PM-10279 PM-10806 - Setup autofill screen for new onboarding flow #979

Merged
merged 18 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
600fb91
PM-10278 PM-10279 PM-10806 - Password Autofill View for onboarding flow
phil-livefront Sep 26, 2024
96f8a77
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Sep 26, 2024
4e50dcf
fix compiler error after changing variable name and added miss comment
phil-livefront Sep 26, 2024
01d4a4d
fix tests
phil-livefront Sep 27, 2024
ffdc336
fix horizontal padding of subtitle
phil-livefront Sep 27, 2024
50ac0c3
add unit tests in AuthCoordinatorTests and AuthRouterTests
phil-livefront Sep 27, 2024
ee6ee70
attempting to fix tests on CI
phil-livefront Sep 27, 2024
107190d
Fix issue with auto fill screen not dismissing when navigating back tโ€ฆ
phil-livefront Sep 30, 2024
9485a93
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Sep 30, 2024
cd462b8
using ASSettingsHelper for iOS 17+
phil-livefront Sep 30, 2024
837d17e
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Sep 30, 2024
b9516c1
fix tests
phil-livefront Sep 30, 2024
a4f1091
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Sep 30, 2024
5621900
fix testing issue that was causing an infinite loop
phil-livefront Oct 1, 2024
b819541
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Oct 1, 2024
a9f687a
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Oct 1, 2024
9ddb5a8
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Oct 1, 2024
2aa2947
Merge branch 'main' into phil/PM-10278-PM-10279-PM-10806-autofill-view
phil-livefront Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class MockStateService: StateService { // swiftlint:disable:this type_body_lengt
var setAccountHasBeenUnlockedInteractivelyHasBeenCalled = false // swiftlint:disable:this identifier_name
// swiftlint:disable:next identifier_name
var setAccountHasBeenUnlockedInteractivelyResult: Result<Void, Error> = .success(())
var setAccountSetupAutofillCalled = false
var setBiometricAuthenticationEnabledResult: Result<Void, Error> = .success(())
var setBiometricIntegrityStateError: Error?
var settingsBadgeSubject = CurrentValueSubject<SettingsBadgeState, Never>(
Expand Down Expand Up @@ -360,6 +361,7 @@ class MockStateService: StateService { // swiftlint:disable:this type_body_lengt
}

func setAccountSetupAutofill(_ autofillSetup: AccountSetupProgress?, userId: String?) async throws {
setAccountSetupAutofillCalled = true
let userId = try unwrapUserId(userId)
if let accountSetupAutofillError {
throw accountSetupAutofillError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ enum ExternalLinksConstants {
/// A link to the app review page within the app store.
static let appReview = URL(string: "https://itunes.apple.com/us/app/id1137397744?action=write-review")

/// A link to the auto fill help page.
static let autofillHelp = URL(string: "https://bitwarden.com/help/auto-fill-ios/#keyboard-auto-fill")!

/// A link to Bitwarden's help page for learning more about the account fingerprint phrase.
static let fingerprintPhrase = URL(string: "https://bitwarden.com/help/fingerprint-phrase/")!

Expand All @@ -25,6 +28,9 @@ enum ExternalLinksConstants {
/// A link to Bitwarden's general help and feedback page.
static let helpAndFeedback = URL(string: "https://bitwarden.com/help/")!

/// A link to the password options within the passwords section of the settings menu.
static let passwordOptions = URL(string: "App-prefs:PASSWORDS&path=PASSWORD_OPTIONS")!

/// A markdown link to Bitwarden's privacy policy.
static let privacyPolicy = URL(string: "https://bitwarden.com/privacy/")!

Expand Down
18 changes: 16 additions & 2 deletions BitwardenShared/UI/Auth/AuthCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class AuthCoordinator: NSObject, // swiftlint:disable:this type_body_lengt
& HasAuthAPIService
& HasAuthRepository
& HasAuthService
& HasAutofillCredentialService
& HasBiometricsRepository
& HasCaptchaService
& HasClientService
Expand All @@ -49,6 +50,7 @@ final class AuthCoordinator: NSObject, // swiftlint:disable:this type_body_lengt
& HasEnvironmentService
& HasErrorReporter
& HasGeneratorRepository
& HasNotificationCenterService
& HasNFCReaderService
& HasOrganizationAPIService
& HasPolicyService
Expand Down Expand Up @@ -118,8 +120,7 @@ final class AuthCoordinator: NSObject, // swiftlint:disable:this type_body_lengt
func navigate(to route: AuthRoute, context: AnyObject?) { // swiftlint:disable:this function_body_length
switch route {
case .autofillSetup:
// TODO: PM-10278 Add autofill setup screen
break
showAutoFillSetup()
case let .captcha(url, callbackUrlScheme):
showCaptcha(
url: url,
Expand Down Expand Up @@ -265,6 +266,19 @@ final class AuthCoordinator: NSObject, // swiftlint:disable:this type_body_lengt
return try await services.authRepository.setActiveAccount(userId: alternate.profile.userId)
}

/// Shows the password autofill screen.
///
private func showAutoFillSetup() {
let processor = PasswordAutoFillProcessor(
coordinator: asAnyCoordinator(),
services: services,
state: .init(mode: .onboarding)
)

let view = PasswordAutoFillView(store: Store(processor: processor))
stackNavigator?.replace(view)
}

/// Shows the captcha screen.
///
/// - Parameters:
Expand Down
8 changes: 8 additions & 0 deletions BitwardenShared/UI/Auth/AuthCoordinatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ class AuthCoordinatorTests: BitwardenTestCase { // swiftlint:disable:this type_b

// MARK: Tests

/// `navigate(to:)` with `.autofillSetup` pushes the password autofill view onto the navigation stack.
@MainActor
func test_navigate_autofillSetup() {
subject.navigate(to: .autofillSetup)
XCTAssertEqual(stackNavigator.actions.last?.type, .replaced)
XCTAssertTrue(stackNavigator.actions.last?.view is PasswordAutoFillView)
}

/// `navigate(to:)` with `.complete` notifies the delegate that auth has completed.
@MainActor
func test_navigate_complete() {
Expand Down
11 changes: 5 additions & 6 deletions BitwardenShared/UI/Auth/AuthRouterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,11 @@ final class AuthRouterTests: BitwardenTestCase { // swiftlint:disable:this type_
/// `handleAndRoute(_:)` redirects `.didCompleteAuth` to `.autofillSetup` if the user still
/// needs to set up autofill.
func test_handleAndRoute_didCompleteAuth_incompleteAutofill() async {
// TODO: PM-10278 Add autofill setup screen
// authRepository.activeAccount = .fixture()
// stateService.activeAccount = .fixture()
// stateService.accountSetupAutofill["1"] = .incomplete
// let route = await subject.handleAndRoute(.didCompleteAuth)
// XCTAssertEqual(route, .autofillSetup)
authRepository.activeAccount = .fixture()
stateService.activeAccount = .fixture()
stateService.accountSetupAutofill["1"] = .incomplete
let route = await subject.handleAndRoute(.didCompleteAuth)
XCTAssertEqual(route, .autofillSetup)
}

/// `handleAndRoute(_:)` redirects `.didCompleteAuth` to `.vaultUnlockSetup` if the user still
Expand Down
19 changes: 19 additions & 0 deletions BitwardenShared/UI/Auth/Extensions/Alert+Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,25 @@ extension Alert {
)
}

/// An alert confirming that the user wants to finish setting up autofill later in settings.
///
/// - Parameter action: The action taken when the user taps on Confirm to finish setting up
/// autofill later in settings.
/// - Returns: An alert confirming that the user wants to finish setting up autofill let in settings.
///
static func setUpAutoFillLater(action: @escaping () async -> Void) -> Alert {
matt-livefront marked this conversation as resolved.
Show resolved Hide resolved
Alert(
title: Localizations.turnOnAutoFillLaterQuestion,
message: Localizations.youCanReturnToCompleteThisStepAnytimeInSettings,
alertActions: [
AlertAction(title: Localizations.cancel, style: .cancel),
AlertAction(title: Localizations.confirm, style: .default) { _ in
await action()
},
]
)
}

/// An alert confirming that the user wants to finish setting up their vault unlock methods
/// later in settings.
///
Expand Down
21 changes: 21 additions & 0 deletions BitwardenShared/UI/Auth/Extensions/AlertAuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,27 @@ class AlertAuthTests: BitwardenTestCase {
await fulfillment(of: [expectation], timeout: 3)
}

/// `setUpAutoFillLater(action:)` builds an `Alert` for setting up autofill later.
func test_setUpAutoFillLater() async throws {
var actionCalled = false
let subject = Alert.setUpAutoFillLater {
actionCalled = true
}

XCTAssertEqual(subject.title, Localizations.turnOnAutoFillLaterQuestion)
XCTAssertEqual(subject.message, Localizations.youCanReturnToCompleteThisStepAnytimeInSettings)
XCTAssertEqual(subject.preferredStyle, .alert)
XCTAssertEqual(subject.alertActions.count, 2)
XCTAssertEqual(subject.alertActions[0].title, Localizations.cancel)
XCTAssertEqual(subject.alertActions[1].title, Localizations.confirm)

try await subject.tapAction(title: Localizations.cancel)
XCTAssertFalse(actionCalled)

try await subject.tapAction(title: Localizations.confirm)
XCTAssertTrue(actionCalled)
}

/// `setUpUnlockMethodLater(action:)` builds an `Alert` confirming the user wants to set up
/// their unlock methods later.
func test_setUpUnlockMethodLater() async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ extension AuthRouter {
return .updateMasterPassword
} else if await (try? services.stateService.getAccountSetupVaultUnlock()) == .incomplete {
return .vaultUnlockSetup
// TODO: PM-10278 Add autofill setup screen
// } else if await (try? services.stateService.getAccountSetupAutofill()) == .incomplete {
// return .autofillSetup
} else if await (try? services.stateService.getAccountSetupAutofill()) == .incomplete {
return .autofillSetup
} else {
await setCarouselShownIfEnabled()
return .complete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class VaultUnlockProcessorTests: BitwardenTestCase { // swiftlint:disable:this t
authRepository = nil
biometricsRepository = nil
coordinator = nil
errorReporter = nil
stateService = nil
subject = nil
}

Expand All @@ -64,11 +66,10 @@ class VaultUnlockProcessorTests: BitwardenTestCase { // swiftlint:disable:this t
XCTAssertEqual(subject.state.biometricUnlockStatus, .notAvailable)
}

/// `perform(.appeared)` with a biometrics status error yields `BiometricUnlockStatus.unavailable`.
/// `perform(.appeared)` with a biometrics status yields the expected status.
@MainActor
func test_perform_appeared_biometricUnlockStatus_success() async {
stateService.activeAccount = .fixture()
struct FetchError: Error {}
let expectedStatus = BiometricsUnlockStatus.available(.touchID, enabled: true, hasValidIntegrity: false)
biometricsRepository.biometricUnlockStatus = .success(expectedStatus)
await subject.perform(.appeared)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"data" : [
{
"filename" : "autofill_ios_dark.gif",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "autofill_ios_dark_placeholder.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"data" : [
{
"filename" : "autofill_ios_light.gif",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "autofill_ios_light_placeholder.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,15 @@
"Confirm" = "Confirm";
"ErrorConnectingWithTheDuoServiceUseADifferentTwoStepLoginMethodOrContactDuoForAssistance" = "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance.";
"ThePreAuthUrlsCouldNotBeLoadedToStartTheAccountCreation" = "The Pre Auth Urls could not be loaded to start the account creation.";
"FromYourDeviceSettingsToggleOnAutoFillPasswordsAndPasskeys" = "From your device settings, **toggle on autofill Passwords and Passkeys.**";
"ToggleOffICloudToMakeBitwardenYourDefaultAutoFillSource" = "**Toggle off iCloud Keychain** to make Bitwarden your default autofill source.";
"ToggleOnBitwardenToUseYourSavedPasswordsToLogIntoYourAccounts" = "**Toggle on Bitwarden** to use your saved passwords to log into your accounts.";
"TurnOnAutoFill"= "Turn on autofill";
"UseAutoFillToLogIntoYourAccountsWithASingleTap" = "Use autofill to log into your accounts with a single tap.";
"NeedHelpCheckOutAutofillHelp" = "Need help? Check out **[autofill help](%1$@)**";
"TurnOnLater" = "Turn on later";
"TurnOnAutoFillLaterQuestion" = "Turn on autofill later?";
"YouCanReturnToCompleteThisStepAnytimeInSettings" = "You can return to complete this step anytime in Settings.";
Comment on lines +987 to +995
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿค” Could you confirm with product on the use of autofill string instead of auto-fill? I see there are a lot of resources using auto-fill so wanted to check if this changed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out. I just confirmed with the livefront designer that "autofill" is correct but lmk if this is something we should escalate up to the BW team?

"ContinueToBitwarden" = "Continue to Bitwarden";
"BackToSettings" = "Back to settings";
"YoureAllSet" = "You're all set!";
Expand Down
45 changes: 45 additions & 0 deletions BitwardenShared/UI/Platform/Application/Views/GifView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import SwiftUI
import WebKit

// MARK: - GifView

/// A view that displays a GIF using a `WKWebView`.
/// This view handles the rendering of the GIF asset
/// and ensures that the background is transparent.
///
struct GifView: UIViewRepresentable {
// MARK: Properties

/// The `DataAsset` that contains the GIF data to be rendered.
private let gif: DataAsset

// MARK: Initialization

/// Initializes the `GifView` with a specific `DataAsset` for the GIF.
///
/// - Parameter gif: The data asset for the GIF.
///
init(gif: DataAsset) {
self.gif = gif
}

func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.isOpaque = false
webView.backgroundColor = .clear
webView.scrollView.backgroundColor = .clear

webView.load(
gif.data.data,
mimeType: "image/gif",
characterEncodingName: "UTF-8",
baseURL: Bundle.main.bundleURL
)

return webView
}

func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.reload()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// MARK: - PasswordAutoFillEffect

/// Effects handled by the `PasswordAutoFillProcessor`.
///
enum PasswordAutoFillEffect: Equatable {
/// The password autofill view appeared on screen.
case appeared

/// Check the autofill status when the view enters the foreground.
case checkAutofillOnForeground

/// The turn on later button was tapped.
case turnAutoFillOnLaterButtonTapped
}
Loading
Loading