[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
@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.
public internal(set) var key: String = ""
@ -27,6 +33,14 @@ public class Option<Value: OptionValue>: _AnyOption
private let defaultValue: Value
private var valueBinding: Binding<Value> {
Binding(get: {
self.wrappedValue
}, set: { newValue in
self.wrappedValue = newValue
})
}
/// @propertyWrapper
public var projectedValue: some Option {
return self
@ -59,21 +73,75 @@ public class Option<Value: OptionValue>: _AnyOption
}
}
// Non-Optional
public init(wrappedValue: Value)
private init(defaultValue: Value, name: LocalizedStringKey?, description: LocalizedStringKey?)
{
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
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
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
{
associatedtype Value: OptionValue
associatedtype DetailView: View
var name: LocalizedStringKey? { get }
var description: LocalizedStringKey? { get }
var key: String { get }
var settingsKey: SettingsName { get }
var detailView: () -> DetailView? { get }
var value: Value { get set }
}