Skip to content

Commit

Permalink
Allow namespaced child elements in decoded modifiers, finish backgrou…
Browse files Browse the repository at this point in the history
…nd modifier
  • Loading branch information
supernintendo committed Mar 24, 2023
1 parent 399978a commit 841402f
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 29 deletions.
34 changes: 34 additions & 0 deletions Sources/LiveViewNative/Modifiers/BackgroundModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// BackgroundModifier.swift
// LiveViewNative
//
// Created by May Matyi on 3/17/23.
//

import SwiftUI

struct BackgroundModifier<R: RootRegistry>: ViewModifier, Decodable {
let alignment: Alignment
let content: String
let element: ElementNode
@LiveContext<R> private var context

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.alignment = try container.decode(Alignment.self, forKey: .alignment)
self.content = try container.decode(String.self, forKey: .content)
self.element = decoder.userInfo[.elementNode!] as! ElementNode
}

func body(content: Content) -> some View {
content.background(alignment: alignment) {
context.buildChildren(of: element, withTagName: self.content, namespace: "background")
}
}

enum CodingKeys: CodingKey {
case alignment
case content
}
}
4 changes: 3 additions & 1 deletion Sources/LiveViewNative/Modifiers/FrameModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ enum FrameModifier: ViewModifier, Decodable, Equatable {
}

init(string value: String) {
self = try! BuiltinRegistry.attributeDecoder.decode(Self.self, from: value.data(using: .utf8)!)
let attributeDecoder = JSONDecoder()

self = try! attributeDecoder.decode(Self.self, from: value.data(using: .utf8)!)
}

func body(content: Content) -> some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ struct ListRowSeparatorModifier: ViewModifier, Decodable, Equatable {
self.visibility = .automatic
self.edges = .all
default:
self = try! BuiltinRegistry.attributeDecoder.decode(ListRowSeparatorModifier.self, from: value.data(using: .utf8)!)
let attributeDecoder = JSONDecoder()

self = try! attributeDecoder.decode(ListRowSeparatorModifier.self, from: value.data(using: .utf8)!)
}
}

Expand Down
12 changes: 6 additions & 6 deletions Sources/LiveViewNative/Registries/BuiltinRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ protocol BuiltinRegistryProtocol {
static func decodeModifier(_ type: ModifierType, from decoder: Decoder) throws -> BuiltinModifier
}

