craft-native 0.0.435 → 0.0.442

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.
Files changed (39) hide show
  1. package/LICENSE.md +30 -0
  2. package/README.md +35 -0
  3. package/dist/android/src/index.d.ts +8 -0
  4. package/dist/android/src/index.js +17 -2
  5. package/dist/android/templates/CraftHealthServices.kt.template +2 -0
  6. package/dist/android/templates/CraftNativeCapabilities.kt.template +42 -1
  7. package/dist/android/templates/CraftNativeMotion.kt.template +269 -0
  8. package/dist/android/templates/CraftNativeServices.kt.template +6 -1
  9. package/dist/android/templates/CraftPassiveSteps.kt.template +159 -0
  10. package/dist/android/templates/CraftRecordingPressure.kt.template +156 -0
  11. package/dist/android/templates/LocationRecordingService.kt.template +77 -9
  12. package/dist/android/templates/MainActivityNative.kt.template +129 -1
  13. package/dist/cli.js +289 -108
  14. package/dist/components/index.d.ts +25 -0
  15. package/dist/index.cjs +207 -27
  16. package/dist/index.d.cts +2 -2
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +321 -140
  19. package/dist/installer-background.d.ts +2 -0
  20. package/dist/installer-layout.d.ts +1 -0
  21. package/dist/ios/src/index.d.ts +10 -0
  22. package/dist/ios/src/index.js +28 -2
  23. package/dist/ios/templates/CraftApp.swift +14 -0
  24. package/dist/ios/templates/CraftHealth.swift +2 -1
  25. package/dist/ios/templates/CraftHealthBackground.swift +308 -0
  26. package/dist/ios/templates/CraftNativeActions.swift +153 -0
  27. package/dist/ios/templates/CraftNativeMutation.swift +18 -3
  28. package/dist/ios/templates/CraftNativePedometer.swift +181 -0
  29. package/dist/ios/templates/CraftNativeScreen.swift +76 -0
  30. package/dist/ios/templates/testing/DriverTests.swift.template +24 -4
  31. package/dist/macos/src/index.js +1 -1
  32. package/dist/macos/src/toolchain.d.ts +1 -1
  33. package/dist/macos/templates/CraftMacApplication.swift +124 -39
  34. package/dist/macos/templates/CraftMacRenderer.swift +169 -37
  35. package/dist/macos/templates/CraftMacRuntime.swift +298 -77
  36. package/dist/macos/templates/CraftNativeMutation.swift +18 -3
  37. package/dist/package.d.ts +40 -0
  38. package/dist/types.d.ts +26 -0
  39. package/package.json +1 -1
