Skip to content

Commit 4d3b9ab

Browse files
committed
Add ProgressView
1 parent 7e4028b commit 4d3b9ab

File tree

5 files changed

+120
-16
lines changed

5 files changed

+120
-16
lines changed

Sources/LiveViewNative/BuiltinRegistry.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ struct BuiltinRegistry {
4646
Shape(element: element, context: context, shape: RoundedRectangle(from: element))
4747
case "lvn-link":
4848
Link(element: element, context: context)
49+
case "progressview":
50+
ProgressView(element: element, context: context)
4951

5052
case "phx-form":
5153
PhxForm<R>(element: element, context: context)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// DateParsing.swift
3+
//
4+
//
5+
// Created by Carson Katri on 1/17/23.
6+
//
7+
8+
import Foundation
9+
10+
/// A formatter that parses ISO8601 dates as produced by Elixir's `DateTime`.
11+
fileprivate let dateTimeFormatter: ISO8601DateFormatter = {
12+
let formatter = ISO8601DateFormatter()
13+
formatter.formatOptions = [.withFullDate, .withFullTime, .withFractionalSeconds]
14+
return formatter
15+
}()
16+
17+
/// A formatter that parses ISO8601 dates as produced by Elixir's `Date`.
18+
fileprivate let dateFormatter: DateFormatter = {
19+
let formatter = DateFormatter()
20+
formatter.dateFormat = "yyyy-MM-dd"
21+
return formatter
22+
}()
23+
24+
struct ElixirDateFormat: ParseableFormatStyle {
25+
typealias FormatInput = Date
26+
27+
typealias FormatOutput = String
28+
29+
func format(_ value: Date) -> String {
30+
dateTimeFormatter.string(from: value)
31+
}
32+
33+
var parseStrategy = ElixirDateParseStrategy()
34+
}
35+
36+
struct ElixirDateParseStrategy: ParseStrategy {
37+
func parse(_ value: String) throws -> Date {
38+
guard let value = dateTimeFormatter.date(from: value) ?? dateFormatter.date(from: value)
39+
else { throw DateParseError.invalidDate }
40+
return value
41+
}
42+
43+
enum DateParseError: Error {
44+
case invalidDate
45+
}
46+
}
47+
48+
extension FormatStyle where Self == ElixirDateFormat {
49+
static var elixirDate: ElixirDateFormat { .init() }
50+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Link.swift
3+
//
4+
//
5+
// Created by Carson Katri on 1/17/23.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ProgressView<R: CustomRegistry>: View {
11+
@ObservedElement private var element: ElementNode
12+
let context: LiveContext<R>
13+
14+
init(element: ElementNode, context: LiveContext<R>) {
15+
self.context = context
16+
}
17+
18+
public var body: some View {
19+
Group {
20+
if let timerIntervalStart = element.attributeValue(for: "timer-interval-start").flatMap({ try? ElixirDateParseStrategy().parse($0) }),
21+
let timerIntervalEnd = element.attributeValue(for: "timer-interval-end").flatMap({ try? ElixirDateParseStrategy().parse($0) })
22+
{
23+
SwiftUI.ProgressView(
24+
timerInterval: timerIntervalStart...timerIntervalEnd,
25+
countsDown: element.attributeValue(for: "counts-down") != "false"
26+
) {
27+
context.buildChildren(of: element)
28+
}
29+
} else if let value = element.attributeValue(for: "value").flatMap(Double.init) {
30+
SwiftUI.ProgressView(
31+
value: value,
32+
total: element.attributeValue(for: "total").flatMap(Double.init) ?? 1
33+
) {
34+
context.buildChildren(of: element)
35+
} currentValueLabel: {
36+
EmptyView() // TODO: Implement currentValueLabel once we have a design for multi-body content.
37+
}
38+
} else {
39+
SwiftUI.ProgressView {
40+
context.buildChildren(of: element)
41+
}
42+
}
43+
}
44+
.applyProgressViewStyle(element.attributeValue(for: "progress-view-style").flatMap(ProgressViewStyle.init) ?? .automatic)
45+
}
46+
}
47+
48+
fileprivate enum ProgressViewStyle: String {
49+
case automatic
50+
case linear
51+
case circular
52+
}
53+
54+
fileprivate extension View {
55+
@ViewBuilder
56+
func applyProgressViewStyle(_ style: ProgressViewStyle) -> some View {
57+
switch style {
58+
case .automatic:
59+
self.progressViewStyle(.automatic)
60+
case .linear:
61+
self.progressViewStyle(.linear)
62+
case .circular:
63+
self.progressViewStyle(.circular)
64+
}
65+
}
66+
}

Sources/LiveViewNative/Views/Images/AsyncImage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct AsyncImage<R: CustomRegistry>: View {
2525
case .failure(let error):
2626
SwiftUI.Text(error.localizedDescription)
2727
case .empty:
28-
ProgressView().progressViewStyle(.circular)
28+
SwiftUI.ProgressView().progressViewStyle(.circular)
2929
@unknown default:
3030
EmptyView()
3131
}

Sources/LiveViewNative/Views/Text Input and Output/Text.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,6 @@
77

88
import SwiftUI
99

10-
/// A formatter that parses ISO8601 dates as produced by Elixir's `DateTime`.
11-
fileprivate let dateTimeFormatter: ISO8601DateFormatter = {
12-
let formatter = ISO8601DateFormatter()
13-
formatter.formatOptions = [.withFullDate, .withFullTime, .withFractionalSeconds]
14-
return formatter
15-
}()
16-
17-
/// A formatter that parses ISO8601 dates as produced by Elixir's `Date`.
18-
fileprivate let dateFormatter: DateFormatter = {
19-
let formatter = DateFormatter()
20-
formatter.dateFormat = "yyyy-MM-dd"
21-
return formatter
22-
}()
23-
2410
struct Text<R: CustomRegistry>: View {
2511
let element: ElementNode
2612
let context: LiveContext<R>
@@ -37,7 +23,7 @@ struct Text<R: CustomRegistry>: View {
3723
}
3824

3925
private func formatDate(_ date: String) -> Date? {
40-
dateTimeFormatter.date(from: date) ?? dateFormatter.date(from: date)
26+
try? ElixirDateParseStrategy().parse(date)
4127
}
4228

4329
private var text: SwiftUI.Text {

0 commit comments

Comments
 (0)