struct BuiltinRegistry: BuiltinRegistryProtocol {

static let attributeDecoder = JSONDecoder()

struct BuiltinRegistry<R: RootRegistry>: BuiltinRegistryProtocol {
// note: the context parameter is unused, but it needs to be there for swift to infer the generic type R
@ViewBuilder
static func lookup<R: RootRegistry>(_ name: String, _ element: ElementNode, context: LiveContextStorage<R>) -> some View {
Expand Down Expand Up @@ -171,8 +168,9 @@ struct BuiltinRegistry: BuiltinRegistryProtocol {
EmptyView()
}
}

enum ModifierType: String {
case background = "background"
case backgroundStyle = "background_style"
case fontWeight = "font_weight"
case foregroundStyle = "foreground_style"
Expand All @@ -189,10 +187,12 @@ struct BuiltinRegistry: BuiltinRegistryProtocol {
case tag
case tint
}

@ViewModifierBuilder
static func decodeModifier(_ type: ModifierType, from decoder: Decoder) throws -> some ViewModifier {
switch type {
case .background:
try BackgroundModifier<R>(from: decoder)
case .backgroundStyle:
try BackgroundStyleModifier(from: decoder)
case .foregroundStyle:
Expand Down
53 changes: 33 additions & 20 deletions Sources/LiveViewNative/ViewTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ struct ViewTreeBuilder<R: RootRegistry> {
fileprivate func fromElement(_ element: ElementNode, context: LiveContextStorage<R>) -> some View {
let view = createView(element, context: context)
let jsonStr = element.attributeValue(for: "modifiers")
let modified = applyModifiers(encoded: jsonStr, to: view, context: context)
let bound = applyBindings(to: modified, element: element)
let modified = applyModifiers(encoded: jsonStr, to: view, element: element, context: context)
let bound = applyBindings(to: modified, element: element, context: context)
let withID = applyID(element: element, to: bound)
return withID
.environment(\.element, element)
Expand All @@ -93,14 +93,17 @@ struct ViewTreeBuilder<R: RootRegistry> {
if let tagName = R.TagName(rawValue: element.tag) {
R.lookup(tagName, element: element)
} else {
BuiltinRegistry.lookup(element.tag, element, context: context)
}
BuiltinRegistry<R>.lookup(element.tag, element, context: context)
}
}

private func applyModifiers(encoded: String?, to view: some View, context: LiveContextStorage<R>) -> some View {
private func applyModifiers(encoded: String?, to view: some View, element: ElementNode, context: LiveContextStorage<R>) -> some View {
let modifiers: [ModifierContainer<R>]
if let encoded {
let decoder = JSONDecoder()
decoder.userInfo[.liveContext!] = context
decoder.userInfo[.elementNode!] = element

if let decoded = try? decoder.decode([ModifierContainer<R>].self, from: Data(encoded.utf8)) {
modifiers = decoded
} else {
Expand All @@ -109,19 +112,21 @@ struct ViewTreeBuilder<R: RootRegistry> {
} else {
modifiers = []
}
return view.applyModifiers(modifiers[...])
return view.applyModifiers(modifiers[...], element: element, context: context)
}

@ViewBuilder
private func applyBindings(
private func applyBindings<R: RootRegistry>(
to view: some View,
element: ElementNode
element: ElementNode,
context: LiveContextStorage<R>
) -> some View {
view.applyBindings(
element.attributes.filter({
$0.name.rawValue.starts(with: "phx-") && $0.value != nil
})[...],
element: element
element: element,
context: context
)
}
}
Expand All @@ -140,7 +145,7 @@ extension ViewTreeBuilder {
}

enum ModifierContainer<R: RootRegistry>: Decodable {
case builtin(BuiltinRegistry.BuiltinModifier)
case builtin(BuiltinRegistry<R>.BuiltinModifier)
case custom(R.CustomModifier)
case error(ErrorModifier)

Expand All @@ -153,9 +158,9 @@ enum ModifierContainer<R: RootRegistry>: Decodable {
} catch {
self = .error(ErrorModifier(type: type.rawValue, error: error))
}
} else if let type = BuiltinRegistry.ModifierType(rawValue: type) {
} else if let type = BuiltinRegistry<R>.ModifierType(rawValue: type) {
do {
self = .builtin(try BuiltinRegistry.decodeModifier(type, from: decoder))
self = .builtin(try BuiltinRegistry<R>.decodeModifier(type, from: decoder))
} catch {
self = .error(ErrorModifier(type: type.rawValue, error: error))
}
Expand Down Expand Up @@ -185,51 +190,54 @@ enum ModifierContainer<R: RootRegistry>: Decodable {
private struct ModifierApplicator<Parent: View, R: RootRegistry>: View {
let parent: Parent
let modifiers: ArraySlice<ModifierContainer<R>>
let element: ElementNode
let context: LiveContextStorage<R>

var body: some View {
let remaining = modifiers.dropFirst()
// force-unwrap is okay, this view is never constructed with an empty slice
parent.modifier(modifiers.first!.modifier)
.applyModifiers(remaining)
.applyModifiers(remaining, element: element, context: context)
}
}

private struct BindingApplicator<Parent: View>: View {
private struct BindingApplicator<Parent: View, R: RootRegistry>: View {
let parent: Parent
let bindings: ArraySlice<LiveViewNativeCore.Attribute>
let element: ElementNode
let context: LiveContextStorage<R>

var body: some View {
let remaining = bindings.dropFirst()
// force-unwrap is okay, this view is never constructed with an empty slice
let binding = bindings.first!
BuiltinRegistry.applyBinding(
BuiltinRegistry<R>.applyBinding(
binding.name,
event: binding.value!,
value: element.buildPhxValuePayload(),
to: parent,
element: element
)
.applyBindings(remaining, element: element)
.applyBindings(remaining, element: element, context: context)
}
}

private extension View {
@ViewBuilder
func applyModifiers<R: RootRegistry>(_ modifiers: ArraySlice<ModifierContainer<R>>) -> some View {
func applyModifiers<R: RootRegistry>(_ modifiers: ArraySlice<ModifierContainer<R>>, element: ElementNode, context: LiveContextStorage<R>) -> some View {
if modifiers.isEmpty {
self
} else {
ModifierApplicator(parent: self, modifiers: modifiers)
ModifierApplicator(parent: self, modifiers: modifiers, element: element, context: context)
}
}

@ViewBuilder
func applyBindings(_ bindings: ArraySlice<LiveViewNativeCore.Attribute>, element: ElementNode) -> some View {
func applyBindings<R: RootRegistry>(_ bindings: ArraySlice<LiveViewNativeCore.Attribute>, element: ElementNode, context: LiveContextStorage<R>) -> some View {
if bindings.isEmpty {
self
} else {
BindingApplicator(parent: self, bindings: bindings, element: element)
BindingApplicator(parent: self, bindings: bindings, element: element, context: context)
}
}
}
Expand Down Expand Up @@ -260,3 +268,8 @@ func forEach<R: CustomRegistry>(nodes: some Collection<Node>, context: LiveConte
ElementView<R>(element: $0.0, context: context)
}
}

extension CodingUserInfoKey {
static let elementNode = CodingUserInfoKey(rawValue: "elementNode")
static let liveContext = CodingUserInfoKey(rawValue: "liveContext")
}
15 changes: 14 additions & 1 deletion lib/live_view_native_swift_ui/modifiers/background.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ defmodule LiveViewNativeSwiftUi.Modifiers.Background do
use LiveViewNativePlatform.Modifier

modifier_schema "background" do
field :shape, :string
field :alignment, Ecto.Enum, values: ~w(
bottom
bottom_leading
bottom_trailing
center
leading
leading_last_text_baseline
top
top_leading
top_trailing
trailing
trailing_first_text_baseline
)a
field :content, :string
end
end

0 comments on commit 841402f

Please sign in to comment.