@craft-native/android 0.0.160 → 0.0.162

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
@@ -98,7 +98,11 @@ craft android build --native-bundle ../native-screen.js
98
98
 
99
99
  Native mode maps STX `View`, `SafeAreaView`, `Text`, `Button`, `Link`, `Icon`,
100
100
  `Pressable`, `TouchableOpacity`, `TouchableHighlight`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Modal`, `Switch`, `Slider`, `ActivityIndicator`, `Image`, `Video`, `ScrollView`,
101
- and `FlatList` to Android views. `Picker` renders `<select>`/`<option>` values
101
+ `SectionList`, and `FlatList` to Android views. `SectionList` uses the same
102
+ native scroll host for ordinary `.stx` slot children; grouped section data
103
+ should use `FlatList` until the section-data protocol is available. The
104
+ generated host routes both list names through `CraftNativeScrollView` for this
105
+ slot-based contract. `Picker` renders `<select>`/`<option>` values
102
106
  with `onValueChange` or `onChange`. `DatePicker` uses the platform `DatePicker`
103
107
  and `TimePicker` controls for date, time, datetime, and countdown modes and
104
108
  emits local `value`, ISO, and millisecond `timestamp` fields. `Modal` is parent-scoped and supports
@@ -131,7 +135,8 @@ When `flexShrink` is omitted, horizontal text labels and scroll hosts use the
131
135
  same shrink defaults as the iOS renderer; buttons and editable fields retain
132
136
  their intrinsic width unless explicitly configured.
133
137
  Wrapped containers also honor `alignContent` (`center`, `flex-end`,
134
- `space-between`, `space-around`, and `stretch`) consistently across hosts.
138
+ `space-between`, `space-around`, `space-evenly`, and `stretch`) consistently
139
+ across hosts.
135
140
  Shared wrapped-line IDs are accepted only when they are complete and monotonic;
136
141
  invalid bridge data falls back to the host layout implementation.
137
142
  Push, replace, and back preserve the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/android",
3
- "version": "0.0.160",
3
+ "version": "0.0.162",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/android#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -298,6 +298,43 @@ object CraftNative {
298
298
  margins: FloatArray,
299
299
  flags: FloatArray,
300
300
  ): FloatArray?
301
+ private external fun nativeGridAligned(
302
+ availableWidth: Float,
303
+ availableHeight: Float,
304
+ columns: Int,
305
+ columnGap: Float,
306
+ rowGap: Float,
307
+ alignContent: Int,
308
+ intrinsicWidth: FloatArray,
309
+ intrinsicHeight: FloatArray,
310
+ explicitWidth: FloatArray,
311
+ explicitHeight: FloatArray,
312
+ minWidth: FloatArray,
313
+ maxWidth: FloatArray,
314
+ minHeight: FloatArray,
315
+ maxHeight: FloatArray,
316
+ margins: FloatArray,
317
+ flags: FloatArray,
318
+ ): FloatArray?
319
+ private external fun nativeGridAutoRowsAligned(
320
+ availableWidth: Float,
321
+ availableHeight: Float,
322
+ columns: Int,
323
+ columnGap: Float,
324
+ rowGap: Float,
325
+ alignContent: Int,
326
+ autoRowHeight: Float,
327
+ intrinsicWidth: FloatArray,
328
+ intrinsicHeight: FloatArray,
329
+ explicitWidth: FloatArray,
330
+ explicitHeight: FloatArray,
331
+ minWidth: FloatArray,
332
+ maxWidth: FloatArray,
333
+ minHeight: FloatArray,
334
+ maxHeight: FloatArray,
335
+ margins: FloatArray,
336
+ flags: FloatArray,
337
+ ): FloatArray?
301
338
  private external fun nativeClipboardRead(activity: Activity): String?
302
339
  private external fun nativeClipboardWrite(activity: Activity, text: String): Boolean
303
340
  private external fun nativeOpenUrl(activity: Activity, url: String): Boolean
@@ -532,13 +569,14 @@ object CraftNative {
532
569
  margins: FloatArray,
533
570
  flags: FloatArray,
534
571
  autoRowHeight: Float? = null,
572
+ alignContent: Int = 0,
535
573
  ): FloatArray? {
536
574
  if (!isAvailable) return null
537
575
  return try {
538
576
  if (autoRowHeight != null) {
539
- nativeGridAutoRows(availableWidth, availableHeight, columns, columnGap, rowGap, autoRowHeight, intrinsicWidth, intrinsicHeight, explicitWidth, explicitHeight, minWidth, maxWidth, minHeight, maxHeight, margins, flags)
577
+ nativeGridAutoRowsAligned(availableWidth, availableHeight, columns, columnGap, rowGap, alignContent, autoRowHeight, intrinsicWidth, intrinsicHeight, explicitWidth, explicitHeight, minWidth, maxWidth, minHeight, maxHeight, margins, flags)
540
578
  } else {
541
- nativeGrid(availableWidth, availableHeight, columns, columnGap, rowGap, intrinsicWidth, intrinsicHeight, explicitWidth, explicitHeight, minWidth, maxWidth, minHeight, maxHeight, margins, flags)
579
+ nativeGridAligned(availableWidth, availableHeight, columns, columnGap, rowGap, alignContent, intrinsicWidth, intrinsicHeight, explicitWidth, explicitHeight, minWidth, maxWidth, minHeight, maxHeight, margins, flags)
542
580
  }
543
581
  ?.takeIf { it.size >= intrinsicWidth.size * 4 && it.all { value -> value.isFinite() } }
544
582
  } catch (e: UnsatisfiedLinkError) {
@@ -303,7 +303,8 @@ internal class CraftNativeCapabilities(
303
303
  return FileResult.Multipart(body.toByteArray(), boundary)
304
304
  }
305
305
 
306
- private fun files(requestToken: String, method: String, args: JSONArray): CraftNativeCapabilityResult = when (method) {
306
+ private fun files(requestToken: String, method: String, args: JSONArray): CraftNativeCapabilityResult {
307
+ return when (method) {
307
308
  "write" -> {
308
309
  val name = args.optString(0).trim()
309
310
  if (name.isBlank()) return failure("INVALID_ARGUMENT", "files.write needs a file name")
@@ -322,7 +323,8 @@ internal class CraftNativeCapabilities(
322
323
  success(file.delete())
323
324
  }
324
325
  "download" -> download(requestToken, args)
325
- else -> failure("UNKNOWN_ACTION", "Unsupported Files method")
326
+ else -> failure("UNKNOWN_ACTION", "Unsupported Files method")
327
+ }
326
328
  }
327
329
 
328
330
  private fun download(requestToken: String, args: JSONArray): CraftNativeCapabilityResult {
@@ -115,6 +115,7 @@ private data class CraftNativeLength(
115
115
  private data class CraftNativeLayoutStyle(
116
116
  val width: CraftNativeLength? = null,
117
117
  val height: CraftNativeLength? = null,
118
+ val aspectRatio: Double? = null,
118
119
  val minWidth: CraftNativeLength? = null,
119
120
  val maxWidth: CraftNativeLength? = null,
120
121
  val minHeight: CraftNativeLength? = null,
@@ -143,6 +144,7 @@ private data class CraftNativeLayoutStyle(
143
144
  val flex = decimal("flex")
144
145
  return CraftNativeLayoutStyle(
145
146
  width = length("width"), height = length("height"),
147
+ aspectRatio = decimal("aspectRatio")?.takeIf { it.isFinite() && it > 0.0 },
146
148
  minWidth = length("minWidth"), maxWidth = length("maxWidth"),
147
149
  minHeight = length("minHeight"), maxHeight = length("maxHeight"),
148
150
  position = raw.opt("position")
@@ -583,7 +585,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
583
585
  // The STX style is authoritative. A reused View may still carry a
584
586
  // previous fixed LayoutParams width after a reactive width is removed;
585
587
  // using that value here prevents intrinsic sizing from being restored.
586
- val explicit = style.width?.resolve(base) ?: flexBasis
588
+ val authored = style.width?.resolve(base)
589
+ val explicit = authored ?: style.aspectRatio?.let { ratio -> style.height?.resolve(base)?.times(ratio)?.roundToInt() } ?: flexBasis
587
590
  if (explicit != null) {
588
591
  child.measure(
589
592
  View.MeasureSpec.makeMeasureSpec(explicit, View.MeasureSpec.EXACTLY),
@@ -613,7 +616,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
613
616
  val flexBasis = if (orientation == VERTICAL) style.flexBasis?.resolve(base) else null
614
617
  // Match width measurement: do not let stale native LayoutParams keep
615
618
  // a removed reactive height fixed.
616
- val explicit = style.height?.resolve(base) ?: flexBasis
619
+ val authored = style.height?.resolve(base)
620
+ val explicit = authored ?: style.aspectRatio?.let { ratio -> style.width?.resolve(base)?.div(ratio)?.roundToInt() } ?: flexBasis
617
621
  val target = explicit ?: base
618
622
  val spec = if (explicit != null) View.MeasureSpec.makeMeasureSpec(target, View.MeasureSpec.EXACTLY)
619
623
  else View.MeasureSpec.makeMeasureSpec(base, View.MeasureSpec.AT_MOST)
@@ -649,6 +653,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
649
653
  "space-between" -> 3
650
654
  "space-around" -> 4
651
655
  "stretch" -> 5
656
+ "space-evenly" -> 6
652
657
  else -> 0
653
658
  }
654
659
 
@@ -772,6 +777,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
772
777
  listOf(if (align == "stretch") 1f else 0f, if (align == "center") 1f else if (align == "flex-end") 2f else 0f)
773
778
  }.toFloatArray(),
774
779
  gridAutoRows?.resolve(contentHeight)?.toFloat(),
780
+ alignContentCode(),
775
781
  )
776
782
  if (sharedFrames != null) {
777
783
  for ((index, entry) in flow.withIndex()) {
@@ -783,9 +789,30 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
783
789
  entry.view.layout(x, y, x + childWidth, y + childHeight)
784
790
  }
785
791
  } else {
786
- var rowTop = paddingTop
792
+ val totalRows = layout.rowHeights.sum()
793
+ val rowGaps = max(0, layout.rowHeights.size - 1) * rowGap
794
+ val freeRows = max(0, contentHeight - totalRows - rowGaps)
795
+ val rowCount = max(1, layout.rowHeights.size)
796
+ var rowLeading = 0
797
+ var rowBetween = rowGap
798
+ var rowExtra = 0
799
+ when (alignContentCode()) {
800
+ 1 -> rowLeading = freeRows / 2
801
+ 2 -> rowLeading = freeRows
802
+ 3 -> if (rowCount > 1) rowBetween += freeRows / (rowCount - 1)
803
+ 4 -> {
804
+ rowLeading = freeRows / (rowCount * 2)
805
+ rowBetween += freeRows / rowCount
806
+ }
807
+ 6 -> {
808
+ rowLeading = freeRows / (rowCount + 1)
809
+ rowBetween += freeRows / (rowCount + 1)
810
+ }
811
+ 5 -> rowExtra = freeRows / rowCount
812
+ }
813
+ var rowTop = paddingTop + rowLeading
787
814
  for ((rowIndex, row) in layout.rows.withIndex()) {
788
- val rowHeight = layout.rowHeights[rowIndex]
815
+ val rowHeight = layout.rowHeights[rowIndex] + rowExtra
789
816
  for ((column, entry) in row.withIndex()) {
790
817
  val align = entry.style.alignSelf ?: alignItems
791
818
  val lp = entry.view.layoutParams as? MarginLayoutParams
@@ -809,7 +836,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
809
836
  }
810
837
  entry.view.layout(x, y, x + childWidth, y + childHeight)
811
838
  }
812
- rowTop += rowHeight + rowGap
839
+ rowTop += rowHeight + rowBetween
813
840
  }
814
841
  }
815
842
  for (entry in layout.absolute) {
@@ -849,6 +876,10 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
849
876
  crossBetween = crossFree / lines.size + crossGap
850
877
  crossLeading = crossFree / (lines.size * 2)
851
878
  }
879
+ "space-evenly" -> {
880
+ crossBetween = crossFree / (lines.size + 1) + crossGap
881
+ crossLeading = crossFree / (lines.size + 1)
882
+ }
852
883
  "stretch" -> {
853
884
  val extra = crossFree / lines.size
854
885
  lines.forEach { it.cross += extra }
@@ -2803,7 +2834,7 @@ class MainActivity : AppCompatActivity() {
2803
2834
  "Svg" -> (previous as? CraftNativeSvgView ?: CraftNativeSvgView(this)).apply {
2804
2835
  drawing = node
2805
2836
  }
2806
- "ScrollView" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
2837
+ "ScrollView", "SectionList" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
2807
2838
  val horizontal = props.optBoolean("horizontal", false) || style.optString("flexDirection") in setOf("row", "row-reverse")
2808
2839
  setHorizontal(horizontal)
2809
2840
  setNativeNestedScrollingEnabled(true)
@@ -3064,6 +3095,7 @@ class MainActivity : AppCompatActivity() {
3064
3095
  "video" -> "Video"
3065
3096
  "icon" -> "Icon"
3066
3097
  "scrollview" -> "ScrollView"
3098
+ "sectionlist" -> "SectionList"
3067
3099
  "flatlist" -> "FlatList"
3068
3100
  "svg" -> "Svg"
3069
3101
  "touchableopacity" -> "TouchableOpacity"
@@ -3477,7 +3509,9 @@ class MainActivity : AppCompatActivity() {
3477
3509
  else -> null
3478
3510
  }
3479
3511
  view.tooltipText = props.optString("accessibilityHint").takeIf { it.isNotBlank() }
3480
- val role = props.optString("accessibilityRole").ifBlank { type.lowercase() }
3512
+ val role = props.optString("accessibilityRole").ifBlank {
3513
+ if (type == "Icon") "image" else type.lowercase()
3514
+ }
3481
3515
  val state = props.optJSONObject("accessibilityState")
3482
3516
  val disabled = props.optBoolean("disabled", false) || state?.optBoolean("disabled", false) == true
3483
3517
  if (type !in setOf("Button", "Link", "Checkbox", "TextInput", "Picker", "DatePicker", "Modal", "Switch", "Slider", "ActivityIndicator")) {