[BITAU-126] Add option in Settings to Open Bitwarden App (#170)

This commit is contained in:
Brant DeBow 2024-10-22 17:09:00 -04:00 committed by GitHub
parent f37414702d
commit 23150d48e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 70 additions and 3 deletions

View File

@ -37,7 +37,7 @@ enum ExternalLinksConstants {
static let passwordManagerScheme = URL(string: "bitwarden://")!
/// A deeplink used by the password manager app to open the options menu.
static let passwordManagerSettings = URL(string: "bitwarden://sync_authenticator?options=true")!
static let passwordManagerSettings = URL(string: "bitwarden://settings/account_security")!
/// A markdown link to Bitwarden's privacy policy.
static let privacyPolicy = URL(string: "https://bitwarden.com/privacy/")!

View File

@ -25,6 +25,9 @@ enum SettingsAction: Equatable {
/// The privacy policy button was tapped.
case privacyPolicyTapped
/// The sync with bitwarden app button was tapped.
case syncWithBitwardenAppTapped
/// The toast was shown or hidden.
case toastShown(Toast?)

View File

@ -8,6 +8,7 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
// MARK: Types
typealias Services = HasBiometricsRepository
& HasConfigService
& HasErrorReporter
& HasExportItemsService
& HasPasteboardService
@ -76,6 +77,8 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
coordinator.showAlert(.privacyPolicyAlert {
self.state.url = ExternalLinksConstants.privacyPolicy
})
case .syncWithBitwardenAppTapped:
state.url = ExternalLinksConstants.passwordManagerSettings
case let .toastShown(newValue):
state.toast = newValue
case .tutorialTapped:
@ -114,6 +117,7 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
state.currentLanguage = services.stateService.appLanguage
state.appTheme = await services.stateService.getAppTheme()
state.biometricUnlockStatus = await loadBiometricUnlockPreference()
state.shouldShowSyncButton = await services.configService.getFeatureFlag(.enablePasswordManagerSync)
}
/// Sets the user's biometric auth

View File

@ -5,6 +5,7 @@ import XCTest
class SettingsProcessorTests: AuthenticatorTestCase {
// MARK: Properties
var configService: MockConfigService!
var coordinator: MockCoordinator<SettingsRoute, SettingsEvent>!
var subject: SettingsProcessor!
@ -13,10 +14,13 @@ class SettingsProcessorTests: AuthenticatorTestCase {
override func setUp() {
super.setUp()
configService = MockConfigService()
coordinator = MockCoordinator()
subject = SettingsProcessor(
coordinator: coordinator.asAnyCoordinator(),
services: ServiceContainer.withMocks(),
services: ServiceContainer.withMocks(
configService: configService
),
state: SettingsState()
)
}
@ -30,6 +34,24 @@ class SettingsProcessorTests: AuthenticatorTestCase {
// MARK: Tests
/// Performing `.loadData` with the password manager sync disabled sets
/// `state.shouldShowSyncButton` to `false`.
func test_perform_loadData_syncDisabled() async throws {
configService.featureFlagsBool[.enablePasswordManagerSync] = false
await subject.perform(.loadData)
XCTAssertFalse(subject.state.shouldShowSyncButton)
}
/// Performing `.loadData` with the password manager sync enabled sets
/// `state.shouldShowSyncButton` to `true`.
func test_perform_loadData_syncEnabled() async throws {
configService.featureFlagsBool[.enablePasswordManagerSync] = true
await subject.perform(.loadData)
XCTAssertTrue(subject.state.shouldShowSyncButton)
}
/// Receiving `.backupTapped` shows an alert for the backup information.
func test_receive_backupTapped() async throws {
subject.receive(.backupTapped)
@ -45,4 +67,12 @@ class SettingsProcessorTests: AuthenticatorTestCase {
XCTAssertEqual(coordinator.routes.last, .exportItems)
}
/// Receiving `.syncWithBitwardenAppTapped` adds the Password Manager settings URL to the state to
/// navigate the user to the PM app's settings.
func test_receive_syncWithBitwardenAppTapped() {
subject.receive(.syncWithBitwardenAppTapped)
XCTAssertEqual(subject.state.url, ExternalLinksConstants.passwordManagerSettings)
}
}

View File

@ -17,6 +17,10 @@ struct SettingsState: Equatable {
/// The current language selection.
var currentLanguage: LanguageOption = .default
/// A flag to indicate if we should show the "Sync with the Bitwarden app" button
/// Defaults to false, which indicates we should not show the button.
var shouldShowSyncButton = false
/// A toast message to show in the view.
var toast: Toast?

View File

@ -102,9 +102,17 @@ struct SettingsView: View {
store.send(.exportItemsTapped)
}
SettingsListItem(Localizations.backup, hasDivider: false) {
SettingsListItem(Localizations.backup, hasDivider: store.state.shouldShowSyncButton) {
store.send(.backupTapped)
}
if store.state.shouldShowSyncButton {
externalLinkRow(
Localizations.syncWithTheBitwardenApp,
action: .syncWithBitwardenAppTapped,
hasDivider: false
)
}
}
.cornerRadius(10)
}

View File

@ -70,6 +70,14 @@ class SettingsViewTests: AuthenticatorTestCase {
XCTAssertEqual(processor.dispatchedActions.last, .privacyPolicyTapped)
}
/// Tapping the sync with Bitwarden app button dispatches the `.syncWithBitwardenAppTapped` action.
func test_syncWithBitwardenButton_tap() throws {
processor.state.shouldShowSyncButton = true
let button = try subject.inspect().find(button: Localizations.syncWithTheBitwardenApp)
try button.tap()
XCTAssertEqual(processor.dispatchedActions.last, .syncWithBitwardenAppTapped)
}
/// Tapping the tutorial button dispatches the `.tutorialTapped` action.
func test_tutorialButton_tap() throws {
let button = try subject.inspect().find(button: Localizations.launchTutorial)
@ -91,4 +99,13 @@ class SettingsViewTests: AuthenticatorTestCase {
as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5]
)
}
/// Tests the view renders correctly with the `shouldShowSyncButton` set to `true`.
func test_viewRenderWithSyncRow() {
processor.state.shouldShowSyncButton = true
assertSnapshots(
of: subject,
as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5]
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -13,6 +13,7 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator {
typealias Services = HasBiometricsRepository
& HasCameraService
& HasConfigService
& HasErrorReporter
& HasExportItemsService
& HasImportItemsService