[Features] Supports user-facing @Options with custom SwiftUI views
@Options with non-nil names will be exposed in Delta’s settings and can be configured by users via provided SwiftUI view.
This commit is contained in:
parent
4d30ef2929
commit
240b74de94
@ -10,8 +10,14 @@ import SwiftUI
|
|||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public class Option<Value: OptionValue>: _AnyOption
|
public class Option<Value: OptionValue, DetailView: View>: _AnyOption
|
||||||
{
|
{
|
||||||
|
// Nil name == hidden option.
|
||||||
|
public let name: LocalizedStringKey?
|
||||||
|
public let description: LocalizedStringKey?
|
||||||
|
|
||||||
|
public private(set) var detailView: () -> DetailView? = { nil }
|
||||||
|
|
||||||
// Assigned to property name.
|
// Assigned to property name.
|
||||||
public internal(set) var key: String = ""
|
public internal(set) var key: String = ""
|
||||||
|
|
||||||
@ -27,6 +33,14 @@ public class Option<Value: OptionValue>: _AnyOption
|
|||||||
|
|
||||||
private let defaultValue: Value
|
private let defaultValue: Value
|
||||||
|
|
||||||
|
private var valueBinding: Binding<Value> {
|
||||||
|
Binding(get: {
|
||||||
|
self.wrappedValue
|
||||||
|
}, set: { newValue in
|
||||||
|
self.wrappedValue = newValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// @propertyWrapper
|
/// @propertyWrapper
|
||||||
public var projectedValue: some Option {
|
public var projectedValue: some Option {
|
||||||
return self
|
return self
|
||||||
@ -59,21 +73,75 @@ public class Option<Value: OptionValue>: _AnyOption
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-Optional
|
private init(defaultValue: Value, name: LocalizedStringKey?, description: LocalizedStringKey?)
|
||||||
public init(wrappedValue: Value)
|
|
||||||
{
|
{
|
||||||
self.defaultValue = wrappedValue
|
self.defaultValue = defaultValue
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.detailView = { nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Hidden" Option (no name or custom SwiftUI view)
|
||||||
|
public extension Option where DetailView == EmptyView
|
||||||
|
{
|
||||||
|
// Non-Optional
|
||||||
|
convenience init(wrappedValue: Value)
|
||||||
|
{
|
||||||
|
self.init(defaultValue: wrappedValue, name: nil, description: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional, default = nil
|
// Optional, default = nil
|
||||||
public init() where Value: OptionalProtocol
|
convenience init() where Value: OptionalProtocol
|
||||||
{
|
{
|
||||||
self.defaultValue = Value.none
|
self.init(defaultValue: Value.none, name: nil, description: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional, default = non-nil
|
// Optional, default = non-nil
|
||||||
public init(wrappedValue: Value) where Value: OptionalProtocol
|
convenience init(wrappedValue: Value) where Value: OptionalProtocol
|
||||||
{
|
{
|
||||||
self.defaultValue = wrappedValue
|
self.init(defaultValue: wrappedValue, name: nil, description: nil)
|
||||||
|
|
||||||
|
// "Custom" Option (User-visible, provides SwiftUI view to configure option)
|
||||||
|
public extension Option where Value: LocalizedOptionValue
|
||||||
|
{
|
||||||
|
// Non-Optional
|
||||||
|
convenience init(wrappedValue: Value, name: LocalizedStringKey, description: LocalizedStringKey? = nil, @ViewBuilder detailView: @escaping (Binding<Value>) -> DetailView)
|
||||||
|
{
|
||||||
|
self.init(defaultValue: wrappedValue, name: name, description: description)
|
||||||
|
|
||||||
|
self.detailView = { [weak self] in
|
||||||
|
guard let self else { return nil }
|
||||||
|
|
||||||
|
let view = detailView(self.valueBinding)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional, default = nil
|
||||||
|
convenience init(name: LocalizedStringKey, description: LocalizedStringKey? = nil, @ViewBuilder detailView: @escaping (Binding<Value>) -> DetailView) where Value: OptionalProtocol, Value.Wrapped: LocalizedOptionValue
|
||||||
|
{
|
||||||
|
self.init(defaultValue: Value.none, name: name, description: description)
|
||||||
|
|
||||||
|
self.detailView = { [weak self] in
|
||||||
|
guard let self else { return nil }
|
||||||
|
|
||||||
|
let view = detailView(self.valueBinding)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional, default = non-nil
|
||||||
|
convenience init(wrappedValue: Value, name: LocalizedStringKey, description: LocalizedStringKey? = nil, @ViewBuilder detailView: @escaping (Binding<Value>) -> DetailView) where Value: OptionalProtocol, Value.Wrapped: LocalizedOptionValue
|
||||||
|
{
|
||||||
|
self.init(defaultValue: wrappedValue, name: name, description: description)
|
||||||
|
|
||||||
|
self.detailView = { [weak self] in
|
||||||
|
guard let self else { return nil }
|
||||||
|
|
||||||
|
let view = detailView(self.valueBinding)
|
||||||
|
return view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,16 @@ import SwiftUI
|
|||||||
public protocol AnyOption<Value>: AnyObject, Identifiable
|
public protocol AnyOption<Value>: AnyObject, Identifiable
|
||||||
{
|
{
|
||||||
associatedtype Value: OptionValue
|
associatedtype Value: OptionValue
|
||||||
|
associatedtype DetailView: View
|
||||||
|
|
||||||
|
var name: LocalizedStringKey? { get }
|
||||||
|
var description: LocalizedStringKey? { get }
|
||||||
|
|
||||||
var key: String { get }
|
var key: String { get }
|
||||||
var settingsKey: SettingsName { get }
|
var settingsKey: SettingsName { get }
|
||||||
|
|
||||||
|
var detailView: () -> DetailView? { get }
|
||||||
|
|
||||||
var value: Value { get set }
|
var value: Value { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user