@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/ios",
3
- "version": "0.0.210",
3
+ "version": "0.0.211",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/ios#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
- Image(systemName: symbol(context)).font(.title2).foregroundStyle(tint(context))
14
+ activityIcon(context).font(.title2)
15
15
  VStack(alignment: .leading, spacing: 4) {
16
- Text(context.attributes.title).font(.headline).lineLimit(1)
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
- Image(systemName: symbol(context)).foregroundStyle(tint(context)).font(.title2)
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(1)
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
- Image(systemName: symbol(context)).foregroundStyle(tint(context))
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
- Image(systemName: symbol(context)).foregroundStyle(tint(context))
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 = true
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
- if let target = poll(10, { element(containing: arg, in: springboard) }) { press(target); note("systemtap \(arg): ok") }
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")