[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:
Riley Testut 2023-04-14 16:41:16 -05:00
parent 4d30ef2929
commit 240b74de94
2 changed files with 82 additions and 8 deletions

View File

@ -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
}
} }
} }

View File

@ -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 }
} }