8000 PM-15891: Updated slider by ezimet-livefront · Pull Request #1219 · bitwarden/ios · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

PM-15891: Updated slider #1219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 20, 2024
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
10000
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xDC",
"green" : "0x5D",
"red" : "0x17"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xAB",
"red" : "0x65"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x33",
"green" : "0x27",
"red" : "0x20"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xEF",
"green" : "0xE9",
"red" : "0xE6"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x6A",
"green" : "0x5B",
"red" : "0x52"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
137 changes: 137 additions & 0 deletions BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import SwiftUI

/// A custom slider view that allows for custom styling and accessibility.
///
struct BitwardenSlider: View {
// MARK: Private Properties

/// The size of the thumb view.
@SwiftUI.State private var thumbSize: CGSize = .zero

// MARK: Properties

/// A closure containing the action to take when the slider begins or ends editing.
let onEditingChanged: (Bool) -> Void

/// The range of allowable values for the slider.
let range: ClosedRange<Double>

/// The distance between each valid value.
let step: Double

/// The current value of the slider.
@Binding var value: Double

// MARK: Default Colors

/// The color of the slider track.
var trackColor: Color = Asset.Colors.sliderTrack.swiftUIColor

/// The color of the filled portion of the slider track.
var filledTrackColor: Color = Asset.Colors.sliderFilled.swiftUIColor

var body: some View {
GeometryReader { geometry in
let thumbPosition = thumbPosition(in: geometry.size)
ZStack {
Rectangle()
.fill(trackColor)
.frame(height: 4)
.cornerRadius(2)
.overlay(
Rectangle()
.fill(filledTrackColor)
.frame(width: thumbPosition, height: 4)
.cornerRadius(2),
alignment: .leading
)

Circle()
.fill(Asset.Colors.sliderFilled.swiftUIColor)
.frame(width: 18, height: 18)
.overlay(
Circle()
.stroke(Asset.Colors.sliderThumbBorder.swiftUIColor, lineWidth: 2)
)
.onSizeChanged { size in
thumbSize = size
}
.position(x: max(0, thumbPosition), y: geometry.size.height / 2)
.gesture(
DragGesture()
.onChanged { value in
self.value = valueFrom(position: value.location.x, in: geometry.size)
onEditingChanged(true)
}

Check warning on line 65 in BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift

View check run for this annotation

Codecov / codecov/patch

BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift#L63-L65

Added lines #L63 - L65 were not covered by tests
.onEnded { _ in
onEditingChanged(false)
}

Check warning on line 68 in BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift

View check run for this annotation

Codecov / codecov/patch

BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift#L67-L68

Added lines #L67 - L68 were not covered by tests
)
}
}
.frame(height: 44)
.accessibilityElement()
.accessibilityValue(Text("\(Int(value))"))
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
let newValue = min(value + step, range.upperBound)
value = newValue
onEditingChanged(true)
onEditingChanged(false)
case .decrement:
let newValue = max(value - step, range.lowerBound)
value = newValue
onEditingChanged(true)
onEditingChanged(false)
default:
break
}
}

Check warning on line 90 in BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift

View check run for this annotation

Codecov / codecov/patch

BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift#L76-L90

Added lines #L76 - L90 were not covered by tests
}

// MARK: Initialization

/// Initialize a `BitwardenSlider`.
///
/// - Parameters:
/// - value: The current value of the slider.
/// - range: The range of allowable values for the slider.
/// - step: The distance between each valid value.
/// - onEditingChanged: A closure containing the action to take when the slider begins or ends editing.
/// - trackColor: The color of the slider track.
/// - filledTrackColor: The color of the filled portion of the slider track.
///
init(
value: Binding<Double>,
in range: ClosedRange<Double>,
step: Double,
onEditingChanged: @escaping (Bool) -> Void,
trackColor: Color = Asset.Colors.sliderTrack.swiftUIColor,
filledTrackColor: Color = Asset.Colors.sliderFilled.swiftUIColor
) {
_value = value
self.range = range
self.step = step
self.>
self.trackColor = trackColor
self.filledTrackColor = filledTrackColor
}

// MARK: private methods

