Files
ios/BitwardenShared/UI/Auth/UpdateMasterPassword/UpdateMasterPasswordView.swift
2025-10-06 15:18:35 -05:00

148 lines
5.9 KiB
Swift

import BitwardenKit
import BitwardenResources
import SwiftUI
// MARK: - UpdateMasterPasswordView
/// A view that forces the user to update their master password.
///
struct UpdateMasterPasswordView: View {
// MARK: Properties
/// The `Store` for this view.
@ObservedObject var store: Store<UpdateMasterPasswordState, UpdateMasterPasswordAction, UpdateMasterPasswordEffect>
var body: some View {
VStack(spacing: 24) {
if store.state.forcePasswordResetReason != nil {
InfoContainer(store.state.updateMasterPasswordWarning)
if let policy = store.state.masterPasswordPolicy,
policy.isInEffect,
let policySummary = policy.policySummary {
InfoContainer(policySummary)
}
}
ContentBlock {
if store.state.requireCurrentPassword {
BitwardenTextField(
title: Localizations.currentMasterPasswordRequired,
text: store.binding(
get: \.currentMasterPassword,
send: UpdateMasterPasswordAction.currentMasterPasswordChanged,
),
accessibilityIdentifier: "MasterPasswordField",
passwordVisibilityAccessibilityId: "MasterPasswordVisibilityToggle",
isPasswordVisible: store.binding(
get: \.isCurrentMasterPasswordRevealed,
send: UpdateMasterPasswordAction.revealCurrentMasterPasswordFieldPressed,
),
)
.textFieldConfiguration(.password)
}
BitwardenTextField(
title: Localizations.newMasterPasswordRequired,
text: store.binding(
get: \.masterPassword,
send: UpdateMasterPasswordAction.masterPasswordChanged,
),
accessibilityIdentifier: "NewPasswordField",
passwordVisibilityAccessibilityId: "NewPasswordVisibilityToggle",
isPasswordVisible: store.binding(
get: \.isMasterPasswordRevealed,
send: UpdateMasterPasswordAction.revealMasterPasswordFieldPressed,
),
footerContent: {
PasswordStrengthIndicator(
passwordStrengthScore: store.state.passwordStrengthScore,
passwordTextCount: store.state.masterPassword.count,
requiredTextCount: store.state.requiredPasswordCount,
)
.padding(.vertical, 12)
},
)
.textFieldConfiguration(.password)
BitwardenTextField(
title: Localizations.retypeNewMasterPasswordRequired,
text: store.binding(
get: \.masterPasswordRetype,
send: UpdateMasterPasswordAction.masterPasswordRetypeChanged,
),
accessibilityIdentifier: "RetypePasswordField",
passwordVisibilityAccessibilityId: "RetypePasswordVisibilityToggle",
isPasswordVisible: store.binding(
get: \.isMasterPasswordRetypeRevealed,
send: UpdateMasterPasswordAction.revealMasterPasswordRetypeFieldPressed,
),
)
.textFieldConfiguration(.password)
BitwardenTextField(
title: Localizations.newMasterPasswordHint,
text: store.binding(
get: \.masterPasswordHint,
send: UpdateMasterPasswordAction.masterPasswordHintChanged,
),
accessibilityIdentifier: "MasterPasswordHintLabel",
footerContent: {
VStack(alignment: .leading, spacing: 4) {
Text(Localizations.bitwardenCannotResetALostOrForgottenMasterPassword)
.foregroundColor(Color(asset: SharedAsset.Colors.textSecondary))
.styleGuide(.footnote)
Button {
store.send(.preventAccountLockTapped)
} label: {
Text(Localizations.learnAboutWaysToPreventAccountLockout)
}
.buttonStyle(.bitwardenBorderless)
.accessibilityIdentifier("PreventAccountLockButton")
}
.padding(.vertical, 12)
},
)
}
}
.scrollView()
.background(SharedAsset.Colors.backgroundPrimary.swiftUIColor)
.navigationTitle(Localizations.updateMasterPassword)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
toolbarButton(Localizations.logOut) {
await store.perform(.logoutTapped)
}
}
ToolbarItemGroup(placement: .topBarTrailing) {
primaryActionToolbarButton(Localizations.save) {
await store.perform(.saveTapped)
}
.accessibilityIdentifier("SaveButton")
}
}
.task {
await store.perform(.appeared)
}
}
}
// MARK: Previews
#if DEBUG
#Preview {
NavigationView {
UpdateMasterPasswordView(
store: Store(
processor: StateProcessor(
state: UpdateMasterPasswordState(),
),
),
)
}
}
#endif