Skip to content

Commit 73fc2d8

Browse files
[PM-26063] Update Authenticator's settings view to latest designs (#2113)
1 parent f9ae28d commit 73fc2d8

File tree

6 files changed

+120
-363
lines changed

6 files changed

+120
-363
lines changed

AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView+ViewInspectorTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class SettingsViewTests: BitwardenTestCase {
6060
func test_defaultSaveOptionChanged_updateValue() throws {
6161
processor.state.shouldShowDefaultSaveOption = true
6262
processor.state.defaultSaveOption = .none
63-
let menuField = try subject.inspect().find(settingsMenuField: Localizations.defaultSaveOption)
63+
let menuField = try subject.inspect().find(bitwardenMenuField: Localizations.defaultSaveOption)
6464
try menuField.select(newValue: DefaultSaveOption.saveToBitwarden)
6565
XCTAssertEqual(processor.dispatchedActions.last, .defaultSaveChanged(.saveToBitwarden))
6666
}
@@ -102,7 +102,7 @@ class SettingsViewTests: BitwardenTestCase {
102102
func test_sessionTimeoutValue_updateValue() throws {
103103
processor.state.biometricUnlockStatus = .available(.faceID, enabled: false, hasValidIntegrity: true)
104104
processor.state.sessionTimeoutValue = .never
105-
let menuField = try subject.inspect().find(settingsMenuField: Localizations.sessionTimeout)
105+
let menuField = try subject.inspect().find(bitwardenMenuField: Localizations.sessionTimeout)
106106
try menuField.select(newValue: SessionTimeoutValue.fifteenMinutes)
107107

108108
waitFor(!processor.effects.isEmpty)

AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView.swift

Lines changed: 99 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -27,59 +27,54 @@ struct SettingsView: View {
2727
// MARK: View
2828

2929
var body: some View {
30-
settingsItems
31-
.scrollView()
32-
.navigationBar(title: Localizations.settings, titleDisplayMode: titleDisplayMode)
33-
.toast(store.binding(
34-
get: \.toast,
35-
send: SettingsAction.toastShown,
36-
))
37-
.onChange(of: store.state.url) { newValue in
38-
guard let url = newValue else { return }
39-
openURL(url)
40-
store.send(.clearURL)
41-
}
42-
.task {
43-
await store.perform(.loadData)
44-
}
30+
VStack(spacing: 16) {
31+
securitySection
32+
dataSection
33+
appearanceSection
34+
helpSection
35+
aboutSection
36+
copyrightNotice
37+
}
38+
.scrollView()
39+
.navigationBar(title: Localizations.settings, titleDisplayMode: titleDisplayMode)
40+
.toast(store.binding(
41+
get: \.toast,
42+
send: SettingsAction.toastShown,
43+
))
44+
.onChange(of: store.state.url) { newValue in
45+
guard let url = newValue else { return }
46+
openURL(url)
47+
store.send(.clearURL)
48+
}
49+
.task {
50+
await store.perform(.loadData)
51+
}
4552
}
4653

4754
// MARK: Private views
4855

49-
/// A view for the user's biometrics setting
50-
///
51-
@ViewBuilder private var biometricsSetting: some View {
52-
switch store.state.biometricUnlockStatus {
53-
case let .available(type, enabled: enabled, _):
54-
SectionView(Localizations.security) {
55-
VStack(spacing: 8) {
56-
biometricUnlockToggle(enabled: enabled, type: type)
57-
SettingsMenuField(
58-
title: Localizations.sessionTimeout,
59-
options: SessionTimeoutValue.allCases,
60-
hasDivider: false,
61-
accessibilityIdentifier: "VaultTimeoutChooser",
62-
selectionAccessibilityID: "SessionTimeoutStatusLabel",
63-
selection: store.bindingAsync(
64-
get: \.sessionTimeoutValue,
65-
perform: SettingsEffect.sessionTimeoutValueChanged,
66-
),
67-
)
68-
.clipShape(RoundedRectangle(cornerRadius: 10))
56+
/// The about section containing privacy policy and version information.
57+
@ViewBuilder private var aboutSection: some View {
58+
SectionView(Localizations.about) {
59+
ContentBlock(dividerLeadingPadding: 16) {
60+
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)
61+
62+
SettingsListItem(store.state.version) {
63+
store.send(.versionTapped)
64+
} trailingContent: {
65+
SharedAsset.Icons.copy24.swiftUIImage
66+
.imageStyle(.rowIcon)
6967
}
7068
}
71-
.padding(.bottom, 32)
72-
default:
73-
EmptyView()
7469
}
7570
}
7671

77-
/// The chevron shown in the settings list item.
78-
private var chevron: some View {
79-
Image(asset: SharedAsset.Icons.chevronRight16)
80-
.resizable()
81-
.scaledFrame(width: 12, height: 12)
82-
.foregroundColor(Color(asset: Asset.Colors.textSecondary))
72+
/// The appearance section containing language and theme settings.
73+
@ViewBuilder private var appearanceSection: some View {
74+
SectionView(Localizations.appearance, contentSpacing: 8) {
75+
language
76+
theme
77+
}
8378
}
8479

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

94-
/// The language picker view
95-
private var language: some View {
96-
Button {
97-
store.send(.languageTapped)
98-
} label: {
99-
BitwardenField(
100-
title: Localizations.language,
101-
footer: Localizations.languageChangeRequiresAppRestart,
102-
) {
103-
Text(store.state.currentLanguage.title)
104-
.styleGuide(.body)
105-
.foregroundColor(Color(asset: SharedAsset.Colors.textPrimary))
106-
.multilineTextAlignment(.leading)
107-
} accessoryContent: {
108-
SharedAsset.Icons.chevronDown24.swiftUIImage
109-
.imageStyle(.rowIcon)
110-
}
111-
}
112-
}
113-
114-
/// The settings items.
115-
private var settingsItems: some View {
116-
VStack(spacing: 0) {
117-
biometricsSetting
118-
89+
/// The data section containing import, export, backup, and sync options.
90+
@ViewBuilder private var dataSection: some View {
91+
SectionView(Localizations.data) {
11992
ContentBlock(dividerLeadingPadding: 16) {
12093
SettingsListItem(Localizations.import) {
12194
store.send(.importItemsTapped)
@@ -135,46 +108,47 @@ struct SettingsView: View {
135108
defaultSaveOption
136109
}
137110
}
138-
.padding(.bottom, 32)
139-
140-
SectionView(Localizations.appearance) {
141-
language
142-
theme
143-
}
144-
.padding(.bottom, 32)
111+
}
112+
}
145113

114+
/// The help section containing tutorial and help center links.
115+
@ViewBuilder private var helpSection: some View {
116+
SectionView(Localizations.help) {
146117
ContentBlock(dividerLeadingPadding: 16) {
147118
SettingsListItem(Localizations.launchTutorial) {
148119
store.send(.tutorialTapped)
149120
}
150121

151122
externalLinkRow(Localizations.bitwardenHelpCenter, action: .helpCenterTapped)
152123
}
153-
.padding(.bottom, 32)
154-
155-
ContentBlock(dividerLeadingPadding: 16) {
156-
externalLinkRow(Localizations.privacyPolicy, action: .privacyPolicyTapped)
124+
}
125+
}
157126

158-
SettingsListItem(store.state.version) {
159-
store.send(.versionTapped)
160-
} trailingContent: {
161-
SharedAsset.Icons.copy24.swiftUIImage
162-
.imageStyle(.rowIcon)
163-
}
127+
/// The language picker view.
128+
private var language: some View {
129+
Button {
130+
store.send(.languageTapped)
131+
} label: {
132+
BitwardenField(
133+
title: Localizations.language,
134+
footer: Localizations.languageChangeRequiresAppRestart,
135+
) {
136+
Text(store.state.currentLanguage.title)
137+
.styleGuide(.body)
138+
.foregroundColor(Color(asset: SharedAsset.Colors.textPrimary))
139+
.multilineTextAlignment(.leading)
140+
} accessoryContent: {
141+
SharedAsset.Icons.chevronDown24.swiftUIImage
142+
.imageStyle(.rowIcon)
164143
}
165-
.padding(.bottom, 16)
166-
167-
copyrightNotice
168144
}
169-
.cornerRadius(10)
170145
}
171146

172-
/// The application's default save option picker view
147+
/// The application's default save option picker view.
173148
@ViewBuilder private var defaultSaveOption: some View {
174-
SettingsMenuField(
149+
BitwardenMenuField(
175150
title: Localizations.defaultSaveOption,
176151
options: DefaultSaveOption.allCases,
177-
hasDivider: false,
178152
selection: store.binding(
179153
get: \.defaultSaveOption,
180154
send: SettingsAction.defaultSaveChanged,
@@ -183,7 +157,31 @@ struct SettingsView: View {
183157
.accessibilityIdentifier("DefaultSaveOptionChooser")
184158
}
185159

186-
/// The application's color theme picker view
160+
/// The security section containing biometric unlock and session timeout settings.
161+
@ViewBuilder private var securitySection: some View {
162+
switch store.state.biometricUnlockStatus {
163+
case let .available(type, enabled: enabled, _):
164+
SectionView(Localizations.security) {
165+
ContentBlock {
166+
biometricUnlockToggle(enabled: enabled, type: type)
167+
168+
BitwardenMenuField(
169+
title: Localizations.sessionTimeout,
170+
accessibilityIdentifier: "VaultTimeoutChooser",
171+
options: SessionTimeoutValue.allCases,
172+
selection: store.bindingAsync(
173+
get: \.sessionTimeoutValue,
174+
perform: SettingsEffect.sessionTimeoutValueChanged,
175+
),
176+
)
177+
}
178+
}
179+
default:
180+
EmptyView()
181+
}
182+
}
183+
184+
/// The application's color theme picker view.
187185
private var theme: some View {
188186
BitwardenMenuField(
189187
title: Localizations.theme,
@@ -202,16 +200,15 @@ struct SettingsView: View {
202200
@ViewBuilder
203201
private func biometricUnlockToggle(enabled: Bool, type: BiometricAuthenticationType) -> some View {
204202
let toggleText = biometricsToggleText(type)
205-
Toggle(isOn: store.bindingAsync(
206-
get: { _ in enabled },
207-
perform: SettingsEffect.toggleUnlockWithBiometrics,
208-
)) {
209-
Text(toggleText)
210-
}
211-
.padding(.trailing, 3)
203+
BitwardenToggle(
204+
toggleText,
205+
isOn: store.bindingAsync(
206+
get: { _ in enabled },
207+
perform: SettingsEffect.toggleUnlockWithBiometrics,
208+
),
209+
)
212210
.accessibilityIdentifier("UnlockWithBiometricsSwitch")
213211
.accessibilityLabel(toggleText)
214-
.toggleStyle(.bitwarden)
215212
}
216213

217214
private func biometricsToggleText(_ biometryType: BiometricAuthenticationType) -> String {

BitwardenKit/UI/Platform/Application/Views/BitwardenMenuField.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ public struct BitwardenMenuField<
120120
)
121121
.foregroundColor(isEnabled
122122
? SharedAsset.Colors.textSecondary.swiftUIColor
123-
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor)
124-
.onSizeChanged { size in
125-
titleWidth = size.width
126-
}
123+
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor
124+
)
125+
.onSizeChanged { size in
126+
titleWidth = size.width
127+
}
127128
}
128129

129130
Text(selection.localizedName)
@@ -150,20 +151,21 @@ public struct BitwardenMenuField<
150151
.styleGuide(.body)
151152
.foregroundColor(isEnabled
152153
? SharedAsset.Colors.textPrimary.swiftUIColor
153-
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor)
154-
.frame(minHeight: 64)
155-
.accessibilityIdentifier(accessibilityIdentifier ?? "")
156-
.overlay {
157-
if let titleAccessoryContent {
158-
titleAccessoryContent
159-
.frame(
160-
maxWidth: .infinity,
161-
maxHeight: .infinity,
162-
alignment: .topLeading,
163-
)
164-
.offset(x: titleWidth + 4, y: 12)
165-
}
154+
: SharedAsset.Colors.buttonFilledDisabledForeground.swiftUIColor
155+
)
156+
.frame(minHeight: 64)
157+
.accessibilityIdentifier(accessibilityIdentifier ?? "")
158+
.overlay {
159+
if let titleAccessoryContent {
160+
titleAccessoryContent
161+
.frame(
162+
maxWidth: .infinity,
163+
maxHeight: .infinity,
164+
alignment: .topLeading,
165+
)
166+
.offset(x: titleWidth + 4, y: 12)
166167
}
168+
}
167169
}
168170

169171
// MARK: Initialization

BitwardenKit/UI/Platform/Application/Views/SettingsMenuField+ViewInspectorTests.swift

Lines changed: 0 additions & 60 deletions
This file was deleted.

0 commit comments

Comments
 (0)