/// Calculate the position of the thumb view based on the current `value`.
private func thumbPosition(in size: CGSize) -> CGFloat {
let availableWidth = size.width - thumbSize.width // Adjust for thumb size
let relativeValue = (value - range.lowerBound) / (range.upperBound - range.lowerBound)
return availableWidth * CGFloat(relativeValue) + thumbSize.width / 2 // Adjust for thumb size
}

/// Calculate the `value` based on the position of the thumb view.
private func valueFrom(position: CGFloat, in size: CGSize) -> Double {
let availableWidth = size.width - thumbSize.width // Adjust for thumb size
let relativePosition = (position - thumbSize.width / 2) / availableWidth // Adjust for thumb size
let newValue = Double(relativePosition) * (range.upperBound - range.lowerBound) + range.lowerBound
return min(max(newValue, range.lowerBound), range.upperBound)
}

Check warning on line 136 in BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift

View check run for this annotation

Codecov / codecov/patch

BitwardenShared/UI/Platform/Application/Views/BitwardenSlider.swift#L131-L136

Added lines #L131 - L136 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import SnapshotTesting
import XCTest

@testable import BitwardenShared

// MARK: - BitwardenSliderTests

class BitwardenSliderTests: BitwardenTestCase {
// MARK: Tests

/// Test a snapshot of the slider with a value of 0.
func test_snapshot_slider_minValue() {
let subject = BitwardenSlider(
value: .constant(0),
in: 0...50,
step: 1,
onEditingChanged: { _ in }
)
assertSnapshots(
of: subject,
as: [.defaultPortrait, .defaultPortraitDark]
)
}

/// Test a snapshot of the slider with a value of 25.
func test_snapshot_slider_midValue() {
let subject = BitwardenSlider(
value: .constant(25),
in: 0...50,
step: 1,
onEditingChanged: { _ in }
)
assertSnapshots(
of: subject,
as: [.defaultPortrait, .defaultPortraitDark]
)
}

/// Test a snapshot of the slider with a value of 50.
func test_snapshot_slider_maxValue() {
let subject = BitwardenSlider(
value: .constant(50),
in: 0...50,
step: 1,
onEditingChanged: { _ in }
)
assertSnapshots(
of: subject,
as: [.defaultPortrait, .defaultPortraitDark]
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,22 @@ struct SliderFieldView<State>: View {
/// A closure containing the action to take when a new value is selected.
let onValueChanged: (Double) -> Void

var body: some View {
VStack(spacing: 8) {
HStack {
Text(field.title)
.styleGuide(.body)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)

Spacer()

Text(String(Int(field.value)))
.styleGuide(.body, monoSpacedDigit: true)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
.accessibilityIdentifier(field.sliderValueAccessibilityId ?? field.id)
}
.accessibilityHidden(true)
/// The width of the three digit text "000" based on the current font.
@SwiftUI.State private var minTextWidth: CGFloat = 14

Divider()
var body: some View {
HStack(alignment: .center, spacing: 16) {
Text(field.title)
.styleGuide(.body)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
.accessibilityHidden(true)

Slider(
BitwardenSlider(
value: Binding(get: { field.value }, set: onValueChanged),
in: field.range,
step: field.step,
onEditingChanged: onEditingChanged
)
.tint(Asset.Colors.tintPrimary.swiftUIColor)
.accessibilityLabel(field.title)
.accessibilityIdentifier(field.sliderAccessibilityId ?? field.title)
.apply { view in
Expand All @@ -94,11 +85,21 @@ struct SliderFieldView<State>: View {
view
}
}

Text(String(Int(field.value)))
.styleGuide(.body, monoSpacedDigit: true)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
.accessibilityIdentifier(field.sliderValueAccessibilityId ?? field.id)
.accessibilityHidden(true)
.frame(minWidth: minTextWidth)
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(Asset.Colors.backgroundSecondary.swiftUIColor)
.cornerRadius(10)
.background {
calculateMinTextWidth()
}
}

// MARK: Initialization
Expand All @@ -119,4 +120,19 @@ struct SliderFieldView<State>: View {
self.>
self.>
}

// MARK: Private methods

/// Calculate the width of the text "000" based on the current font.
private func calculateMinTextWidth() -> some View {
Text("000")
.styleGuide(.body, monoSpacedDigit: true)
.hidden()
.background(GeometryReader { geometry in
Color.clear
.onAppear {
minTextWidth = geometry.size.width
}
})
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading
0