@craft-native/ios 0.0.210 → 0.0.211
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/README.md
CHANGED
|
@@ -187,6 +187,10 @@ under test is activated by its bundle identifier and is not rebuilt by the
|
|
|
187
187
|
driver. Export evidence with `xcrun xcresulttool export attachments --path
|
|
188
188
|
<result.xcresult> --output-path <evidence>`. Omitting `script` restores the
|
|
189
189
|
control-server protocol, required for the `net`, `delay` and `exec` hooks.
|
|
190
|
+
Journeys stop at the first failed check so later actions cannot operate on the
|
|
191
|
+
wrong screen. `systemtap` targets the system button around a Live Activity rather
|
|
192
|
+
than its remotely hosted text. The [Live Activity layout fixture](./fixtures/live-activity)
|
|
193
|
+
exercises long names and timer alignment using the production widget.
|
|
190
194
|
|
|
191
195
|
The iOS host uses the shared tree-layout ABI for eligible definite-size,
|
|
192
196
|
non-grid, non-wrapped containers and retains a local fallback for older
|
package/package.json
CHANGED
|
@@ -11,9 +11,9 @@ struct CraftLiveActivityWidget: Widget {
|
|
|
11
11
|
var body: some WidgetConfiguration {
|
|
12
12
|
ActivityConfiguration(for: CraftActivityAttributes.self) { context in
|
|
13
13
|
HStack(spacing: 12) {
|
|
14
|
-
|
|
14
|
+
activityIcon(context).font(.title2)
|
|
15
15
|
VStack(alignment: .leading, spacing: 4) {
|
|
16
|
-
Text(context.attributes.title).font(.headline).lineLimit(
|
|
16
|
+
Text(context.attributes.title).font(.headline).lineLimit(2)
|
|
17
17
|
if let detail = context.state.detail, !detail.isEmpty {
|
|
18
18
|
Text(detail).font(.subheadline).lineLimit(1)
|
|
19
19
|
}
|
|
@@ -33,14 +33,14 @@ struct CraftLiveActivityWidget: Widget {
|
|
|
33
33
|
} dynamicIsland: { context in
|
|
34
34
|
DynamicIsland {
|
|
35
35
|
DynamicIslandExpandedRegion(.leading) {
|
|
36
|
-
|
|
36
|
+
activityIcon(context).font(.title2)
|
|
37
37
|
}
|
|
38
38
|
DynamicIslandExpandedRegion(.trailing) {
|
|
39
39
|
timer(context).font(.title2.monospacedDigit().weight(.semibold))
|
|
40
40
|
}
|
|
41
41
|
DynamicIslandExpandedRegion(.center) {
|
|
42
42
|
VStack(spacing: 3) {
|
|
43
|
-
Text(context.attributes.title).font(.headline).lineLimit(
|
|
43
|
+
Text(context.attributes.title).font(.headline).lineLimit(2)
|
|
44
44
|
Text(context.isStale ? context.state.staleStatus ?? context.state.status : context.state.status).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -59,17 +59,26 @@ struct CraftLiveActivityWidget: Widget {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
} compactLeading: {
|
|
62
|
-
|
|
62
|
+
activityIcon(context)
|
|
63
63
|
} compactTrailing: {
|
|
64
|
-
timer(context).monospacedDigit().frame(maxWidth: 64)
|
|
64
|
+
timer(context).monospacedDigit().frame(maxWidth: 64, alignment: .trailing)
|
|
65
65
|
} minimal: {
|
|
66
|
-
|
|
66
|
+
activityIcon(context)
|
|
67
67
|
}
|
|
68
68
|
.widgetURL(context.attributes.deepLink.flatMap(URL.init(string:)))
|
|
69
69
|
.keylineTint(tint(context))
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
private func activityIcon(_ context: ActivityViewContext<CraftActivityAttributes>) -> some View {
|
|
74
|
+
// The pause glyph uses the active symbol's footprint, keeping the
|
|
75
|
+
// title's leading edge steady through state changes.
|
|
76
|
+
Image(systemName: context.attributes.symbol ?? "timer")
|
|
77
|
+
.hidden()
|
|
78
|
+
.overlay { Image(systemName: symbol(context)) }
|
|
79
|
+
.foregroundStyle(tint(context))
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
private func symbol(_ context: ActivityViewContext<CraftActivityAttributes>) -> String {
|
|
74
83
|
context.state.paused == true ? "pause.fill" : context.attributes.symbol ?? "timer"
|
|
75
84
|
}
|
|
@@ -82,8 +91,23 @@ struct CraftLiveActivityWidget: Widget {
|
|
|
82
91
|
|
|
83
92
|
private func date(_ milliseconds: Double) -> Date { Date(timeIntervalSince1970: milliseconds / 1000) }
|
|
84
93
|
|
|
85
|
-
@ViewBuilder
|
|
86
94
|
private func timer(_ context: ActivityViewContext<CraftActivityAttributes>) -> some View {
|
|
95
|
+
// Measure the hour format in the inherited native font. A widget's
|
|
96
|
+
// ticking Text is horizontally flexible; an overlay bounds it to this
|
|
97
|
+
// footprint so names retain the same width when the timer pauses.
|
|
98
|
+
Text(duration(max(3600, context.state.durationSeconds, context.state.remainingSeconds ?? 0)))
|
|
99
|
+
.lineLimit(1)
|
|
100
|
+
.hidden()
|
|
101
|
+
.overlay(alignment: .trailing) {
|
|
102
|
+
timerContent(context)
|
|
103
|
+
.multilineTextAlignment(.trailing)
|
|
104
|
+
.lineLimit(1)
|
|
105
|
+
.minimumScaleFactor(0.8)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@ViewBuilder
|
|
110
|
+
private func timerContent(_ context: ActivityViewContext<CraftActivityAttributes>) -> some View {
|
|
87
111
|
if context.state.paused == true {
|
|
88
112
|
Text(duration(context.state.remainingSeconds ?? context.state.durationSeconds))
|
|
89
113
|
} else if let end = context.state.timerEndsAt {
|
|
@@ -167,7 +167,7 @@ final class DriverTests: XCTestCase {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
func testScript() throws {
|
|
170
|
-
continueAfterFailure =
|
|
170
|
+
continueAfterFailure = false
|
|
171
171
|
let data: Data?
|
|
172
172
|
if let resource = Bundle(for: DriverTests.self).url(forResource: "journey", withExtension: "txt") {
|
|
173
173
|
bundledJourney = true
|
|
@@ -219,7 +219,13 @@ final class DriverTests: XCTestCase {
|
|
|
219
219
|
check(element(containing: arg, in: springboard) == nil, "systemabsent \(arg)")
|
|
220
220
|
case "systemtap":
|
|
221
221
|
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
|
222
|
-
|
|
222
|
+
// Widget text lives in a remote accessibility host. Its frame can
|
|
223
|
+
// be visible while XCTest cannot resolve an activation point. Tap
|
|
224
|
+
// the containing system button (the actual Live Activity card).
|
|
225
|
+
let predicate = NSPredicate(format: "label CONTAINS[c] %@ OR identifier == %@", arg, arg)
|
|
226
|
+
let card = springboard.buttons.containing(predicate).firstMatch
|
|
227
|
+
if card.exists && card.isHittable { press(card); note("systemtap \(arg): ok") }
|
|
228
|
+
else if let target = poll(10, { element(containing: arg, in: springboard) }) { press(target); note("systemtap \(arg): ok") }
|
|
223
229
|
else { XCTFail("System element not found"); note("FAIL systemtap \(arg): NOT FOUND") }
|
|
224
230
|
case "systemtree":
|
|
225
231
|
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|