@craft-native/android 0.0.212 → 0.0.214

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/android",
3
- "version": "0.0.212",
3
+ "version": "0.0.214",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/android#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -353,6 +353,12 @@ object CraftNative {
353
353
  nodeData: FloatArray,
354
354
  treeMeta: FloatArray,
355
355
  ): FloatArray?
356
+ private external fun nativeLayoutTreeV4(
357
+ width: Float,
358
+ height: Float,
359
+ nodeData: FloatArray,
360
+ treeMeta: FloatArray,
361
+ ): FloatArray?
356
362
  private external fun nativeClipboardRead(activity: Activity): String?
357
363
  private external fun nativeClipboardWrite(activity: Activity, text: String): Boolean
358
364
  private external fun nativeOpenUrl(activity: Activity, url: String): Boolean
@@ -635,6 +641,17 @@ object CraftNative {
635
641
  }
636
642
  }
637
643
 
644
+ /** Resolve a reverse-aware tree with shared grid-auto-rows support. */
645
+ fun layoutTreeV4(width: Float, height: Float, nodeData: FloatArray, treeMeta: FloatArray): FloatArray? {
646
+ if (!isAvailable || nodeData.isEmpty() || treeMeta.isEmpty()) return null
647
+ return try {
648
+ nativeLayoutTreeV4(width, height, nodeData, treeMeta)
649
+ ?.takeIf { it.size >= treeMeta.size / 2 * 4 && it.all { value -> value.isFinite() } }
650
+ } catch (e: UnsatisfiedLinkError) {
651
+ null
652
+ }
653
+ }
654
+
638
655
  /**
639
656
  * The clipboard's text, or null to use the Kotlin implementation.
640
657
  *
@@ -395,6 +395,7 @@ internal object CraftNativePrelude {
395
395
  if (typeof globalThis.window === 'undefined') globalThis.window = globalThis;
396
396
  (function() {
397
397
  var forward = null;
398
+ var listeners = [];
398
399
  var queued = [];
399
400
  var sync = { storage: {}, secure: {}, snapshots: {} };
400
401
  var preload = $STORES;
@@ -445,7 +446,11 @@ internal object CraftNativePrelude {
445
446
  }
446
447
  function send(value) { if (typeof value !== 'string') value = JSON.stringify(value); $post }
447
448
  function emit(message) {
448
- if (forward) forward(typeof message === 'string' ? message : JSON.stringify(message));
449
+ var raw = typeof message === 'string' ? message : JSON.stringify(message);
450
+ listeners.slice().forEach(function(callback) {
451
+ try { callback(raw); } catch (error) {}
452
+ });
453
+ if (forward) forward(raw);
449
454
  else queued.push(message);
450
455
  }
451
456
  function deliver(raw) {
@@ -515,6 +520,14 @@ internal object CraftNativePrelude {
515
520
  var pending = queued;
516
521
  queued = [];
517
522
  pending.forEach(emit);
523
+ },
524
+ addMessageListener: function(callback) {
525
+ if (typeof callback !== 'function') return function() {};
526
+ listeners.push(callback);
527
+ return function() {
528
+ var index = listeners.indexOf(callback);
529
+ if (index >= 0) listeners.splice(index, 1);
530
+ };
518
531
  }
519
532
  };
520
533
  $receiveSetup
@@ -563,6 +576,47 @@ internal object CraftNativePrelude {
563
576
  return value === undefined ? null : value;
564
577
  }
565
578
  });
579
+ // Older native bundles call the page-shaped `craft.dialog` API
580
+ // directly. Keep that API on the capability bridge so JavaScript
581
+ // Core/JavaScriptSandbox screens never fall back to a DOM sheet.
582
+ if (Array.isArray(__stxNativeBridge.capabilities) && __stxNativeBridge.capabilities.indexOf('dialog') >= 0 && !craft.dialog) {
583
+ var nextDialogRequest = 0;
584
+ function dialogRequest(method, args) {
585
+ return new Promise(function(resolve, reject) {
586
+ var id = 'craft-dialog-' + (++nextDialogRequest);
587
+ var remove = __stxNativeBridge.addMessageListener(function(raw) {
588
+ var message;
589
+ try { message = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch (error) { return; }
590
+ var payload = message && message.payload;
591
+ if (!payload || payload.requestId !== id) return;
592
+ remove();
593
+ if (message.type === 'API_ERROR') {
594
+ var failure = new Error(payload.message || 'Native dialog failed');
595
+ failure.code = payload.code || 'NATIVE_CALL_FAILED';
596
+ reject(failure);
597
+ } else if (message.type === 'API_RESPONSE') {
598
+ resolve(payload.data === undefined ? null : payload.data);
599
+ }
600
+ });
601
+ __stxNativeBridge.postMessage(JSON.stringify({
602
+ type: 'API_REQUEST',
603
+ id: id,
604
+ payload: {
605
+ version: __stxNativeBridge.capabilityProtocolVersion || 1,
606
+ module: 'Dialog',
607
+ method: method,
608
+ args: args,
609
+ untimed: true,
610
+ },
611
+ }));
612
+ });
613
+ }
614
+ craft.dialog = {
615
+ alert: function(options) { return dialogRequest('alert', [options || {}]).then(function() {}); },
616
+ confirm: function(options) { return dialogRequest('confirm', [options || {}]).then(Boolean); },
617
+ actionSheet: function(options) { return dialogRequest('actionSheet', [options || {}]).then(function(id) { return typeof id === 'string' ? id : null; }); },
618
+ };
619
+ }
566
620
  })();
567
621
  """.trimIndent()
568
622
  }
@@ -1327,9 +1327,9 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1327
1327
  return result
1328
1328
  }
1329
1329
 
1330
- private fun defaultFlexShrink(view: View): Double = when {
1330
+ private fun defaultFlexShrink(view: View, horizontal: Boolean = orientation == HORIZONTAL): Double = when {
1331
1331
  view is ScrollView || view is HorizontalScrollView -> 1.0
1332
- view is TextView && view !is Button && view !is EditText && orientation == HORIZONTAL -> 1.0
1332
+ view is TextView && view !is Button && view !is EditText && horizontal -> 1.0
1333
1333
  else -> 0.0
1334
1334
  }
1335
1335
 
@@ -1372,6 +1372,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1372
1372
  val margins: IntArray,
1373
1373
  val grid: Boolean,
1374
1374
  val gridColumns: Int,
1375
+ val gridAutoRows: CraftNativeLength?,
1375
1376
  val reverse: Boolean,
1376
1377
  val wrapReverse: Boolean,
1377
1378
  val children: List<SharedTreeNode>,
@@ -1383,14 +1384,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1383
1384
  val frames: FloatArray,
1384
1385
  )
1385
1386
 
1386
- /**
1387
- * Flatten a flex or equal-track grid subtree for the shared ABI. The local
1388
- * layout stays authoritative for grid-auto-rows,
1389
- * absolute/modal children, and scroll hosts; those cases cannot be
1390
- * represented without changing their native measurement contract.
1391
- */
1387
+ /** Flatten a flex or equal-track grid subtree for the shared ABI. */
1392
1388
  private fun sharedTreeChildren(width: Int, height: Int): List<Entry>? {
1393
- if (gridAutoRows != null) return null
1394
1389
  if (grid) {
1395
1390
  val layout = gridLayout(width, height)
1396
1391
  if (layout.absolute.isNotEmpty()) return null
@@ -1428,6 +1423,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1428
1423
  margins = intArrayOf(lp?.leftMargin ?: 0, lp?.rightMargin ?: 0, lp?.topMargin ?: 0, lp?.bottomMargin ?: 0),
1429
1424
  grid = nested?.grid ?: false,
1430
1425
  gridColumns = nested?.gridColumns ?: 1,
1426
+ gridAutoRows = nested?.gridAutoRows,
1431
1427
  reverse = nested?.reverse ?: false,
1432
1428
  wrapReverse = nested?.wrapReverse ?: false,
1433
1429
  children = children.map { child -> sharedTreeNode(child) ?: return null },
@@ -1435,7 +1431,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1435
1431
  }
1436
1432
 
1437
1433
  private fun sharedTreeFrames(entries: List<Entry>, width: Int, height: Int): SharedTreeResult? {
1438
- if (entries.isEmpty() || width < 0 || height < 0 || gridAutoRows != null) return null
1434
+ if (entries.isEmpty() || width < 0 || height < 0) return null
1439
1435
  val children = entries.map { sharedTreeNode(it) ?: return null }
1440
1436
  val root = SharedTreeNode(
1441
1437
  view = null,
@@ -1455,6 +1451,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1455
1451
  margins = intArrayOf(0, 0, 0, 0),
1456
1452
  grid = grid,
1457
1453
  gridColumns = gridColumns,
1454
+ gridAutoRows = gridAutoRows,
1458
1455
  reverse = reverse,
1459
1456
  wrapReverse = wrapReverse,
1460
1457
  children = children,
@@ -1470,14 +1467,19 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1470
1467
  append(root, -1)
1471
1468
  val reverseAware = nodes.any { it.reverse || it.wrapReverse }
1472
1469
  val wrapReverseAware = nodes.any { it.wrapReverse }
1473
- val fields = if (reverseAware) 33 else 32
1470
+ val autoRowsAware = nodes.any { it.gridAutoRows != null }
1471
+ val fields = when {
1472
+ autoRowsAware -> 34
1473
+ reverseAware -> 33
1474
+ else -> 32
1475
+ }
1474
1476
  val nodeData = FloatArray(nodes.size * fields) { Float.NaN }
1475
1477
  val treeMeta = FloatArray(nodes.size * 2)
1476
1478
  fun enumBits(value: Int): Float = Float.fromBits(value)
1477
1479
  fun put(base: Int, index: Int, value: Float) { nodeData[base + index] = value }
1478
1480
  fun putEnum(base: Int, index: Int, value: Int) { put(base, index, enumBits(value)) }
1479
1481
  var cursor = 0
1480
- fun write(node: SharedTreeNode, parentWidth: Int, parentHeight: Int, rootNode: Boolean) {
1482
+ fun write(node: SharedTreeNode, parentWidth: Int, parentHeight: Int, rootNode: Boolean, parentDirection: Int) {
1481
1483
  val index = cursor++
1482
1484
  val base = index * fields
1483
1485
  val measuredWidth = if (rootNode) width else node.style.width?.resolve(parentWidth) ?: node.intrinsicWidth.roundToInt()
@@ -1494,7 +1496,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1494
1496
  put(base, 6, node.style.aspectRatio?.toFloat() ?: Float.NaN)
1495
1497
  put(base, 7, sharedLength(node.style.flexBasis, mainSize))
1496
1498
  put(base, 8, if (rootNode) 0f else node.style.flexGrow.toFloat().coerceAtLeast(0f))
1497
- put(base, 9, if (rootNode) 0f else (node.style.flexShrink ?: 0.0).toFloat().coerceAtLeast(0f))
1499
+ put(base, 9, if (rootNode) 0f else (node.style.flexShrink ?: node.view?.let { defaultFlexShrink(it, parentDirection == 0) } ?: 0.0).toFloat().coerceAtLeast(0f))
1498
1500
  put(base, 10, sharedLength(node.style.minWidth, contentWidth))
1499
1501
  put(base, 11, sharedLength(node.style.maxWidth, contentWidth))
1500
1502
  put(base, 12, sharedLength(node.style.minHeight, contentHeight))
@@ -1517,14 +1519,17 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1517
1519
  put(base, 29, node.gridColumns.toFloat())
1518
1520
  put(base, 30, node.rowGap)
1519
1521
  put(base, 31, node.columnGap)
1520
- if (reverseAware) putEnum(base, 32, (if (node.reverse) 1 else 0) or (if (node.wrapReverse) 2 else 0))
1522
+ if (fields >= 33) putEnum(base, 32, (if (node.reverse) 1 else 0) or (if (node.wrapReverse) 2 else 0))
1523
+ if (autoRowsAware) put(base, 33, sharedLength(node.gridAutoRows, contentHeight))
1521
1524
  val firstChild = cursor
1522
- node.children.forEach { child -> write(child, contentWidth, contentHeight, false) }
1525
+ node.children.forEach { child -> write(child, contentWidth, contentHeight, false, node.direction) }
1523
1526
  treeMeta[index * 2] = firstChild.toFloat()
1524
1527
  treeMeta[index * 2 + 1] = (cursor - firstChild).toFloat()
1525
1528
  }
1526
- write(root, width, height, true)
1527
- val frames = (if (wrapReverseAware) {
1529
+ write(root, width, height, true, root.direction)
1530
+ val frames = (if (autoRowsAware) {
1531
+ CraftNative.layoutTreeV4(width.toFloat(), height.toFloat(), nodeData, treeMeta)
1532
+ } else if (wrapReverseAware) {
1528
1533
  CraftNative.layoutTreeV3(width.toFloat(), height.toFloat(), nodeData, treeMeta)
1529
1534
  } else if (reverseAware) {
1530
1535
  CraftNative.layoutTreeV2(width.toFloat(), height.toFloat(), nodeData, treeMeta)
@@ -1537,9 +1542,9 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
1537
1542
  /**
1538
1543
  * Use the host-neutral tree ABI when platform measurements are complete:
1539
1544
  * definite direct-child containers, including wrapped flows, equal-track
1540
- * grids, and independent row and column gaps. Grid
1541
- * auto-rows, absolute children, missing runtimes, and malformed results
1542
- * deliberately fall through to the established Kotlin layout below.
1545
+ * grids, independent row and column gaps, and grid-auto-rows. Absolute
1546
+ * children, missing runtimes, and malformed results deliberately fall
1547
+ * through to the established Kotlin layout below.
1543
1548
  */
1544
1549
  private fun sharedDirectFrames(entries: List<Entry>, width: Int, height: Int): FloatArray? {
1545
1550
  if (entries.isEmpty() || width < 0 || height < 0 || grid || reverse || wrapReverse || gridAutoRows != null) return null