@@ -4563,6 +4563,11 @@ class MainActivity : AppCompatActivity() {
4563
4563
  val pressUnderlayShowHandlers = mutableMapOf<View, String>()
4564
4564
  val pressUnderlayHideHandlers = mutableMapOf<View, String>()
4565
4565
  val longPressHandlers = mutableMapOf<View, String>()
4566
+ val accessibilityActionHandlers = mutableMapOf<View, String>()
4567
+ // TalkBack actions are added by id, so removing them on a reconcile
4568
+ // needs the ids back; without this a renamed action stacks up beside
4569
+ // the old one every render.
4570
+ val accessibilityActionIds = mutableMapOf<View, List<Int>>()
4566
4571
  val scrollHandlers = mutableMapOf<View, String>()
4567
4572
  val scrollBeginHandlers = mutableMapOf<View, String>()
4568
4573
  val scrollEndHandlers = mutableMapOf<View, String>()
@@ -4596,6 +4601,9 @@ class MainActivity : AppCompatActivity() {
4596
4601
  val capabilityTimeouts = mutableMapOf<String, Runnable>()
4597
4602
  val audio = CraftNativeAudio()
4598
4603
  val sounds = CraftNativeSounds(view.context)
4604
+ var motion: CraftNativeMotion? = null
4605
+ var barometer: CraftNativeBarometer? = null
4606
+ var pedometer: CraftNativePedometer? = null
4599
4607
  val soundRequestCancellers = mutableMapOf<String, () -> Unit>()
4600
4608
  /** Snapshot writes awaiting the writer thread; close rejects them once. */
4601
4609
  val pendingSyncResults = mutableSetOf<String>()
@@ -4617,6 +4625,9 @@ class MainActivity : AppCompatActivity() {
4617
4625
  soundRequestCancellers.values.toList().forEach { it() }
4618
4626
  soundRequestCancellers.clear()
4619
4627
  sounds.close()
4628
+ motion?.close()
4629
+ barometer?.close()
4630
+ pedometer?.close()
4620
4631
  // Evaluation completion does not mean its outbound messages have
4621
4632
  // reached the host. Close only after the ordered channel barrier,
4622
4633
  // so onDestroy's final writes run before navigation continues.
@@ -4688,6 +4699,10 @@ class MainActivity : AppCompatActivity() {
4688
4699
  private var hapticsEnabled = false
4689
4700
  private var clipboardEnabled = false
4690
4701
  private var shakeEnabled = false
4702
+ private var motionSensorsEnabled = false
4703
+ private var barometerEnabled = false
4704
+ private var pedometerEnabled = false
4705
+ private var motionForeground = false
4691
4706
  internal var nativeAppState = "inactive"
4692
4707
  private set
4693
4708
  private var deliveredInitialDeepLink = false
@@ -4761,6 +4776,9 @@ class MainActivity : AppCompatActivity() {
4761
4776
  hapticsEnabled = config.optBoolean("enableHaptics", false)
4762
4777
  clipboardEnabled = config.optBoolean("enableClipboard", false)
4763
4778
  shakeEnabled = config.optBoolean("enableShakeGesture", false)
4779
+ motionSensorsEnabled = config.optBoolean("enableMotionSensors", false)
4780
+ barometerEnabled = config.optBoolean("enableBarometer", false)
4781
+ pedometerEnabled = config.optBoolean("enablePedometer", false)
4764
4782
  // Normally started already by its content provider; this covers a
4765
4783
  // process that skipped providers (instrumentation).
4766
4784
  CraftCrashReporter.start(this)
@@ -5153,6 +5171,30 @@ class MainActivity : AppCompatActivity() {
5153
5171
  activeScreen.view.onLayoutChanged = {
5154
5172
  if (activeScreen.active) emitLayoutEvents(activeScreen)
5155
5173
  }
5174
+ if (motionSensorsEnabled && !headless) {
5175
+ activeScreen.motion = CraftNativeMotion(CraftAndroidMotionSource(this)).apply {
5176
+ emit = { detail ->
5177
+ if (activeScreen.active && motionForeground) send(activeScreen, "CRAFT_EVENT",
5178
+ JSONObject().put("event", "craftRawMotionUpdate").put("detail", detail))
5179
+ }
5180
+ }
5181
+ }
5182
+ if (barometerEnabled && !headless) {
5183
+ activeScreen.barometer = CraftNativeBarometer(CraftAndroidMotionSource(this)).apply {
5184
+ emit = { detail ->
5185
+ if (activeScreen.active) send(activeScreen, "CRAFT_EVENT",
5186
+ JSONObject().put("event", "craftBarometerUpdate").put("detail", detail))
5187
+ }
5188
+ }
5189
+ }
5190
+ if (pedometerEnabled && !headless) {
5191
+ activeScreen.pedometer = CraftNativePedometer(CraftAndroidMotionSource(this), capabilities::pedometerPermissionGranted).apply {
5192
+ emit = { detail ->
5193
+ if (activeScreen.active) send(activeScreen, "CRAFT_EVENT",
5194
+ JSONObject().put("event", "craftPedometerUpdate").put("detail", detail))
5195
+ }
5196
+ }
5197
+ }
5156
5198
  activeScreen.sounds.emit = { payload ->
5157
5199
  if (activeScreen.active) send(activeScreen, "SOUND_EVENT", payload)
5158
5200
  }
@@ -5534,6 +5576,9 @@ class MainActivity : AppCompatActivity() {
5534
5576
  private fun capabilityNames(): JSONArray = capabilities.names().apply {
5535
5577
  services.names().forEach { name -> put(name) }
5536
5578
  put("audio")
5579
+ if (motionSensorsEnabled) put("rawMotionSensors")
5580
+ if (barometerEnabled) put("barometer")
5581
+ if (pedometerEnabled) put("pedometer")
5537
5582
  put("viewFocus")
5538
5583
  put("viewMeasure")
5539
5584
  put("viewScroll")
@@ -6952,6 +6997,7 @@ class MainActivity : AppCompatActivity() {
6952
6997
  CraftNativePointers.configure(control, props, style, control is CraftNativeFlexLayout || control is CraftNativeImageBackgroundView ||
6953
6998
  control is CraftNativeKeyboardAvoidingView || control is CraftNativeScrollView || control is CraftNativeRefreshableFlatList || control is CraftNativeModalView)
6954
6999
  applyAccessibility(control, props, type, explicitKey ?: protocolId ?: path)
7000
+ applyAccessibilityActions(screen, control, props, events, longPressDisabled)
6955
7001
  // One element across an animated change (craft.animation.next).
6956
7002
  control.transitionName = craftNativeOptionalString(props.opt("transitionID"))?.takeIf { it.isNotBlank() }
6957
7003
  val layoutHandler = craftNativeEventName(events, "onLayout")
@@ -7854,6 +7900,54 @@ class MainActivity : AppCompatActivity() {
7854
7900
  field.setSelection(minOf(boundedStart, boundedEnd), maxOf(boundedStart, boundedEnd))
7855
7901
  }
7856
7902
 
7903
+ /**
7904
+ * What a hold offers, published where TalkBack can reach it.
7905
+ *
7906
+ * A long click is invisible to a screen reader: nothing announces that it
7907
+ * is there and nothing can perform it, so an action living only behind a
7908
+ * hold does not exist for anyone driving the screen by assistive
7909
+ * technology. A thread that holds a message for Copy and Report is the
7910
+ * case this was written for; without this the honest mitigation is a
7911
+ * permanently visible button under every message.
7912
+ *
7913
+ * Runs after applyAccessibility, which installs its own delegate and
7914
+ * would drop actions added before it.
7915
+ */
7916
+ private fun applyAccessibilityActions(
7917
+ screen: Screen,
7918
+ control: View,
7919
+ props: JSONObject,
7920
+ events: JSONObject,
7921
+ disabled: Boolean,
7922
+ ) {
7923
+ screen.accessibilityActionIds.remove(control)?.forEach { ViewCompat.removeAccessibilityAction(control, it) }
7924
+ val handler = craftNativeEventName(events, "onAccessibilityAction").takeUnless { disabled } ?: ""
7925
+ val authored = props.optJSONArray("accessibilityActions")
7926
+ if (handler.isBlank() || authored == null || authored.length() == 0) {
7927
+ screen.accessibilityActionHandlers.remove(control)
7928
+ return
7929
+ }
7930
+ screen.accessibilityActionHandlers[control] = handler
7931
+ val ids = mutableListOf<Int>()
7932
+ for (index in 0 until authored.length()) {
7933
+ val entry = authored.optJSONObject(index) ?: continue
7934
+ val name = craftNativeOptionalString(entry.opt("name"))?.takeIf { it.isNotBlank() } ?: continue
7935
+ val label = craftNativeOptionalString(entry.opt("label"))?.takeIf { it.isNotBlank() } ?: name
7936
+ ids += ViewCompat.addAccessibilityAction(control, label) { _, _ ->
7937
+ // Read the handler at firing time: a reconcile can rename it
7938
+ // while the action sits in TalkBack's menu.
7939
+ val current = screen.accessibilityActionHandlers[control]
7940
+ if (current.isNullOrBlank()) {
7941
+ false
7942
+ } else {
7943
+ send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("actionName", name)))
7944
+ true
7945
+ }
7946
+ }
7947
+ }
7948
+ if (ids.isEmpty()) screen.accessibilityActionHandlers.remove(control) else screen.accessibilityActionIds[control] = ids
7949
+ }
7950
+
7857
7951
  private fun applyAccessibility(view: View, props: JSONObject, type: String, fallbackLabel: String) {
7858
7952
  val testId = craftNativeOptionalString(props.opt("testID"))
7859
7953
  view.tag = testId?.takeIf { it.isNotBlank() } ?: fallbackLabel
@@ -8039,6 +8133,37 @@ class MainActivity : AppCompatActivity() {
8039
8133
  val method = payload.optString("method")
8040
8134
  val perform: ((CraftNativeCapabilityResult) -> Unit) -> Unit = if (module == "View") {
8041
8135
  { completion -> performView(screen, method, args, completion) }
8136
+ } else if (module == "Pedometer") {
8137
+ { completion ->
8138
+ val pedometer = screen.pedometer
8139
+ if (!pedometerEnabled) completion(CraftNativeCapabilityResult(code = "CAPABILITY_DISABLED", message = "Pedometer is disabled"))
8140
+ else if (pedometer == null) completion(CraftNativeCapabilityResult(code = "NEEDS_ACTIVITY", message = "Pedometer needs a visible app"))
8141
+ else if (method in listOf("start", "requestPermission") && !motionForeground) completion(CraftNativeCapabilityResult(code = "APP_INACTIVE", message = "Pedometer needs a foreground app"))
8142
+ else if (method == "requestPermission") capabilities.perform(token, payload.optInt("version", CRAFT_NATIVE_CAPABILITY_PROTOCOL_VERSION), module, method, args, completion)
8143
+ else completion(pedometer.perform(method, args))
8144
+ }
8145
+ } else if (module == "Barometer") {
8146
+ { completion ->
8147
+ val barometer = screen.barometer
8148
+ completion(if (!barometerEnabled) {
8149
+ CraftNativeCapabilityResult(code = "CAPABILITY_DISABLED", message = "Barometer is disabled")
8150
+ } else if (barometer == null) {
8151
+ CraftNativeCapabilityResult(code = "NEEDS_ACTIVITY", message = "Barometer needs a visible app")
8152
+ } else if (method == "start" && !motionForeground) {
8153
+ CraftNativeCapabilityResult(code = "APP_INACTIVE", message = "Barometer needs a foreground app")
8154
+ } else barometer.perform(method, args))
8155
+ }
8156
+ } else if (module == "MotionSensors") {
8157
+ { completion ->
8158
+ val motion = screen.motion
8159
+ completion(if (!motionSensorsEnabled) {
8160
+ CraftNativeCapabilityResult(code = "CAPABILITY_DISABLED", message = "Motion sensors are disabled")
8161
+ } else if (motion == null) {
8162
+ CraftNativeCapabilityResult(code = "NEEDS_ACTIVITY", message = "Motion sensors need a visible app")
8163
+ } else if (method == "start" && !motionForeground) {
8164
+ CraftNativeCapabilityResult(code = "APP_INACTIVE", message = "Motion sensors need a foreground app")
8165
+ } else motion.perform(method, args))
8166
+ }
8042
8167
  } else if (module == "Audio") {
8043
8168
  { completion ->
8044
8169
  val id = args.optString(0)
@@ -8290,6 +8415,8 @@ class MainActivity : AppCompatActivity() {
8290
8415
  }
8291
8416
 
8292
8417
  override fun onPause() {
8418
+ motionForeground = false
8419
+ allScreens().forEach { it.motion?.stopAll(); it.barometer?.stop("background"); it.pedometer?.stop("background") }
8293
8420
  disarmShakeGesture()
8294
8421
  if (::services.isInitialized) services.pauseVoice()
8295
8422
  nativeAppState = "inactive"
@@ -8350,13 +8477,14 @@ class MainActivity : AppCompatActivity() {
8350
8477
 
8351
8478
  override fun onResume() {
8352
8479
  super.onResume()
8480
+ motionForeground = true
8353
8481
  armShakeGesture()
8354
8482
  if (::services.isInitialized) services.resumed()
8355
8483
  }
8356
8484
 
8357
8485
  override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
8358
8486
  super.onRequestPermissionsResult(requestCode, permissions, grantResults)
8359
- if (::capabilities.isInitialized) { capabilities.onBluetoothPermissions(requestCode); capabilities.onNotificationPermissions(requestCode) }
8487
+ if (::capabilities.isInitialized) { capabilities.onBluetoothPermissions(requestCode); capabilities.onNotificationPermissions(requestCode); capabilities.onPedometerPermissions(requestCode) }
8360
8488
  if (::services.isInitialized) services.onRequestPermissionsResult(requestCode)
8361
8489
  }
8362
8490