sitepong 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +39 -0
- package/android/src/main/java/com/sitepong/deviceid/DeviceIdHelper.kt +45 -0
- package/android/src/main/java/com/sitepong/deviceid/DeviceIdModule.kt +45 -0
- package/android/src/main/java/com/sitepong/screenrecorder/ChunkUploader.kt +78 -0
- package/android/src/main/java/com/sitepong/screenrecorder/H264Encoder.kt +163 -0
- package/android/src/main/java/com/sitepong/screenrecorder/ScreenCapture.kt +103 -0
- package/android/src/main/java/com/sitepong/screenrecorder/ScreenRecorderModule.kt +80 -0
- package/android/src/main/java/com/sitepong/screenrecorder/SensitiveViewManager.kt +19 -0
- package/app.plugin.js +426 -0
- package/dist/cdn/sitepong.min.js +5 -5
- package/dist/cdn/sitepong.min.js.map +1 -1
- package/dist/entries/rn.d.ts +188 -16
- package/dist/entries/rn.js +188 -35
- package/dist/entries/rn.js.map +1 -1
- package/dist/entries/web.js +2 -1
- package/dist/entries/web.js.map +1 -1
- package/dist/entries/web.mjs +2 -1
- package/dist/entries/web.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +2 -1
- package/dist/react/index.mjs.map +1 -1
- package/expo-module.config.json +16 -0
- package/ios/DeviceId/DeviceIdModule.swift +55 -0
- package/ios/DeviceId/KeychainHelper.swift +68 -0
- package/ios/LiveActivity/ConfigStore.swift +43 -0
- package/ios/LiveActivity/DSLNode.swift +188 -0
- package/ios/LiveActivity/SitePongActivityAttributes.swift +112 -0
- package/ios/LiveActivity/SitePongLiveActivityModule.swift +250 -0
- package/ios/LiveActivity/widget/ColorHex.swift +30 -0
- package/ios/LiveActivity/widget/DSLAnimations.swift +128 -0
- package/ios/LiveActivity/widget/DSLRenderer.swift +538 -0
- package/ios/LiveActivity/widget/SitePongLiveActivityWidget.swift +393 -0
- package/ios/LiveActivity/widget/SitePongWidgetBundle.swift +84 -0
- package/ios/ScreenRecorder/ChunkUploader.swift +52 -0
- package/ios/ScreenRecorder/H264Encoder.swift +229 -0
- package/ios/ScreenRecorder/ScreenCapture.swift +149 -0
- package/ios/ScreenRecorder/ScreenRecorderModule.swift +179 -0
- package/ios/ScreenRecorder/SensitiveViewManager.swift +63 -0
- package/ios/Sitepong.podspec +57 -0
- package/ios/WatchtowerCore/Screenshotter.swift +163 -0
- package/ios/WatchtowerCore/SwiftUIModifiers.swift +78 -0
- package/ios/WatchtowerCore/Swizzling.swift +59 -0
- package/ios/WatchtowerCore/TapEvent.swift +61 -0
- package/ios/WatchtowerCore/Transport.swift +136 -0
- package/ios/WatchtowerCore/UIViewExtensions.swift +31 -0
- package/ios/WatchtowerCore/Watchtower.swift +80 -0
- package/ios/WatchtowerCore/WatchtowerEngine.swift +565 -0
- package/ios/WatchtowerCore/WatchtowerHash.swift +120 -0
- package/ios/WatchtowerCore/WatchtowerRegistry.swift +87 -0
- package/package.json +19 -8
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import WidgetKit
|
|
3
|
+
|
|
4
|
+
// MARK: - DSLView
|
|
5
|
+
|
|
6
|
+
@available(iOS 16.2, *)
|
|
7
|
+
struct DSLView: View {
|
|
8
|
+
let node: DSLNode
|
|
9
|
+
let data: [String: AnyCodable]
|
|
10
|
+
|
|
11
|
+
var body: some View {
|
|
12
|
+
applyStyle(to: renderNode())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// MARK: - Node routing
|
|
16
|
+
|
|
17
|
+
@ViewBuilder
|
|
18
|
+
private func renderNode() -> some View {
|
|
19
|
+
switch node.type {
|
|
20
|
+
case "vstack":
|
|
21
|
+
renderVStack()
|
|
22
|
+
case "hstack":
|
|
23
|
+
renderHStack()
|
|
24
|
+
case "zstack":
|
|
25
|
+
renderZStack()
|
|
26
|
+
case "text":
|
|
27
|
+
renderText()
|
|
28
|
+
case "image":
|
|
29
|
+
renderImage()
|
|
30
|
+
case "spacer":
|
|
31
|
+
renderSpacer()
|
|
32
|
+
case "gauge":
|
|
33
|
+
renderGauge()
|
|
34
|
+
case "progress":
|
|
35
|
+
renderProgress()
|
|
36
|
+
case "shape":
|
|
37
|
+
renderShape()
|
|
38
|
+
case "container":
|
|
39
|
+
renderContainer()
|
|
40
|
+
case "conditional":
|
|
41
|
+
renderConditional()
|
|
42
|
+
default:
|
|
43
|
+
EmptyView()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MARK: - Stack views
|
|
48
|
+
|
|
49
|
+
@ViewBuilder
|
|
50
|
+
private func renderVStack() -> some View {
|
|
51
|
+
let alignment = horizontalAlignment(from: prop("alignment")?.stringValue)
|
|
52
|
+
let spacing = prop("spacing")?.doubleValue
|
|
53
|
+
|
|
54
|
+
VStack(alignment: alignment, spacing: spacing.map { CGFloat($0) }) {
|
|
55
|
+
renderChildren()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@ViewBuilder
|
|
60
|
+
private func renderHStack() -> some View {
|
|
61
|
+
let alignment = verticalAlignment(from: prop("alignment")?.stringValue)
|
|
62
|
+
let spacing = prop("spacing")?.doubleValue
|
|
63
|
+
|
|
64
|
+
HStack(alignment: alignment, spacing: spacing.map { CGFloat($0) }) {
|
|
65
|
+
renderChildren()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@ViewBuilder
|
|
70
|
+
private func renderZStack() -> some View {
|
|
71
|
+
let alignment = compositeAlignment(from: prop("alignment")?.stringValue)
|
|
72
|
+
|
|
73
|
+
ZStack(alignment: alignment) {
|
|
74
|
+
renderChildren()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - Text
|
|
79
|
+
|
|
80
|
+
@ViewBuilder
|
|
81
|
+
private func renderText() -> some View {
|
|
82
|
+
let content = interpolate(prop("value")?.stringValue ?? "")
|
|
83
|
+
let fontPreset = prop("font")?.stringValue
|
|
84
|
+
let fontSize = prop("fontSize")?.doubleValue
|
|
85
|
+
let fontWeightName = prop("fontWeight")?.stringValue
|
|
86
|
+
let color = prop("color")?.stringValue
|
|
87
|
+
let italic = prop("italic")?.boolValue ?? false
|
|
88
|
+
let monoDigit = prop("monospacedDigit")?.boolValue ?? false
|
|
89
|
+
let lineLimit = prop("lineLimit")?.intValue
|
|
90
|
+
|
|
91
|
+
let weight = symbolWeight(fontWeightName)
|
|
92
|
+
let size = fontSize ?? fontPresetSize(fontPreset) ?? 14
|
|
93
|
+
|
|
94
|
+
var font = Font.system(size: size, weight: weight)
|
|
95
|
+
if monoDigit {
|
|
96
|
+
font = font.monospacedDigit()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let textView = Text(content)
|
|
100
|
+
.font(font)
|
|
101
|
+
.italic(italic)
|
|
102
|
+
|
|
103
|
+
if let lineLimit = lineLimit {
|
|
104
|
+
if let color = Color(hex: color) {
|
|
105
|
+
textView.foregroundStyle(color).lineLimit(lineLimit)
|
|
106
|
+
} else {
|
|
107
|
+
textView.lineLimit(lineLimit)
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
if let color = Color(hex: color) {
|
|
111
|
+
textView.foregroundStyle(color)
|
|
112
|
+
} else {
|
|
113
|
+
textView
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// MARK: - Image (SF Symbols only)
|
|
119
|
+
|
|
120
|
+
@ViewBuilder
|
|
121
|
+
private func renderImage() -> some View {
|
|
122
|
+
let symbolName = interpolate(prop("symbol")?.stringValue ?? prop("name")?.stringValue ?? "questionmark")
|
|
123
|
+
let color = prop("color")?.stringValue
|
|
124
|
+
let weightName = prop("weight")?.stringValue
|
|
125
|
+
let size = prop("size")?.doubleValue ?? 16
|
|
126
|
+
|
|
127
|
+
let weight = symbolWeight(weightName)
|
|
128
|
+
|
|
129
|
+
let img = Image(systemName: symbolName)
|
|
130
|
+
.font(.system(size: size, weight: weight))
|
|
131
|
+
|
|
132
|
+
if let color = Color(hex: color) {
|
|
133
|
+
img.foregroundStyle(color)
|
|
134
|
+
} else {
|
|
135
|
+
img
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MARK: - Spacer
|
|
140
|
+
|
|
141
|
+
@ViewBuilder
|
|
142
|
+
private func renderSpacer() -> some View {
|
|
143
|
+
if let minLength = prop("minLength")?.doubleValue {
|
|
144
|
+
Spacer(minLength: CGFloat(minLength))
|
|
145
|
+
} else {
|
|
146
|
+
Spacer()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - Gauge (iOS 16+)
|
|
151
|
+
|
|
152
|
+
@ViewBuilder
|
|
153
|
+
private func renderGauge() -> some View {
|
|
154
|
+
let value = prop("value")?.doubleValue ?? 0
|
|
155
|
+
let minVal = prop("min")?.doubleValue ?? 0
|
|
156
|
+
let maxVal = prop("max")?.doubleValue ?? 1
|
|
157
|
+
let label = interpolate(prop("label")?.stringValue ?? "")
|
|
158
|
+
let gaugeStyle = prop("style")?.stringValue ?? "linear"
|
|
159
|
+
let tintColor = prop("tint")?.stringValue
|
|
160
|
+
let trackColor = prop("trackColor")?.stringValue
|
|
161
|
+
|
|
162
|
+
let clamped = max(minVal, min(maxVal, value))
|
|
163
|
+
|
|
164
|
+
if gaugeStyle == "circular" {
|
|
165
|
+
Gauge(value: clamped, in: minVal...maxVal) {
|
|
166
|
+
Text(label)
|
|
167
|
+
}
|
|
168
|
+
.gaugeStyle(.accessoryCircular)
|
|
169
|
+
.tint(Color(hex: tintColor) ?? .accentColor)
|
|
170
|
+
} else {
|
|
171
|
+
Gauge(value: clamped, in: minVal...maxVal) {
|
|
172
|
+
Text(label)
|
|
173
|
+
}
|
|
174
|
+
.gaugeStyle(.accessoryLinear)
|
|
175
|
+
.tint(Color(hex: tintColor) ?? .accentColor)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// MARK: - ProgressView
|
|
180
|
+
|
|
181
|
+
@ViewBuilder
|
|
182
|
+
private func renderProgress() -> some View {
|
|
183
|
+
let value = prop("value")?.doubleValue ?? 0
|
|
184
|
+
let tintColor = prop("tint")?.stringValue
|
|
185
|
+
|
|
186
|
+
ProgressView(value: max(0, min(1, value)))
|
|
187
|
+
.tint(Color(hex: tintColor) ?? .accentColor)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// MARK: - Shape
|
|
191
|
+
|
|
192
|
+
@ViewBuilder
|
|
193
|
+
private func renderShape() -> some View {
|
|
194
|
+
let shapeType = prop("shape")?.stringValue ?? "rectangle"
|
|
195
|
+
let fillColor = prop("fill")?.stringValue
|
|
196
|
+
let strokeColor = prop("stroke")?.stringValue
|
|
197
|
+
let strokeWidth = prop("strokeWidth")?.doubleValue ?? 1
|
|
198
|
+
|
|
199
|
+
switch shapeType {
|
|
200
|
+
case "circle":
|
|
201
|
+
if let strokeColor = Color(hex: strokeColor) {
|
|
202
|
+
Circle().stroke(strokeColor, lineWidth: strokeWidth)
|
|
203
|
+
.background(Circle().fill(Color(hex: fillColor) ?? .clear))
|
|
204
|
+
} else {
|
|
205
|
+
Circle().fill(Color(hex: fillColor) ?? .clear)
|
|
206
|
+
}
|
|
207
|
+
case "capsule":
|
|
208
|
+
if let strokeColor = Color(hex: strokeColor) {
|
|
209
|
+
Capsule().stroke(strokeColor, lineWidth: strokeWidth)
|
|
210
|
+
.background(Capsule().fill(Color(hex: fillColor) ?? .clear))
|
|
211
|
+
} else {
|
|
212
|
+
Capsule().fill(Color(hex: fillColor) ?? .clear)
|
|
213
|
+
}
|
|
214
|
+
case "roundedRectangle":
|
|
215
|
+
let radius = prop("radius")?.doubleValue ?? 8
|
|
216
|
+
if let strokeColor = Color(hex: strokeColor) {
|
|
217
|
+
RoundedRectangle(cornerRadius: radius).stroke(strokeColor, lineWidth: strokeWidth)
|
|
218
|
+
.background(RoundedRectangle(cornerRadius: radius).fill(Color(hex: fillColor) ?? .clear))
|
|
219
|
+
} else {
|
|
220
|
+
RoundedRectangle(cornerRadius: radius).fill(Color(hex: fillColor) ?? .clear)
|
|
221
|
+
}
|
|
222
|
+
default: // rectangle
|
|
223
|
+
if let strokeColor = Color(hex: strokeColor) {
|
|
224
|
+
Rectangle().stroke(strokeColor, lineWidth: strokeWidth)
|
|
225
|
+
.background(Rectangle().fill(Color(hex: fillColor) ?? .clear))
|
|
226
|
+
} else {
|
|
227
|
+
Rectangle().fill(Color(hex: fillColor) ?? .clear)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// MARK: - Container
|
|
233
|
+
|
|
234
|
+
@ViewBuilder
|
|
235
|
+
private func renderContainer() -> some View {
|
|
236
|
+
// Just renders children; style is applied by applyStyle
|
|
237
|
+
VStack(spacing: 0) {
|
|
238
|
+
renderChildren()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// MARK: - Conditional
|
|
243
|
+
|
|
244
|
+
@ViewBuilder
|
|
245
|
+
private func renderConditional() -> some View {
|
|
246
|
+
let condition = prop("condition")?.stringValue ?? ""
|
|
247
|
+
if evaluateCondition(condition) {
|
|
248
|
+
renderChildren()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// MARK: - Children rendering
|
|
253
|
+
|
|
254
|
+
@ViewBuilder
|
|
255
|
+
private func renderChildren() -> some View {
|
|
256
|
+
if let children = node.children {
|
|
257
|
+
ForEach(children) { child in
|
|
258
|
+
DSLView(node: child, data: data)
|
|
259
|
+
.applyAnimation(child.animation)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// MARK: - Style application
|
|
265
|
+
|
|
266
|
+
@ViewBuilder
|
|
267
|
+
private func applyStyle<V: View>(to view: V) -> some View {
|
|
268
|
+
let styled = view
|
|
269
|
+
.modifier(DSLPaddingModifier(padding: node.style?.padding))
|
|
270
|
+
.modifier(DSLFrameModifier(frame: node.style?.frame))
|
|
271
|
+
.modifier(DSLBackgroundModifier(
|
|
272
|
+
backgroundColor: node.style?.backgroundColor,
|
|
273
|
+
backgroundGradient: node.style?.backgroundGradient,
|
|
274
|
+
cornerRadius: node.style?.cornerRadius
|
|
275
|
+
))
|
|
276
|
+
.modifier(DSLForegroundModifier(foregroundColor: node.style?.foregroundColor))
|
|
277
|
+
.modifier(DSLOpacityModifier(opacity: node.style?.opacity))
|
|
278
|
+
.modifier(DSLShadowModifier(shadow: node.style?.shadow))
|
|
279
|
+
|
|
280
|
+
if node.style?.cornerRadius != nil && node.style?.backgroundColor == nil && node.style?.backgroundGradient == nil {
|
|
281
|
+
styled.clipShape(RoundedRectangle(cornerRadius: node.style!.cornerRadius!))
|
|
282
|
+
} else {
|
|
283
|
+
styled
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// MARK: - Helpers
|
|
288
|
+
|
|
289
|
+
private func prop(_ key: String) -> AnyCodable? {
|
|
290
|
+
node.props?[key]
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// Replace `{{varName}}` with data dictionary values.
|
|
294
|
+
private func interpolate(_ text: String) -> String {
|
|
295
|
+
var result = text
|
|
296
|
+
let pattern = "\\{\\{(\\w+)\\}\\}"
|
|
297
|
+
guard let regex = try? NSRegularExpression(pattern: pattern) else { return text }
|
|
298
|
+
let matches = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
|
|
299
|
+
|
|
300
|
+
for match in matches.reversed() {
|
|
301
|
+
guard let keyRange = Range(match.range(at: 1), in: text),
|
|
302
|
+
let fullRange = Range(match.range, in: text) else { continue }
|
|
303
|
+
let key = String(text[keyRange])
|
|
304
|
+
if let replacement = data[key]?.stringValue ?? data[key]?.doubleValue.map(String.init) ?? data[key]?.intValue.map(String.init) {
|
|
305
|
+
result = result.replacingCharacters(in: fullRange, with: replacement)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return result
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// Evaluate simple conditions like `{{status}} == 'active'`
|
|
312
|
+
private func evaluateCondition(_ condition: String) -> Bool {
|
|
313
|
+
let interpolated = interpolate(condition)
|
|
314
|
+
// Support: `value == 'literal'` or `value != 'literal'`
|
|
315
|
+
if interpolated.contains("!=") {
|
|
316
|
+
let parts = interpolated.components(separatedBy: "!=").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
317
|
+
guard parts.count == 2 else { return false }
|
|
318
|
+
return stripQuotes(parts[0]) != stripQuotes(parts[1])
|
|
319
|
+
} else if interpolated.contains("==") {
|
|
320
|
+
let parts = interpolated.components(separatedBy: "==").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
321
|
+
guard parts.count == 2 else { return false }
|
|
322
|
+
return stripQuotes(parts[0]) == stripQuotes(parts[1])
|
|
323
|
+
}
|
|
324
|
+
// Truthy check: non-empty string
|
|
325
|
+
return !interpolated.trimmingCharacters(in: .whitespaces).isEmpty
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private func stripQuotes(_ s: String) -> String {
|
|
329
|
+
var result = s
|
|
330
|
+
if (result.hasPrefix("'") && result.hasSuffix("'")) ||
|
|
331
|
+
(result.hasPrefix("\"") && result.hasSuffix("\"")) {
|
|
332
|
+
result.removeFirst()
|
|
333
|
+
result.removeLast()
|
|
334
|
+
}
|
|
335
|
+
return result
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// Map font preset names to sizes.
|
|
339
|
+
private func fontPresetSize(_ preset: String?) -> Double? {
|
|
340
|
+
switch preset {
|
|
341
|
+
case "largeTitle": return 34
|
|
342
|
+
case "title": return 28
|
|
343
|
+
case "title2": return 22
|
|
344
|
+
case "title3": return 20
|
|
345
|
+
case "headline": return 17
|
|
346
|
+
case "body": return 17
|
|
347
|
+
case "callout": return 16
|
|
348
|
+
case "subheadline": return 15
|
|
349
|
+
case "footnote": return 13
|
|
350
|
+
case "caption": return 12
|
|
351
|
+
case "caption2": return 11
|
|
352
|
+
default: return nil
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// MARK: - Alignment helpers
|
|
358
|
+
|
|
359
|
+
private func horizontalAlignment(from name: String?) -> HorizontalAlignment {
|
|
360
|
+
switch name {
|
|
361
|
+
case "center": return .center
|
|
362
|
+
case "trailing": return .trailing
|
|
363
|
+
case "leading": return .leading
|
|
364
|
+
default: return .leading
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private func verticalAlignment(from name: String?) -> VerticalAlignment {
|
|
369
|
+
switch name {
|
|
370
|
+
case "top": return .top
|
|
371
|
+
case "bottom": return .bottom
|
|
372
|
+
case "center": return .center
|
|
373
|
+
default: return .center
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private func compositeAlignment(from name: String?) -> Alignment {
|
|
378
|
+
switch name {
|
|
379
|
+
case "topLeading": return .topLeading
|
|
380
|
+
case "top": return .top
|
|
381
|
+
case "topTrailing": return .topTrailing
|
|
382
|
+
case "leading": return .leading
|
|
383
|
+
case "center": return .center
|
|
384
|
+
case "trailing": return .trailing
|
|
385
|
+
case "bottomLeading": return .bottomLeading
|
|
386
|
+
case "bottom": return .bottom
|
|
387
|
+
case "bottomTrailing": return .bottomTrailing
|
|
388
|
+
default: return .center
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// MARK: - Style modifiers
|
|
393
|
+
|
|
394
|
+
@available(iOS 16.2, *)
|
|
395
|
+
struct DSLPaddingModifier: ViewModifier {
|
|
396
|
+
let padding: DSLPadding?
|
|
397
|
+
|
|
398
|
+
func body(content: Content) -> some View {
|
|
399
|
+
guard let p = padding else { return AnyView(content) }
|
|
400
|
+
|
|
401
|
+
if let all = p.all {
|
|
402
|
+
return AnyView(content.padding(all))
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
var view = AnyView(content)
|
|
406
|
+
if let h = p.horizontal {
|
|
407
|
+
view = AnyView(view.padding(.horizontal, h))
|
|
408
|
+
}
|
|
409
|
+
if let v = p.vertical {
|
|
410
|
+
view = AnyView(view.padding(.vertical, v))
|
|
411
|
+
}
|
|
412
|
+
if let t = p.top {
|
|
413
|
+
view = AnyView(view.padding(.top, t))
|
|
414
|
+
}
|
|
415
|
+
if let b = p.bottom {
|
|
416
|
+
view = AnyView(view.padding(.bottom, b))
|
|
417
|
+
}
|
|
418
|
+
if let l = p.leading {
|
|
419
|
+
view = AnyView(view.padding(.leading, l))
|
|
420
|
+
}
|
|
421
|
+
if let tr = p.trailing {
|
|
422
|
+
view = AnyView(view.padding(.trailing, tr))
|
|
423
|
+
}
|
|
424
|
+
return view
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@available(iOS 16.2, *)
|
|
429
|
+
struct DSLFrameModifier: ViewModifier {
|
|
430
|
+
let frame: DSLFrame?
|
|
431
|
+
|
|
432
|
+
func body(content: Content) -> some View {
|
|
433
|
+
guard let f = frame else { return AnyView(content) }
|
|
434
|
+
|
|
435
|
+
if f.width != nil || f.height != nil {
|
|
436
|
+
return AnyView(content.frame(
|
|
437
|
+
width: f.width.map { CGFloat($0) },
|
|
438
|
+
height: f.height.map { CGFloat($0) }
|
|
439
|
+
))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return AnyView(content.frame(
|
|
443
|
+
minWidth: f.minWidth.map { CGFloat($0) },
|
|
444
|
+
maxWidth: f.maxWidth.map { CGFloat($0) },
|
|
445
|
+
minHeight: f.minHeight.map { CGFloat($0) },
|
|
446
|
+
maxHeight: f.maxHeight.map { CGFloat($0) }
|
|
447
|
+
))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@available(iOS 16.2, *)
|
|
452
|
+
struct DSLBackgroundModifier: ViewModifier {
|
|
453
|
+
let backgroundColor: String?
|
|
454
|
+
let backgroundGradient: DSLGradient?
|
|
455
|
+
let cornerRadius: Double?
|
|
456
|
+
|
|
457
|
+
func body(content: Content) -> some View {
|
|
458
|
+
let radius = cornerRadius ?? 0
|
|
459
|
+
|
|
460
|
+
if let gradient = backgroundGradient, !gradient.colors.isEmpty {
|
|
461
|
+
let colors = gradient.colors.compactMap { Color(hex: $0) }
|
|
462
|
+
let start = unitPoint(from: gradient.startPoint ?? "top")
|
|
463
|
+
let end = unitPoint(from: gradient.endPoint ?? "bottom")
|
|
464
|
+
return AnyView(
|
|
465
|
+
content.background(
|
|
466
|
+
LinearGradient(colors: colors, startPoint: start, endPoint: end)
|
|
467
|
+
.clipShape(RoundedRectangle(cornerRadius: radius))
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
} else if let bgColor = Color(hex: backgroundColor) {
|
|
471
|
+
return AnyView(
|
|
472
|
+
content.background(
|
|
473
|
+
bgColor.clipShape(RoundedRectangle(cornerRadius: radius))
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
return AnyView(content)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private func unitPoint(from name: String) -> UnitPoint {
|
|
481
|
+
switch name {
|
|
482
|
+
case "top": return .top
|
|
483
|
+
case "bottom": return .bottom
|
|
484
|
+
case "leading": return .leading
|
|
485
|
+
case "trailing": return .trailing
|
|
486
|
+
case "topLeading": return .topLeading
|
|
487
|
+
case "topTrailing": return .topTrailing
|
|
488
|
+
case "bottomLeading": return .bottomLeading
|
|
489
|
+
case "bottomTrailing": return .bottomTrailing
|
|
490
|
+
case "center": return .center
|
|
491
|
+
default: return .top
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@available(iOS 16.2, *)
|
|
497
|
+
struct DSLForegroundModifier: ViewModifier {
|
|
498
|
+
let foregroundColor: String?
|
|
499
|
+
|
|
500
|
+
func body(content: Content) -> some View {
|
|
501
|
+
if let color = Color(hex: foregroundColor) {
|
|
502
|
+
AnyView(content.foregroundStyle(color))
|
|
503
|
+
} else {
|
|
504
|
+
AnyView(content)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
@available(iOS 16.2, *)
|
|
510
|
+
struct DSLOpacityModifier: ViewModifier {
|
|
511
|
+
let opacity: Double?
|
|
512
|
+
|
|
513
|
+
func body(content: Content) -> some View {
|
|
514
|
+
if let opacity = opacity {
|
|
515
|
+
content.opacity(opacity)
|
|
516
|
+
} else {
|
|
517
|
+
content
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
@available(iOS 16.2, *)
|
|
523
|
+
struct DSLShadowModifier: ViewModifier {
|
|
524
|
+
let shadow: DSLShadow?
|
|
525
|
+
|
|
526
|
+
func body(content: Content) -> some View {
|
|
527
|
+
if let s = shadow {
|
|
528
|
+
content.shadow(
|
|
529
|
+
color: Color(hex: s.color) ?? .black.opacity(0.2),
|
|
530
|
+
radius: s.radius ?? 4,
|
|
531
|
+
x: s.x ?? 0,
|
|
532
|
+
y: s.y ?? 2
|
|
533
|
+
)
|
|
534
|
+
} else {
|
|
535
|
+
content
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|