From 12f65b51e4e66f7f13d783e5501dfd776a06c4f1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 3 Dec 2023 20:34:52 +0800 Subject: [PATCH] Add AbstractAnyAccessibilityValue --- .../internal/AccessibilityBoundedNumber.swift | 84 ++++++++++++++++++ .../internal/AccessibilityNumeric.swift | 86 +++++++++++++++++++ .../internal/AccessibilityValue.swift | 33 ++++++- .../internal/AnyAccessibilityValueType.swift | 14 +-- .../Internal/Other/Comparable+Extension.swift | 21 +++++ 5 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 Sources/OpenSwiftUI/Accessibility/internal/AccessibilityNumeric.swift create mode 100644 Sources/OpenSwiftUI/Internal/Other/Comparable+Extension.swift diff --git a/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityBoundedNumber.swift b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityBoundedNumber.swift index 3a7a849..a9cbbeb 100644 --- a/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityBoundedNumber.swift +++ b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityBoundedNumber.swift @@ -9,6 +9,90 @@ import Foundation +// MARK: - AbstractAnyAccessibilityValue + +private protocol AbstractAnyAccessibilityValue: Codable { + var localizedDescription: String? { get } + var displayDescription: String? { get } + var value: Any { get } + var minValue: Any? { get } + var maxValue: Any? { get } + var step: Any? { get } + var type: AnyAccessibilityValueType { get } + func `as`(_ type: Value.Type) -> Value? + func isEqual(to value: AbstractAnyAccessibilityValue) -> Bool +} + +// MARK: - AnyAccessibilityValue + +struct AnyAccessibilityValue/*: AbstractAnyAccessibilityValue*/ { + private var base: AbstractAnyAccessibilityValue + + init(_ base: Value) { + self.base = ConcreteBase(base: base) + } +} + +extension AnyAccessibilityValue: Equatable { + static func == (lhs: AnyAccessibilityValue, rhs: AnyAccessibilityValue) -> Bool { + lhs.base.isEqual(to: rhs.base) + } +} + +extension AnyAccessibilityValue: Codable { + private enum Keys: CodingKey { + case type + case value + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + let type = try container.decode(AnyAccessibilityValueType.self, forKey: .type) + switch type { + case .int: base = try container.decode(ConcreteBase.self, forKey: .value) + case .double: base = try container.decode(ConcreteBase.self, forKey: .value) + case .bool: base = try container.decode(ConcreteBase.self, forKey: .value) + case .string: base = try container.decode(ConcreteBase.self, forKey: .value) + case .boundedNumber: base = try container.decode(ConcreteBase.self, forKey: .value) + case .number: base = try container.decode(ConcreteBase.self, forKey: .value) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Keys.self) + try container.encode(base.type, forKey: .type) + try base.encode(to: container.superEncoder(forKey: .value)) + } +} + +// MARK: AnyAccessibilityValue.ConcreateBase + +extension AnyAccessibilityValue { + fileprivate struct ConcreteBase where Base: Codable, Base: AccessibilityValue { + var base: Base + } +} + +extension AnyAccessibilityValue.ConcreteBase: Codable {} +extension AnyAccessibilityValue.ConcreteBase: Equatable {} +extension AnyAccessibilityValue.ConcreteBase: AbstractAnyAccessibilityValue { + var localizedDescription: String? { base.localizedDescription } + var displayDescription: String? { base.displayDescription } + var value: Any { base.value } + var minValue: Any? { base.minValue } + var maxValue: Any? { base.maxValue } + var step: Any? { base.step } + var type: AnyAccessibilityValueType { Base.type } + func `as`(_ type: Value.Type) -> Value? where Value : AccessibilityValue { + base as? Value + } + func isEqual(to value: AbstractAnyAccessibilityValue) -> Bool { + base == (value as? Self)?.base + } +} + +// MARK: - AccessibilityBoundedNumber + struct AccessibilityBoundedNumber { var number: AccessibilityNumber var lowerBound: AccessibilityNumber? diff --git a/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityNumeric.swift b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityNumeric.swift new file mode 100644 index 0000000..5f5c3b7 --- /dev/null +++ b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityNumeric.swift @@ -0,0 +1,86 @@ +// +// AccessibilityNumber.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/12/3. +// Lastest Version: iOS 15.5 +// Status: Complete + +import Foundation + +protocol AccessibilityNumeric { + var isValidMinValue: Bool { get } + var isValidMaxValue: Bool { get } + func asNumber() -> AccessibilityNumber? +} + +extension AccessibilityNumeric where Self: FixedWidthInteger { + var isValidMinValue: Bool { + // TODO: Add Unit Test and check usage + if Self.bitWidth == 8 || !Self.isSigned { + true + } else { + self != .min + } + } + + var isValidMaxValue: Bool { self != .max } +} + +extension Int: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension Int8: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension Int16: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension Int32: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension Int64: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension UInt: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension UInt8: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension UInt16: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension UInt32: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension UInt64: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension AccessibilityNumeric where Self: BinaryFloatingPoint { + var isValidMinValue: Bool { + isFinite && self > -Self.greatestFiniteMagnitude + } + + var isValidMaxValue: Bool { + isFinite && self < Self.greatestFiniteMagnitude + } +} + +extension Float: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} + +extension Double: AccessibilityNumeric { + func asNumber() -> AccessibilityNumber? { AccessibilityNumber(base: .init(value: self)) } +} diff --git a/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityValue.swift b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityValue.swift index 1b97a1c..27dc9bf 100644 --- a/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityValue.swift +++ b/Sources/OpenSwiftUI/Accessibility/internal/AccessibilityValue.swift @@ -20,5 +20,36 @@ protocol AccessibilityValue: Equatable { } extension AccessibilityValue { - var step: NSNumber? { nil } + var minValue: PlatformValue? { nil } + var maxValue: PlatformValue? { nil } + var step: PlatformValue? { nil } +} + +extension AccessibilityValue where PlatformValue: CustomStringConvertible { + var localizedDescription: String? { value.description } + var displayDescription: String? { value.description } +} + +extension AccessibilityValue where Self == Self.PlatformValue { + var value: PlatformValue { self } +} + +extension Int: AccessibilityValue { + typealias PlatformValue = Int + static var type: AnyAccessibilityValueType { .int } +} + +extension Double: AccessibilityValue { + typealias PlatformValue = Double + static var type: AnyAccessibilityValueType { .number } +} + +extension Bool: AccessibilityValue { + typealias PlatformValue = Bool + static var type: AnyAccessibilityValueType { .bool } +} + +extension String: AccessibilityValue { + typealias PlatformValue = String + static var type: AnyAccessibilityValueType { .string } } diff --git a/Sources/OpenSwiftUI/Accessibility/internal/AnyAccessibilityValueType.swift b/Sources/OpenSwiftUI/Accessibility/internal/AnyAccessibilityValueType.swift index 9480519..c6f206b 100644 --- a/Sources/OpenSwiftUI/Accessibility/internal/AnyAccessibilityValueType.swift +++ b/Sources/OpenSwiftUI/Accessibility/internal/AnyAccessibilityValueType.swift @@ -6,17 +6,17 @@ // Lastest Version: iOS 15.5 // Status: Complete -enum AnyAccessibilityValueType: UInt { +enum AnyAccessibilityValueType: UInt, Codable { case int case double case bool case string - case disclosure - case toggle - case slider - case stepper - case progress +// case disclosure +// case toggle +// case slider +// case stepper +// case progress case boundedNumber - case headingLevel +// case headingLevel case number } diff --git a/Sources/OpenSwiftUI/Internal/Other/Comparable+Extension.swift b/Sources/OpenSwiftUI/Internal/Other/Comparable+Extension.swift new file mode 100644 index 0000000..d9d8db1 --- /dev/null +++ b/Sources/OpenSwiftUI/Internal/Other/Comparable+Extension.swift @@ -0,0 +1,21 @@ +extension Comparable { + func clamped(to range: ClosedRange) -> Self { + var value = self + value.clamp(to: range) + return value + } + + mutating func clamp(to range: ClosedRange) { + self = OpenSwiftUI.clamp(self, min: range.lowerBound, max: range.upperBound) + } +} + +func clamp(_ value: Value, min minValue: Value, max maxValue: Value) -> Value { + if value < minValue { + minValue + } else if value > maxValue { + maxValue + } else { + value + } +}