Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -60,7 +60,7 @@ class SettingsViewTests: BitwardenTestCase {
func test_defaultSaveOptionChanged_updateValue() throws {
processor.state.shouldShowDefaultSaveOption = true
processor.state.defaultSaveOption = .none
let menuField = try subject.inspect().find(settingsMenuField: Localizations.defaultSaveOption)
let menuField = try subject.inspect().find(bitwardenMenuField: Localizations.defaultSaveOption)
try menuField.select(newValue: DefaultSaveOption.saveToBitwarden)
XCTAssertEqual(processor.dispatchedActions.last, .defaultSaveChanged(.saveToBitwarden))
}
Expand Down Expand Up @@ -102,7 +102,7 @@ class SettingsViewTests: BitwardenTestCase {
func test_sessionTimeoutValue_updateValue() throws {
processor.state.biometricUnlockStatus = .available(.faceID, enabled: false, hasValidIntegrity: true)
processor.state.sessionTimeoutValue = .never
let menuField = try subject.inspect().find(settingsMenuField: Localizations.sessionTimeout)
let menuField = try subject.inspect().find(bitwardenMenuField: Localizations.sessionTimeout)
try menuField.select(newValue: SessionTimeoutValue.fifteenMinutes)

waitFor(!processor.effects.isEmpty)
Expand Down
201 changes: 99 additions & 102 deletions AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,59 +27,54 @@ struct SettingsView: View {
// MARK: View

var body: some View {
settingsItems
.scrollView()
.navigationBar(title: Localizations.settings, titleDisplayMode: titleDisplayMode)
.toast(store.binding(
get: \.toast,
send: SettingsAction.toastShown,
))
.onChange(of: store.state.url) { newValue in
guard let url = newValue else { return }
openURL(url)
store.send(.clearURL)
}
.task {
await store.perform(.loadData)
}
VStack(spacing: 16) {
securitySection
dataSection
appearanceSection
helpSection
aboutSection
copyrightNotice
}
.scrollView()
.navigationBar(title: Localizations.settings, titleDisplayMode: titleDisplayMode)
.toast(store.binding(
get: \.toast,
send: SettingsAction.toastShown,
))
.onChange(of: store.state.url) { newValue in
guard let url = newValue else { return }
openURL(url)
store.send(.clearURL)
}
.task {
await store.perform(.loadData)
}
}

// MARK: Private views

/// A view for the user's biometrics setting
///
@ViewBuilder private var biometricsSetting: some View {
switch store.state.biometricUnlockStatus {
case let .available(type, enabled: enabled, _):
SectionView(Localizations.security) {
VStack(spacing: 8) {
biometricUnlockToggle(enabled: enabled, type: type)
SettingsMenuField(
title: Localizations.sessionTimeout,
options: SessionTimeoutValue.allCases,
hasDivider: false,
accessibilityIdentifier: "VaultTimeoutChooser",
selectionAccessibilityID: "SessionTimeoutStatusLabel",
selection: store.bindingAsync(
get: \.sessionTimeoutValue,
perform: SettingsEffect.sessionTimeoutValueChanged,
),
)
.clipShape(RoundedRectangle(cornerRadius: 10))
/// The about section containing privacy policy and version information.
@ViewBuilder private var aboutSection: some View {
SectionView(Localizations.about) {
ContentBlock(dividerLeadingPadding: 16) {
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)

SettingsListItem(store.state.version) {
store.send(.versionTapped)
} trailingContent: {
SharedAsset.Icons.copy24.swiftUIImage
.imageStyle(.rowIcon)
}
}
.padding(.bottom, 32)
default:
EmptyView()
}
}

/// The chevron shown in the settings list item.
private var chevron: some View {
Image(asset: SharedAsset.Icons.chevronRight16)
.resizable()
.scaledFrame(width: 12, height: 12)
.foregroundColor(Color(asset: Asset.Colors.textSecondary))
/// The appearance section containing language and theme settings.
@ViewBuilder private var appearanceSection: some View {
SectionView(Localizations.appearance, contentSpacing: 8) {
language
theme
}
}

/// The copyright notice.
Expand All @@ -91,31 +86,9 @@ struct SettingsView: View {
.frame(maxWidth: .infinity)
}

/// The language picker view
private var language: some View {
Button {
store.send(.languageTapped)
} label: {
BitwardenField(
title: Localizations.language,
footer: Localizations.languageChangeRequiresAppRestart,
) {
Text(store.state.currentLanguage.title)
.styleGuide(.body)
.foregroundColor(Color(asset: SharedAsset.Colors.textPrimary))
.multilineTextAlignment(.leading)
} accessoryContent: {
SharedAsset.Icons.chevronDown24.swiftUIImage
.imageStyle(.rowIcon)
}
}
}

/// The settings items.
private var settingsItems: some View {
VStack(spacing: 0) {
biometricsSetting

/// The data section containing import, export, backup, and sync options.
@ViewBuilder private var dataSection: some View {
SectionView(Localizations.data) {
ContentBlock(dividerLeadingPadding: 16) {
SettingsListItem(Localizations.import) {
store.send(.importItemsTapped)
Expand All @@ -135,46 +108,47 @@ struct SettingsView: View {
defaultSaveOption
}
}
.padding(.bottom, 32)

SectionView(Localizations.appearance) {
language
theme
}
.padding(.bottom, 32)
}
}

/// The help section containing tutorial and help center links.
@ViewBuilder private var helpSection: some View {
SectionView(Localizations.help) {
ContentBlock(dividerLeadingPadding: 16) {
SettingsListItem(Localizations.launchTutorial) {
store.send(.tutorialTapped)
}

externalLinkRow(Localizations.bitwardenHelpCenter, action: .helpCenterTapped)
}
.padding(.bottom, 32)

ContentBlock(dividerLeadingPadding: 16) {
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)
}
}

SettingsListItem(store.state.version) {
store.send(.versionTapped)
} trailingContent: {
SharedAsset.Icons.copy24.swiftUIImage
.imageStyle(.rowIcon)
}
/// The language picker view.
private var language: some View {
Button {
store.send(.languageTapped)
} label: {
BitwardenField(
title: Localizations.language,
footer: Localizations.languageChangeRequiresAppRestart,
) {
Text(store.state.currentLanguage.title)
.styleGuide(.body)
.foregroundColor(Color(asset: SharedAsset.Colors.textPrimary))
.multilineTextAlignment(.leading)
} accessoryContent: {
SharedAsset.Icons.chevronDown24.swiftUIImage
.imageStyle(.rowIcon)
}
.padding(.bottom, 16)

copyrightNotice
}
.cornerRadius(10)
}

/// The application's default save option picker view
/// The application's default save option picker view.
@ViewBuilder private var defaultSaveOption: some View {
SettingsMenuField(
BitwardenMenuField(
title: Localizations.defaultSaveOption,
options: DefaultSaveOption.allCases,
hasDivider: false,
selection: store.binding(
get: \.defaultSaveOption,
send: SettingsAction.defaultSaveChanged,
Expand All @@ -183,7 +157,31 @@ struct SettingsView: View {
.accessibilityIdentifier("DefaultSaveOptionChooser")
}

/// The application's color theme picker view
/// The security section containing biometric unlock and session timeout settings.
@ViewBuilder private var securitySection: some View {
switch store.state.biometricUnlockStatus {
case let .available(type, enabled: enabled, _):
SectionView(Localizations.security) {
ContentBlock {
biometricUnlockToggle(enabled: enabled, type: type)

BitwardenMenuField(
title: Localizations.sessionTimeout,
accessibilityIdentifier: "VaultTimeoutChooser",
options: SessionTimeoutValue.allCases,
selection: store.bindingAsync(
get: \.sessionTimeoutValue,
perform: SettingsEffect.sessionTimeoutValueChanged,
),
)
}
}
default:
EmptyView()
}
}

/// The application's color theme picker view.
private var theme: some View {
BitwardenMenuField(
title: Localizations.theme,
Expand All @@ -202,16 +200,15 @@ struct SettingsView: View {
@ViewBuilder
private func biometricUnlockToggle(enabled: Bool, type: BiometricAuthenticationType) -> some View {
let toggleText = biometricsToggleText(type)
Toggle(isOn: store.bindingAsync(
get: { _ in enabled },
perform: SettingsEffect.toggleUnlockWithBiometrics,
)) {
Text(toggleText)
}
.padding(.trailing, 3)
BitwardenToggle(
toggleText,
isOn: store.bindingAsync(
get: { _ in enabled },
perform: SettingsEffect.toggleUnlockWithBiometrics,
),
)
.accessibilityIdentifier("UnlockWithBiometricsSwitch")
.accessibilityLabel(toggleText)
.toggleStyle(.bitwarden)
}

private func biometricsToggleText(_ biometryType: BiometricAuthenticationType) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ public struct BitwardenMenuField<
)
.foregroundColor(isEnabled
? SharedAsset.Colors.textSecondary.swiftUIColor
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor)
.onSizeChanged { size in
titleWidth = size.width
}
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor
)
.onSizeChanged { size in
titleWidth = size.width
}
}

Text(selection.localizedName)
Expand All @@ -150,20 +151,21 @@ public struct BitwardenMenuField<
.styleGuide(.body)
.foregroundColor(isEnabled
? SharedAsset.Colors.textPrimary.swiftUIColor
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor)
.frame(minHeight: 64)
.accessibilityIdentifier(accessibilityIdentifier ?? "")
.overlay {
if let titleAccessoryContent {
titleAccessoryContent
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading,
)
.offset(x: titleWidth + 4, y: 12)
}
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor
)
.frame(minHeight: 64)
.accessibilityIdentifier(accessibilityIdentifier ?? "")
.overlay {
if let titleAccessoryContent {
titleAccessoryContent
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading,
)
.offset(x: titleWidth + 4, y: 12)
}
}
}

// MARK: Initialization
Expand Down

This file was deleted.

Loading
Loading