sitepong 0.2.3 → 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 +17 -10
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"platforms": ["ios", "android"],
|
|
3
|
+
"ios": {
|
|
4
|
+
"modules": [
|
|
5
|
+
"SitePongScreenRecorderModule",
|
|
6
|
+
"SitePongDeviceIdModule",
|
|
7
|
+
"SitePongLiveActivityModule"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"android": {
|
|
11
|
+
"modules": [
|
|
12
|
+
"com.sitepong.screenrecorder.ScreenRecorderModule",
|
|
13
|
+
"com.sitepong.deviceid.DeviceIdModule"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
public class SitePongDeviceIdModule: Module {
|
|
5
|
+
public func definition() -> ModuleDefinition {
|
|
6
|
+
Name("SitePongDeviceId")
|
|
7
|
+
|
|
8
|
+
AsyncFunction("getDeviceId") { (promise: Promise) in
|
|
9
|
+
let deviceId = KeychainHelper.getOrCreateDeviceId()
|
|
10
|
+
promise.resolve(deviceId)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
AsyncFunction("getDeviceSignals") { (promise: Promise) in
|
|
14
|
+
let deviceId = KeychainHelper.getOrCreateDeviceId()
|
|
15
|
+
let device = UIDevice.current
|
|
16
|
+
let screen = UIScreen.main
|
|
17
|
+
|
|
18
|
+
var signals: [String: Any] = [
|
|
19
|
+
"deviceId": deviceId,
|
|
20
|
+
"platform": "ios",
|
|
21
|
+
"osVersion": device.systemVersion,
|
|
22
|
+
"model": self.modelIdentifier(),
|
|
23
|
+
"manufacturer": "Apple",
|
|
24
|
+
"isEmulator": self.isSimulator(),
|
|
25
|
+
"screenScale": screen.scale,
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
if let idfv = device.identifierForVendor?.uuidString {
|
|
29
|
+
signals["identifierForVendor"] = idfv
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
promise.resolve(signals)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Returns the hardware model identifier (e.g. "iPhone15,2")
|
|
37
|
+
private func modelIdentifier() -> String {
|
|
38
|
+
var systemInfo = utsname()
|
|
39
|
+
uname(&systemInfo)
|
|
40
|
+
let mirror = Mirror(reflecting: systemInfo.machine)
|
|
41
|
+
let identifier = mirror.children.reduce("") { identifier, element in
|
|
42
|
+
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
|
43
|
+
return identifier + String(UnicodeScalar(UInt8(value)))
|
|
44
|
+
}
|
|
45
|
+
return identifier
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func isSimulator() -> Bool {
|
|
49
|
+
#if targetEnvironment(simulator)
|
|
50
|
+
return true
|
|
51
|
+
#else
|
|
52
|
+
return false
|
|
53
|
+
#endif
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
/// Manages a persistent UUID in the iOS Keychain.
|
|
5
|
+
/// Keychain items survive app uninstall/reinstall — they persist until the device
|
|
6
|
+
/// is factory-reset or the item is explicitly deleted.
|
|
7
|
+
final class KeychainHelper {
|
|
8
|
+
private static let service = "com.sitepong.device-id"
|
|
9
|
+
private static let account = "persistent_uuid"
|
|
10
|
+
|
|
11
|
+
/// Read or create the persistent device UUID from Keychain.
|
|
12
|
+
static func getOrCreateDeviceId() -> String {
|
|
13
|
+
// Try to read existing
|
|
14
|
+
if let existing = read() {
|
|
15
|
+
return existing
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Generate new UUID and store it
|
|
19
|
+
let uuid = UUID().uuidString
|
|
20
|
+
save(uuid)
|
|
21
|
+
return uuid
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private static func read() -> String? {
|
|
25
|
+
let query: [String: Any] = [
|
|
26
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
27
|
+
kSecAttrService as String: service,
|
|
28
|
+
kSecAttrAccount as String: account,
|
|
29
|
+
kSecReturnData as String: true,
|
|
30
|
+
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
var item: CFTypeRef?
|
|
34
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
35
|
+
|
|
36
|
+
guard status == errSecSuccess,
|
|
37
|
+
let data = item as? Data,
|
|
38
|
+
let value = String(data: data, encoding: .utf8) else {
|
|
39
|
+
return nil
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private static func save(_ value: String) {
|
|
46
|
+
guard let data = value.data(using: .utf8) else { return }
|
|
47
|
+
|
|
48
|
+
let query: [String: Any] = [
|
|
49
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
50
|
+
kSecAttrService as String: service,
|
|
51
|
+
kSecAttrAccount as String: account,
|
|
52
|
+
kSecValueData as String: data,
|
|
53
|
+
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
// Delete any existing item first (in case of corruption)
|
|
57
|
+
SecItemDelete([
|
|
58
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
59
|
+
kSecAttrService as String: service,
|
|
60
|
+
kSecAttrAccount as String: account,
|
|
61
|
+
] as CFDictionary)
|
|
62
|
+
|
|
63
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
64
|
+
if status != errSecSuccess {
|
|
65
|
+
print("[SitePong DeviceId] Keychain save failed: \(status)")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Reads and writes widget DSL configs via App Group UserDefaults.
|
|
4
|
+
/// Shared between the main app (Expo module) and the widget extension.
|
|
5
|
+
struct WidgetConfigStore {
|
|
6
|
+
private static let appGroupId = "group.com.sitepong.widgets"
|
|
7
|
+
private static let configsKey = "sitepong_widget_configs"
|
|
8
|
+
private static let dataKey = "sitepong_widget_data"
|
|
9
|
+
|
|
10
|
+
private static var defaults: UserDefaults? {
|
|
11
|
+
UserDefaults(suiteName: appGroupId)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// MARK: - Config (DSLNode trees)
|
|
15
|
+
|
|
16
|
+
/// Save raw config JSON data to the shared App Group container.
|
|
17
|
+
static func saveConfigs(_ configs: Data) {
|
|
18
|
+
defaults?.set(configs, forKey: configsKey)
|
|
19
|
+
defaults?.synchronize()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Load and decode widget configs from the App Group container.
|
|
23
|
+
static func loadConfigs() -> [DSLNode]? {
|
|
24
|
+
guard let data = defaults?.data(forKey: configsKey) else { return nil }
|
|
25
|
+
let decoder = JSONDecoder()
|
|
26
|
+
return try? decoder.decode([DSLNode].self, from: data)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - Data (variable values for interpolation)
|
|
30
|
+
|
|
31
|
+
/// Save variable data JSON for widget rendering.
|
|
32
|
+
static func saveData(_ data: Data) {
|
|
33
|
+
defaults?.set(data, forKey: dataKey)
|
|
34
|
+
defaults?.synchronize()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Load variable data dictionary from the App Group container.
|
|
38
|
+
static func loadData() -> [String: AnyCodable] {
|
|
39
|
+
guard let data = defaults?.data(forKey: dataKey) else { return [:] }
|
|
40
|
+
let decoder = JSONDecoder()
|
|
41
|
+
return (try? decoder.decode([String: AnyCodable].self, from: data)) ?? [:]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// MARK: - AnyCodable
|
|
4
|
+
|
|
5
|
+
/// A simple JSON-compatible value wrapper with typed accessors.
|
|
6
|
+
public struct AnyCodable: Codable, Hashable {
|
|
7
|
+
public let value: Any
|
|
8
|
+
|
|
9
|
+
public init(_ value: Any) {
|
|
10
|
+
self.value = value
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public var stringValue: String? { value as? String }
|
|
14
|
+
public var doubleValue: Double? {
|
|
15
|
+
if let d = value as? Double { return d }
|
|
16
|
+
if let i = value as? Int { return Double(i) }
|
|
17
|
+
if let s = value as? String { return Double(s) }
|
|
18
|
+
return nil
|
|
19
|
+
}
|
|
20
|
+
public var intValue: Int? {
|
|
21
|
+
if let i = value as? Int { return i }
|
|
22
|
+
if let d = value as? Double { return Int(d) }
|
|
23
|
+
return nil
|
|
24
|
+
}
|
|
25
|
+
public var boolValue: Bool? { value as? Bool }
|
|
26
|
+
public var arrayValue: [AnyCodable]? { value as? [AnyCodable] }
|
|
27
|
+
public var dictionaryValue: [String: AnyCodable]? { value as? [String: AnyCodable] }
|
|
28
|
+
|
|
29
|
+
// MARK: - Codable
|
|
30
|
+
|
|
31
|
+
public init(from decoder: Decoder) throws {
|
|
32
|
+
let container = try decoder.singleValueContainer()
|
|
33
|
+
if container.decodeNil() {
|
|
34
|
+
value = NSNull()
|
|
35
|
+
} else if let b = try? container.decode(Bool.self) {
|
|
36
|
+
value = b
|
|
37
|
+
} else if let i = try? container.decode(Int.self) {
|
|
38
|
+
value = i
|
|
39
|
+
} else if let d = try? container.decode(Double.self) {
|
|
40
|
+
value = d
|
|
41
|
+
} else if let s = try? container.decode(String.self) {
|
|
42
|
+
value = s
|
|
43
|
+
} else if let arr = try? container.decode([AnyCodable].self) {
|
|
44
|
+
value = arr
|
|
45
|
+
} else if let dict = try? container.decode([String: AnyCodable].self) {
|
|
46
|
+
value = dict
|
|
47
|
+
} else {
|
|
48
|
+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON value")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public func encode(to encoder: Encoder) throws {
|
|
53
|
+
var container = encoder.singleValueContainer()
|
|
54
|
+
if value is NSNull {
|
|
55
|
+
try container.encodeNil()
|
|
56
|
+
} else if let b = value as? Bool {
|
|
57
|
+
try container.encode(b)
|
|
58
|
+
} else if let i = value as? Int {
|
|
59
|
+
try container.encode(i)
|
|
60
|
+
} else if let d = value as? Double {
|
|
61
|
+
try container.encode(d)
|
|
62
|
+
} else if let s = value as? String {
|
|
63
|
+
try container.encode(s)
|
|
64
|
+
} else if let arr = value as? [AnyCodable] {
|
|
65
|
+
try container.encode(arr)
|
|
66
|
+
} else if let dict = value as? [String: AnyCodable] {
|
|
67
|
+
try container.encode(dict)
|
|
68
|
+
} else {
|
|
69
|
+
try container.encodeNil()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// MARK: - Hashable
|
|
74
|
+
|
|
75
|
+
public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
|
|
76
|
+
// Use JSON encoding for equality comparison
|
|
77
|
+
let encoder = JSONEncoder()
|
|
78
|
+
encoder.outputFormatting = .sortedKeys
|
|
79
|
+
guard let lData = try? encoder.encode(lhs),
|
|
80
|
+
let rData = try? encoder.encode(rhs) else {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
return lData == rData
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public func hash(into hasher: inout Hasher) {
|
|
87
|
+
let encoder = JSONEncoder()
|
|
88
|
+
encoder.outputFormatting = .sortedKeys
|
|
89
|
+
if let data = try? encoder.encode(self) {
|
|
90
|
+
hasher.combine(data)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MARK: - DSLNode
|
|
96
|
+
|
|
97
|
+
public struct DSLNode: Codable, Hashable, Identifiable {
|
|
98
|
+
public var id: String
|
|
99
|
+
public var type: String
|
|
100
|
+
public var props: [String: AnyCodable]?
|
|
101
|
+
public var style: DSLStyle?
|
|
102
|
+
public var animation: DSLAnimation?
|
|
103
|
+
public var children: [DSLNode]?
|
|
104
|
+
public var binding: String?
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - DSLStyle
|
|
108
|
+
|
|
109
|
+
public struct DSLStyle: Codable, Hashable {
|
|
110
|
+
public var padding: DSLPadding?
|
|
111
|
+
public var backgroundColor: String?
|
|
112
|
+
public var backgroundGradient: DSLGradient?
|
|
113
|
+
public var cornerRadius: Double?
|
|
114
|
+
public var opacity: Double?
|
|
115
|
+
public var shadow: DSLShadow?
|
|
116
|
+
public var frame: DSLFrame?
|
|
117
|
+
public var foregroundColor: String?
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public struct DSLPadding: Codable, Hashable {
|
|
121
|
+
public var all: Double?
|
|
122
|
+
public var horizontal: Double?
|
|
123
|
+
public var vertical: Double?
|
|
124
|
+
public var top: Double?
|
|
125
|
+
public var bottom: Double?
|
|
126
|
+
public var leading: Double?
|
|
127
|
+
public var trailing: Double?
|
|
128
|
+
|
|
129
|
+
public init(from decoder: Decoder) throws {
|
|
130
|
+
let container = try decoder.singleValueContainer()
|
|
131
|
+
if let single = try? container.decode(Double.self) {
|
|
132
|
+
self.all = single
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if let array = try? container.decode([Double].self), array.count == 4 {
|
|
136
|
+
self.top = array[0]
|
|
137
|
+
self.trailing = array[1]
|
|
138
|
+
self.bottom = array[2]
|
|
139
|
+
self.leading = array[3]
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
let keyed = try decoder.container(keyedBy: CodingKeys.self)
|
|
143
|
+
self.all = try keyed.decodeIfPresent(Double.self, forKey: .all)
|
|
144
|
+
self.horizontal = try keyed.decodeIfPresent(Double.self, forKey: .horizontal)
|
|
145
|
+
self.vertical = try keyed.decodeIfPresent(Double.self, forKey: .vertical)
|
|
146
|
+
self.top = try keyed.decodeIfPresent(Double.self, forKey: .top)
|
|
147
|
+
self.bottom = try keyed.decodeIfPresent(Double.self, forKey: .bottom)
|
|
148
|
+
self.leading = try keyed.decodeIfPresent(Double.self, forKey: .leading)
|
|
149
|
+
self.trailing = try keyed.decodeIfPresent(Double.self, forKey: .trailing)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private enum CodingKeys: String, CodingKey {
|
|
153
|
+
case all, horizontal, vertical, top, bottom, leading, trailing
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public struct DSLGradient: Codable, Hashable {
|
|
158
|
+
public var colors: [String]
|
|
159
|
+
public var startPoint: String?
|
|
160
|
+
public var endPoint: String?
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public struct DSLShadow: Codable, Hashable {
|
|
164
|
+
public var color: String?
|
|
165
|
+
public var radius: Double?
|
|
166
|
+
public var x: Double?
|
|
167
|
+
public var y: Double?
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public struct DSLFrame: Codable, Hashable {
|
|
171
|
+
public var width: Double?
|
|
172
|
+
public var height: Double?
|
|
173
|
+
public var minWidth: Double?
|
|
174
|
+
public var minHeight: Double?
|
|
175
|
+
public var maxWidth: Double?
|
|
176
|
+
public var maxHeight: Double?
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// MARK: - DSLAnimation
|
|
180
|
+
|
|
181
|
+
public struct DSLAnimation: Codable, Hashable {
|
|
182
|
+
public var entrance: String?
|
|
183
|
+
public var entranceDuration: Double?
|
|
184
|
+
public var entranceDelay: Double?
|
|
185
|
+
public var valueTransition: String?
|
|
186
|
+
public var ambient: String?
|
|
187
|
+
public var ambientDuration: Double?
|
|
188
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Single shared ActivityAttributes type used by every SitePong Live Activity.
|
|
5
|
+
///
|
|
6
|
+
/// Every visual element is optional — the SwiftUI template hides slots whose
|
|
7
|
+
/// value is nil, so customers can drive layout density entirely from data
|
|
8
|
+
/// without modifying the widget.
|
|
9
|
+
@available(iOS 16.2, *)
|
|
10
|
+
public struct SitePongActivityAttributes: ActivityAttributes, Codable {
|
|
11
|
+
public typealias ContentState = State
|
|
12
|
+
|
|
13
|
+
public struct State: Codable, Hashable {
|
|
14
|
+
public var title: String?
|
|
15
|
+
public var subtitle: String?
|
|
16
|
+
public var titleStyle: TextStyleSpec?
|
|
17
|
+
public var subtitleStyle: TextStyleSpec?
|
|
18
|
+
|
|
19
|
+
public var leadingIcon: IconSpec?
|
|
20
|
+
public var trailingIcon: IconSpec?
|
|
21
|
+
|
|
22
|
+
public var progress: ProgressSpec?
|
|
23
|
+
|
|
24
|
+
public var backgroundColor: String?
|
|
25
|
+
|
|
26
|
+
public var dynamicIsland: DynamicIslandSpec?
|
|
27
|
+
|
|
28
|
+
/// Optional DSL layout. When non-nil, the renderer uses DSLView
|
|
29
|
+
/// instead of the fixed slot-based layout.
|
|
30
|
+
public var dslLayout: DSLNode?
|
|
31
|
+
|
|
32
|
+
public init(
|
|
33
|
+
title: String? = nil,
|
|
34
|
+
subtitle: String? = nil,
|
|
35
|
+
titleStyle: TextStyleSpec? = nil,
|
|
36
|
+
subtitleStyle: TextStyleSpec? = nil,
|
|
37
|
+
leadingIcon: IconSpec? = nil,
|
|
38
|
+
trailingIcon: IconSpec? = nil,
|
|
39
|
+
progress: ProgressSpec? = nil,
|
|
40
|
+
backgroundColor: String? = nil,
|
|
41
|
+
dynamicIsland: DynamicIslandSpec? = nil,
|
|
42
|
+
dslLayout: DSLNode? = nil
|
|
43
|
+
) {
|
|
44
|
+
self.title = title
|
|
45
|
+
self.subtitle = subtitle
|
|
46
|
+
self.titleStyle = titleStyle
|
|
47
|
+
self.subtitleStyle = subtitleStyle
|
|
48
|
+
self.leadingIcon = leadingIcon
|
|
49
|
+
self.trailingIcon = trailingIcon
|
|
50
|
+
self.progress = progress
|
|
51
|
+
self.backgroundColor = backgroundColor
|
|
52
|
+
self.dynamicIsland = dynamicIsland
|
|
53
|
+
self.dslLayout = dslLayout
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public var id: String?
|
|
58
|
+
public var metadata: [String: String]?
|
|
59
|
+
|
|
60
|
+
public init(id: String? = nil, metadata: [String: String]? = nil) {
|
|
61
|
+
self.id = id
|
|
62
|
+
self.metadata = metadata
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - Visual specs
|
|
67
|
+
|
|
68
|
+
public struct IconSpec: Codable, Hashable {
|
|
69
|
+
public var symbol: String
|
|
70
|
+
public var color: String?
|
|
71
|
+
public var weight: String?
|
|
72
|
+
public var size: Double?
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public struct TextStyleSpec: Codable, Hashable {
|
|
76
|
+
public var size: Double?
|
|
77
|
+
public var weight: String?
|
|
78
|
+
public var color: String?
|
|
79
|
+
public var italic: Bool?
|
|
80
|
+
public var monospacedDigit: Bool?
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public struct ProgressSpec: Codable, Hashable {
|
|
84
|
+
/// "bar", "bar-with-icon", "circular", "timer"
|
|
85
|
+
public var style: String
|
|
86
|
+
|
|
87
|
+
public var value: Double?
|
|
88
|
+
public var icon: String?
|
|
89
|
+
public var iconColor: String?
|
|
90
|
+
public var iconWeight: String?
|
|
91
|
+
public var trackColor: String?
|
|
92
|
+
public var fillColor: String?
|
|
93
|
+
public var label: String?
|
|
94
|
+
public var labelStyle: TextStyleSpec?
|
|
95
|
+
|
|
96
|
+
/// Epoch milliseconds for "timer" style.
|
|
97
|
+
public var endDate: Double?
|
|
98
|
+
public var startDate: Double?
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public struct DynamicIslandSpec: Codable, Hashable {
|
|
102
|
+
public var compactLeading: DynamicIslandSlot?
|
|
103
|
+
public var compactTrailing: DynamicIslandSlot?
|
|
104
|
+
public var minimal: IconSpec?
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public struct DynamicIslandSlot: Codable, Hashable {
|
|
108
|
+
// One of `icon` or `text` is set.
|
|
109
|
+
public var icon: IconSpec?
|
|
110
|
+
public var text: String?
|
|
111
|
+
public var style: TextStyleSpec?
|
|
112
|
+
}
|