Skip to content

Commit 02dd8c6

Browse files
PM-16860: Disable segmented control selected action for selected segment (#1250)
1 parent f50ef9b commit 02dd8c6

File tree

3 files changed

+58
-26
lines changed

3 files changed

+58
-26
lines changed

BitwardenShared/UI/Platform/Application/Views/BitwardenSegmentedControl.swift

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@ struct BitwardenSegmentedControl<T: Menuable & Identifiable>: View {
2626
var body: some View {
2727
HStack(spacing: 0) {
2828
ForEach(selections) { selection in
29+
let isSelected = self.selection.id == selection.id
2930
Button {
31+
// Don't update the selection if this segment is already selected.
32+
guard !isSelected else { return }
3033
self.selection = selection
3134
} label: {
32-
segmentView(
33-
title: selection.localizedName,
34-
isDisabled: isSelectionDisabled(selection),
35-
isSelected: selection == self.selection
36-
)
37-
.matchedGeometryEffect(id: selection, in: segmentedControl)
35+
Text(selection.localizedName)
36+
.styleGuide(.callout, weight: .semibold)
3837
}
38+
.accessibility(if: isSelected, addTraits: .isSelected)
3939
.accessibilityIdentifier(selection.accessibilityId)
40+
.buttonStyle(SegmentButtonStyle(isSelected: isSelected))
4041
.disabled(isSelectionDisabled(selection))
42+
.matchedGeometryEffect(id: selection, in: segmentedControl)
4143
}
4244
}
4345
.background(
@@ -70,36 +72,38 @@ struct BitwardenSegmentedControl<T: Menuable & Identifiable>: View {
7072
_selection = selection
7173
self.selections = selections
7274
}
75+
}
7376

74-
// MARK: Private
77+
// MARK: - SegmentButtonStyle
7578

76-
/// Returns the foreground color for a segment.
77-
///
78-
/// - Parameters:
79-
/// - isDisabled: Whether the segment is disabled.
80-
/// - isSelected: Whether the segment is selected.
81-
///
82-
private func segmentForegroundColor(isDisabled: Bool, isSelected: Bool) -> Color {
83-
guard !isDisabled else { return Asset.Colors.buttonFilledDisabledForeground.swiftUIColor }
79+
/// A `ButtonStyle` for displaying a segment within the `BitwardenSegmentedControl`.
80+
///
81+
private struct SegmentButtonStyle: ButtonStyle {
82+
// MARK: Properties
83+
84+
@Environment(\.isEnabled) var isEnabled: Bool
85+
86+
/// Whether the segment is selected.
87+
let isSelected: Bool
88+
89+
/// The color of the foreground elements in the button.
90+
var foregroundColor: Color {
91+
guard isEnabled else { return Asset.Colors.buttonFilledDisabledForeground.swiftUIColor }
8492
return isSelected ?
8593
Asset.Colors.textInteraction.swiftUIColor :
8694
Asset.Colors.textSecondary.swiftUIColor
8795
}
8896

89-
/// Returns a view for a single segment within the segmented control.
90-
///
91-
/// - Parameters:
92-
/// - title: The title of the segment.
93-
/// - isDisabled: Whether the segment is disabled.
94-
/// - isSelected: Whether the segment is selected.
95-
///
96-
private func segmentView(title: String, isDisabled: Bool, isSelected: Bool) -> some View {
97-
Text(title)
98-
.styleGuide(.callout, weight: .semibold)
97+
// MARK: ButtonStyle
98+
99+
func makeBody(configuration: Configuration) -> some View {
100+
configuration.label
99101
.frame(maxWidth: .infinity)
100-
.foregroundStyle(segmentForegroundColor(isDisabled: isDisabled, isSelected: isSelected))
102+
.foregroundStyle(foregroundColor)
101103
.padding(.vertical, 8)
102104
.dynamicTypeSize(...DynamicTypeSize.xxxLarge)
105+
.opacity(configuration.isPressed && !isSelected ? 0.5 : 1)
106+
.contentShape(Capsule())
103107
}
104108
}
105109

BitwardenShared/UI/Platform/Application/Views/BitwardenSegmentedControlTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,31 @@ class BitwardenSegmentedControlTests: BitwardenTestCase {
3939
try buttonThree.tap()
4040
XCTAssertEqual(selection, .three)
4141
}
42+
43+
/// Tapping on the selected segment doesn't update the selection binding.
44+
func test_selectionCurrentTapped() throws {
45+
var selection = Segment.one
46+
var selectionChangedHistory = [Segment]()
47+
let subject = BitwardenSegmentedControl(
48+
selection: Binding(
49+
get: { selection },
50+
set: { newValue in
51+
selection = newValue
52+
selectionChangedHistory.append(newValue)
53+
}
54+
),
55+
selections: Segment.allCases
56+
)
57+
58+
try subject.inspect().find(button: "One").tap()
59+
XCTAssertEqual(selection, .one)
60+
XCTAssertTrue(selectionChangedHistory.isEmpty)
61+
62+
try subject.inspect().find(button: "Two").tap()
63+
XCTAssertEqual(selection, .two)
64+
XCTAssertEqual(selectionChangedHistory, [.two])
65+
66+
try subject.inspect().find(button: "Two").tap()
67+
XCTAssertEqual(selectionChangedHistory, [.two])
68+
}
4269
}

BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemViewTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class AddEditSendItemViewTests: BitwardenTestCase { // swiftlint:disable:this ty
176176
/// Tapping on text button in the segmented control sends the `.typeChanged` action.
177177
@MainActor
178178
func test_segmentedControl_textTap() throws {
179+
processor.state.type = .file
179180
let button = try subject.inspect().find(button: Localizations.text)
180181
try button.tap()
181182
XCTAssertEqual(processor.dispatchedActions, [.typeChanged(.text)])

0 commit comments

Comments
 (0)