@craft-native/android 0.0.206 → 0.0.209
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
|
@@ -578,6 +578,11 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
578
578
|
private val largeTitleView = TextView(context)
|
|
579
579
|
private val buttons = LinearLayout(context)
|
|
580
580
|
private val tabBar = LinearLayout(context)
|
|
581
|
+
private val tabRow = LinearLayout(context)
|
|
582
|
+
private val accessory = FrameLayout(context)
|
|
583
|
+
private val accessoryIcon = ImageView(context)
|
|
584
|
+
private val accessoryBadge = TextView(context)
|
|
585
|
+
private var accessorySpec: JSONObject? = null
|
|
581
586
|
private val tabViews = mutableListOf<Pair<TextView, TextView>>()
|
|
582
587
|
private val tabIconText = mutableMapOf<String, String>()
|
|
583
588
|
private val tabImageURLs = mutableMapOf<String, String>()
|
|
@@ -596,6 +601,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
596
601
|
var onBack: (() -> Unit)? = null
|
|
597
602
|
var onButton: ((String) -> Unit)? = null
|
|
598
603
|
var onTab: ((Int) -> Unit)? = null
|
|
604
|
+
var onAccessory: ((String) -> Unit)? = null
|
|
599
605
|
var onTabSwipeStart: ((Float, Float, Float) -> Boolean)? = null
|
|
600
606
|
var onTabSwipeMove: ((Float) -> Unit)? = null
|
|
601
607
|
var onTabSwipeEnd: ((Float, Boolean) -> Unit)? = null
|
|
@@ -660,8 +666,22 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
660
666
|
tabBar.addView(cell, LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f))
|
|
661
667
|
tabViews.add(icon to label)
|
|
662
668
|
}
|
|
663
|
-
|
|
664
|
-
|
|
669
|
+
tabRow.orientation = HORIZONTAL
|
|
670
|
+
tabRow.gravity = Gravity.CENTER_VERTICAL
|
|
671
|
+
tabRow.setPadding(dp(12), dp(4), dp(12), dp(4))
|
|
672
|
+
tabRow.addView(tabBar, LayoutParams(0, dp(64), 1f))
|
|
673
|
+
accessory.addView(accessoryIcon, FrameLayout.LayoutParams(dp(26), dp(26), Gravity.CENTER))
|
|
674
|
+
accessoryBadge.textSize = 10f
|
|
675
|
+
accessoryBadge.gravity = Gravity.CENTER
|
|
676
|
+
accessoryBadge.setTextColor(Color.WHITE)
|
|
677
|
+
accessoryBadge.setPadding(dp(4), 0, dp(4), 0)
|
|
678
|
+
accessoryBadge.background = GradientDrawable().apply { setColor(Color.RED); cornerRadius = dp(9).toFloat() }
|
|
679
|
+
accessory.addView(accessoryBadge, FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(18), Gravity.TOP or Gravity.END))
|
|
680
|
+
accessory.setOnClickListener { accessorySpec?.optString("path")?.let { onAccessory?.invoke(it) } }
|
|
681
|
+
accessory.visibility = View.GONE
|
|
682
|
+
tabRow.addView(accessory, LayoutParams(dp(64), dp(64)).apply { marginStart = dp(8) })
|
|
683
|
+
addView(tabRow, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(72)))
|
|
684
|
+
tabRow.visibility = if (tabs.isEmpty()) View.GONE else View.VISIBLE
|
|
665
685
|
refreshColors()
|
|
666
686
|
}
|
|
667
687
|
|
|
@@ -671,7 +691,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
671
691
|
val overHorizontalScroll = onTabSwipeShouldDefer?.invoke(event.x, event.y) == true
|
|
672
692
|
horizontalScrollDeferred = overHorizontalScroll
|
|
673
693
|
if (overHorizontalScroll) parent?.requestDisallowInterceptTouchEvent(true)
|
|
674
|
-
swipeTracking = tabs.size > 1 && tabSwipeEnabled && !overHorizontalScroll &&
|
|
694
|
+
swipeTracking = tabs.size > 1 && tabSwipeEnabled && !overHorizontalScroll && tabRow.visibility == View.VISIBLE && event.y < tabRow.top
|
|
675
695
|
swipeIntercepted = false
|
|
676
696
|
swipeDownX = event.x
|
|
677
697
|
swipeDownY = event.y
|
|
@@ -742,7 +762,17 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
742
762
|
buttons.addView(navButton(button))
|
|
743
763
|
}
|
|
744
764
|
val tabBarHidden = options.optBoolean("tabBarHidden", false) || depth > 0 && options.optBoolean("tabBarHiddenWhenPushed", false)
|
|
745
|
-
|
|
765
|
+
tabRow.visibility = if (tabs.isEmpty() || tabBarHidden) View.GONE else View.VISIBLE
|
|
766
|
+
accessorySpec = options.optJSONObject("tabAccessory")?.takeIf {
|
|
767
|
+
val path = it.optString("path")
|
|
768
|
+
path.startsWith("/") && !path.startsWith("//") && it.has("title")
|
|
769
|
+
}
|
|
770
|
+
accessory.visibility = if (accessorySpec == null) View.GONE else View.VISIBLE
|
|
771
|
+
accessorySpec?.let {
|
|
772
|
+
accessory.contentDescription = it.optString("accessibilityLabel").ifBlank { it.optString("title") } + it.optString("badge").takeIf { badge -> badge.isNotBlank() && badge != "null" }?.let { badge -> ", $badge" }.orEmpty()
|
|
773
|
+
accessoryBadge.text = it.optString("badge").takeUnless { badge -> badge == "null" }.orEmpty().take(8)
|
|
774
|
+
accessoryBadge.visibility = if (accessoryBadge.text.isEmpty()) View.GONE else View.VISIBLE
|
|
775
|
+
}
|
|
746
776
|
tabSwipeEnabled = options.optBoolean("tabSwipeEnabled", true)
|
|
747
777
|
refreshColors()
|
|
748
778
|
}
|
|
@@ -801,13 +831,10 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
801
831
|
titleView.setTextColor(text)
|
|
802
832
|
largeTitleView.setTextColor(text)
|
|
803
833
|
backButton.imageTintList = ColorStateList.valueOf(accent)
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
divider,
|
|
809
|
-
GradientDrawable().apply { setColor(if (dark) Color.parseColor("#121212") else Color.parseColor("#F9F9F9")) },
|
|
810
|
-
)).apply { setLayerInset(1, 0, maxOf(1, (density / 2).toInt()), 0, 0) }
|
|
834
|
+
val surface = if (dark) Color.parseColor("#1C1C1E") else Color.parseColor("#F9F9F9")
|
|
835
|
+
tabBar.background = GradientDrawable().apply { setColor(surface); cornerRadius = dp(32).toFloat() }
|
|
836
|
+
accessory.background = GradientDrawable().apply { setColor(surface); cornerRadius = dp(32).toFloat() }
|
|
837
|
+
accessorySpec?.let { accessoryIcon.setImageDrawable(CraftNativeIcons.drawable(context, null, it.optString("symbol"), text)) }
|
|
811
838
|
tabViews.forEachIndexed { index, (icon, label) ->
|
|
812
839
|
val color = if (index == selectedTab) accent else muted
|
|
813
840
|
val number = tabIconText[tabs[index].id]
|
|
@@ -148,6 +148,10 @@ private fun craftNativeOptionalString(value: Any?): String? = value
|
|
|
148
148
|
?.takeUnless { it == "null" || it == "undefined" }
|
|
149
149
|
?.takeIf { it.isNotBlank() }
|
|
150
150
|
|
|
151
|
+
private fun craftNativeBoolean(value: Any?, fallback: Boolean = false): Boolean = (value as? Boolean) ?: fallback
|
|
152
|
+
|
|
153
|
+
private fun craftNativeInt(value: Any?, fallback: Int): Int = (value as? Number)?.toInt() ?: fallback
|
|
154
|
+
|
|
151
155
|
private fun craftNativePickerValue(value: Any?): String? = when (value) {
|
|
152
156
|
is String -> value.takeIf { it.isNotEmpty() }
|
|
153
157
|
is Number -> value.toDouble().takeIf { it.isFinite() }?.toString()
|
|
@@ -157,20 +161,35 @@ private fun craftNativePickerValue(value: Any?): String? = when (value) {
|
|
|
157
161
|
private fun craftNativeEventName(events: JSONObject, vararg names: String): String =
|
|
158
162
|
names.firstNotNullOfOrNull { name -> craftNativeOptionalString(events.opt(name)) }.orEmpty()
|
|
159
163
|
|
|
164
|
+
private fun craftNativeNumber(value: Any?): Double? = (value as? Number)
|
|
165
|
+
?.toDouble()
|
|
166
|
+
?.takeIf { it.isFinite() }
|
|
167
|
+
|
|
168
|
+
private fun craftNativeFontWeight(value: Any?): String? = when (value) {
|
|
169
|
+
is String -> value
|
|
170
|
+
is Number -> value.toDouble().takeIf { it.isFinite() && it % 1.0 == 0.0 }?.toInt()?.toString()
|
|
171
|
+
else -> null
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
internal fun craftNativePadding(style: JSONObject?, name: String, fallback: Int, density: Float): Int {
|
|
161
|
-
val value = style?.
|
|
175
|
+
val value = craftNativeNumber(style?.opt(name)) ?: Double.NaN
|
|
162
176
|
val pixels = value * density
|
|
163
177
|
return if (value.isFinite() && value >= 0 && pixels.isFinite()) pixels.toInt() else fallback
|
|
164
178
|
}
|
|
165
179
|
|
|
166
180
|
internal fun craftNativeFiniteFloat(style: JSONObject?, name: String, fallback: Float): Float {
|
|
167
|
-
val value = style?.
|
|
181
|
+
val value = craftNativeNumber(style?.opt(name)) ?: Double.NaN
|
|
168
182
|
val narrowed = value.toFloat()
|
|
169
183
|
return if (value.isFinite() && narrowed.isFinite()) narrowed else fallback
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
internal fun craftNativeFiniteDouble(style: JSONObject?, name: String, fallback: Double): Double {
|
|
173
|
-
val value = style?.
|
|
187
|
+
val value = craftNativeNumber(style?.opt(name)) ?: Double.NaN
|
|
188
|
+
return value.takeIf { it.isFinite() } ?: fallback
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
internal fun craftNativeFiniteNumber(props: JSONObject?, name: String, fallback: Double): Double {
|
|
192
|
+
val value = (props?.opt(name) as? Number)?.toDouble() ?: Double.NaN
|
|
174
193
|
return value.takeIf { it.isFinite() } ?: fallback
|
|
175
194
|
}
|
|
176
195
|
|
|
@@ -221,32 +240,25 @@ private data class CraftNativeLayoutStyle(
|
|
|
221
240
|
companion object {
|
|
222
241
|
fun from(raw: JSONObject, density: Float): CraftNativeLayoutStyle {
|
|
223
242
|
fun length(name: String): CraftNativeLength? = CraftNativeLength.from(raw.opt(name), density)
|
|
224
|
-
fun
|
|
243
|
+
fun numeric(name: String): Double? {
|
|
225
244
|
if (!raw.has(name) || raw.isNull(name)) return null
|
|
226
|
-
return raw.
|
|
245
|
+
return (raw.opt(name) as? Number)?.toDouble()?.takeIf { it.isFinite() }
|
|
246
|
+
}
|
|
247
|
+
fun number(name: String): Int? {
|
|
248
|
+
return numeric(name)?.let { points ->
|
|
227
249
|
(points * density).takeIf { it.isFinite() }?.roundToInt()
|
|
228
250
|
}
|
|
229
251
|
}
|
|
230
|
-
fun decimal(name: String): Double?
|
|
231
|
-
if (!raw.has(name) || raw.isNull(name)) return null
|
|
232
|
-
return raw.optDouble(name, Double.NaN).takeIf { it.isFinite() }
|
|
233
|
-
}
|
|
252
|
+
fun decimal(name: String): Double? = numeric(name)
|
|
234
253
|
val flex = decimal("flex")
|
|
235
254
|
return CraftNativeLayoutStyle(
|
|
236
255
|
width = length("width"), height = length("height"),
|
|
237
256
|
aspectRatio = decimal("aspectRatio")?.takeIf { it.isFinite() && it > 0.0 },
|
|
238
257
|
minWidth = length("minWidth"), maxWidth = length("maxWidth"),
|
|
239
258
|
minHeight = length("minHeight"), maxHeight = length("maxHeight"),
|
|
240
|
-
position = raw.opt("position")
|
|
241
|
-
?.takeUnless { it == JSONObject.NULL }
|
|
242
|
-
?.toString()
|
|
243
|
-
?.takeIf { it.isNotBlank() }
|
|
244
|
-
?: "relative",
|
|
259
|
+
position = craftNativeOptionalString(raw.opt("position")) ?: "relative",
|
|
245
260
|
top = number("top"), right = number("right"), bottom = number("bottom"), left = number("left"),
|
|
246
|
-
alignSelf = raw.opt("alignSelf")
|
|
247
|
-
?.takeUnless { it == JSONObject.NULL }
|
|
248
|
-
?.toString()
|
|
249
|
-
?.takeIf { it.isNotBlank() && it != "auto" },
|
|
261
|
+
alignSelf = craftNativeOptionalString(raw.opt("alignSelf"))?.takeUnless { it == "auto" },
|
|
250
262
|
flexGrow = decimal("flexGrow") ?: flex?.coerceAtLeast(0.0) ?: 0.0,
|
|
251
263
|
flexShrink = decimal("flexShrink") ?: flex?.let { if (it != 0.0) 1.0 else 0.0 },
|
|
252
264
|
flexBasis = length("flexBasis")
|
|
@@ -364,11 +376,8 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
364
376
|
fun merge(node: JSONObject): SvgPaint {
|
|
365
377
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
366
378
|
val style = node.optJSONObject("style") ?: JSONObject()
|
|
367
|
-
fun value(name: String): String? =
|
|
368
|
-
|
|
369
|
-
props.has(name) && !props.isNull(name) -> props.optString(name)
|
|
370
|
-
else -> null
|
|
371
|
-
}
|
|
379
|
+
fun value(name: String): String? = craftNativeOptionalString(style.opt(name))
|
|
380
|
+
?: craftNativeOptionalString(props.opt(name))
|
|
372
381
|
fun raw(name: String): Any? = when {
|
|
373
382
|
style.has(name) && !style.isNull(name) -> style.opt(name)
|
|
374
383
|
props.has(name) && !props.isNull(name) -> props.opt(name)
|
|
@@ -377,7 +386,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
377
386
|
fun dashes(rawValue: Any?): List<Float>? {
|
|
378
387
|
val values = rawValue as? JSONArray ?: return null
|
|
379
388
|
return (0 until values.length()).mapNotNull {
|
|
380
|
-
values.
|
|
389
|
+
craftNativeNumber(values.opt(it))?.toFloat()?.takeIf { value -> value.isFinite() && value >= 0 }
|
|
381
390
|
}
|
|
382
391
|
}
|
|
383
392
|
// Keep the cascade explicit: computed style wins over a raw prop
|
|
@@ -455,10 +464,10 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
455
464
|
private fun viewBox(): Quadruple<Float, Float, Float, Float>? {
|
|
456
465
|
val values = drawing.optJSONObject("props")?.optJSONArray("viewBox") ?: return null
|
|
457
466
|
if (values.length() != 4) return null
|
|
458
|
-
val x = values.
|
|
459
|
-
val y = values.
|
|
460
|
-
val width = values.
|
|
461
|
-
val height = values.
|
|
467
|
+
val x = craftNativeNumber(values.opt(0))?.toFloat() ?: Float.NaN
|
|
468
|
+
val y = craftNativeNumber(values.opt(1))?.toFloat() ?: Float.NaN
|
|
469
|
+
val width = craftNativeNumber(values.opt(2))?.toFloat() ?: Float.NaN
|
|
470
|
+
val height = craftNativeNumber(values.opt(3))?.toFloat() ?: Float.NaN
|
|
462
471
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() || !height.isFinite() || width <= 0f || height <= 0f) return null
|
|
463
472
|
return Quadruple(width, height, x, y)
|
|
464
473
|
}
|
|
@@ -467,7 +476,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
467
476
|
for (index in 0 until children.length()) {
|
|
468
477
|
val node = children.optJSONObject(index) ?: continue
|
|
469
478
|
val style = node.optJSONObject("style") ?: JSONObject()
|
|
470
|
-
if (style.
|
|
479
|
+
if (craftNativeOptionalString(style.opt("display")) == "none") continue
|
|
471
480
|
val paint = inherited.merge(node)
|
|
472
481
|
when (node.optString("type")) {
|
|
473
482
|
"G" -> drawNodes(canvas, node.optJSONArray("children") ?: JSONArray(), paint)
|
|
@@ -480,12 +489,12 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
480
489
|
private fun drawPath(canvas: Canvas, node: JSONObject, style: SvgPaint) {
|
|
481
490
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
482
491
|
val geometry = props.optJSONArray("geometry") ?: return
|
|
483
|
-
if ((0 until geometry.length()).any {
|
|
492
|
+
if ((0 until geometry.length()).any { craftNativeNumber(geometry.opt(it))?.toFloat()?.isFinite() != true }) return
|
|
484
493
|
val path = Path()
|
|
485
494
|
var index = 0
|
|
486
495
|
while (index < geometry.length()) {
|
|
487
|
-
val command = geometry.
|
|
488
|
-
fun number(offset: Int): Float = geometry.
|
|
496
|
+
val command = craftNativeNumber(geometry.opt(index))?.toInt() ?: break
|
|
497
|
+
fun number(offset: Int): Float = craftNativeNumber(geometry.opt(index + offset))?.toFloat() ?: Float.NaN
|
|
489
498
|
when (command) {
|
|
490
499
|
0 -> if (index + 2 < geometry.length()) { path.moveTo(number(1), number(2)); index += 3 } else break
|
|
491
500
|
1 -> if (index + 2 < geometry.length()) { path.lineTo(number(1), number(2)); index += 3 } else break
|
|
@@ -551,9 +560,9 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
551
560
|
for (index in 0 until children.length()) if (children.opt(index) is String) append(children.optString(index))
|
|
552
561
|
}
|
|
553
562
|
if (text.isEmpty()) return
|
|
554
|
-
val size = (if (props.has("fontSize")) props.
|
|
555
|
-
.takeIf { it.isFinite() }
|
|
563
|
+
val size = (if (props.has("fontSize")) craftNativeNumber(props.opt("fontSize")) else craftNativeNumber(nodeStyle.opt("fontSize")))
|
|
556
564
|
?.toFloat()
|
|
565
|
+
?.takeIf { it.isFinite() }
|
|
557
566
|
?: 12f
|
|
558
567
|
val paint = android.graphics.Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
559
568
|
color = resolveColor(style.fill, style.color, Color.BLACK)
|
|
@@ -561,9 +570,9 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
561
570
|
textSize = size
|
|
562
571
|
typeface = style.typeface
|
|
563
572
|
}
|
|
564
|
-
var x = props.
|
|
565
|
-
val y = props.
|
|
566
|
-
when (props.
|
|
573
|
+
var x = craftNativeNumber(props.opt("x"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
574
|
+
val y = craftNativeNumber(props.opt("y"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
575
|
+
when (craftNativeOptionalString(props.opt("textAnchor"))) {
|
|
567
576
|
"middle" -> x -= paint.measureText(text) / 2f
|
|
568
577
|
"end" -> x -= paint.measureText(text)
|
|
569
578
|
}
|
|
@@ -629,16 +638,16 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
629
638
|
|
|
630
639
|
private fun applyPending() {
|
|
631
640
|
val map = googleMap ?: return
|
|
632
|
-
map.uiSettings.isScrollGesturesEnabled = pendingProps.
|
|
633
|
-
map.uiSettings.isZoomGesturesEnabled = pendingProps.
|
|
634
|
-
map.uiSettings.isRotateGesturesEnabled = pendingProps.
|
|
641
|
+
map.uiSettings.isScrollGesturesEnabled = craftNativeBoolean(pendingProps.opt("scrollEnabled"), true)
|
|
642
|
+
map.uiSettings.isZoomGesturesEnabled = craftNativeBoolean(pendingProps.opt("zoomEnabled"), true)
|
|
643
|
+
map.uiSettings.isRotateGesturesEnabled = craftNativeBoolean(pendingProps.opt("rotateEnabled"))
|
|
635
644
|
map.mapType = when (craftNativeOptionalString(pendingProps.opt("mapType")).orEmpty().lowercase()) {
|
|
636
645
|
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
|
|
637
646
|
"hybrid" -> GoogleMap.MAP_TYPE_HYBRID
|
|
638
647
|
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
|
|
639
648
|
else -> GoogleMap.MAP_TYPE_NORMAL
|
|
640
649
|
}
|
|
641
|
-
val showUserLocation = pendingProps.
|
|
650
|
+
val showUserLocation = craftNativeBoolean(pendingProps.opt("showsUserLocation")) && hasLocationPermission()
|
|
642
651
|
runCatching { map.isMyLocationEnabled = showUserLocation }
|
|
643
652
|
val nextMarkers = mutableMapOf<String, Pair<LatLng, String?>>()
|
|
644
653
|
val nextHandlers = mutableMapOf<String, String>()
|
|
@@ -654,7 +663,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
654
663
|
val points = coordinates(props.optJSONArray("geometry"))
|
|
655
664
|
if (type == "mapmarker" || type == "marker") {
|
|
656
665
|
points.firstOrNull()?.let { point ->
|
|
657
|
-
nextMarkers[id] = point to
|
|
666
|
+
nextMarkers[id] = point to (props.opt("title") as? String)
|
|
658
667
|
listOf(events.opt("onPress"), events.opt("onMarkerPress"))
|
|
659
668
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
660
669
|
?.let { nextHandlers[id] = it }
|
|
@@ -717,7 +726,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
717
726
|
moveCamera(initialRegion)
|
|
718
727
|
initialCameraKey = key
|
|
719
728
|
}
|
|
720
|
-
} else if (pendingProps.
|
|
729
|
+
} else if (craftNativeBoolean(pendingProps.opt("fitToBounds"))) {
|
|
721
730
|
val key = "fit:${overlayKey(nextMarkers, nextPolylines)}"
|
|
722
731
|
if (initialCameraKey != key) {
|
|
723
732
|
if (fitToBounds()) initialCameraKey = key
|
|
@@ -731,7 +740,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
731
740
|
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
732
741
|
// MapView can become ready before its host has a usable viewport. Do
|
|
733
742
|
// not mark a fit as complete until the camera update can be issued.
|
|
734
|
-
if (width > 0 && height > 0 && pendingProps.
|
|
743
|
+
if (width > 0 && height > 0 && craftNativeBoolean(pendingProps.opt("fitToBounds")) && initialCameraKey == null) {
|
|
735
744
|
applyPending()
|
|
736
745
|
}
|
|
737
746
|
}
|
|
@@ -1008,16 +1017,17 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1008
1017
|
}
|
|
1009
1018
|
|
|
1010
1019
|
fun configure(style: JSONObject, density: Float) {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1020
|
+
val flexDirection = craftNativeOptionalString(style.opt("flexDirection")).orEmpty()
|
|
1021
|
+
orientation = if (flexDirection in setOf("row", "row-reverse")) HORIZONTAL else VERTICAL
|
|
1022
|
+
reverse = flexDirection.endsWith("-reverse")
|
|
1023
|
+
val flexWrap = craftNativeOptionalString(style.opt("flexWrap")).orEmpty()
|
|
1024
|
+
wrap = flexWrap == "wrap" || flexWrap == "wrap-reverse" || craftNativeBoolean(style.opt("flexWrap"))
|
|
1015
1025
|
wrapReverse = flexWrap == "wrap-reverse"
|
|
1016
|
-
alignItems = style.
|
|
1017
|
-
alignContent = style.
|
|
1018
|
-
justifyContent = style.
|
|
1026
|
+
alignItems = craftNativeOptionalString(style.opt("alignItems")) ?: "stretch"
|
|
1027
|
+
alignContent = craftNativeOptionalString(style.opt("alignContent")) ?: "flex-start"
|
|
1028
|
+
justifyContent = craftNativeOptionalString(style.opt("justifyContent")) ?: "flex-start"
|
|
1019
1029
|
fun spacing(name: String, fallback: Float): Float {
|
|
1020
|
-
val value = style.
|
|
1030
|
+
val value = craftNativeNumber(style.opt(name)) ?: Double.NaN
|
|
1021
1031
|
val pixels = value * density
|
|
1022
1032
|
val narrowed = pixels.toFloat()
|
|
1023
1033
|
return if (value.isFinite() && value >= 0 && pixels.isFinite() && narrowed.isFinite()) narrowed else fallback
|
|
@@ -1025,7 +1035,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1025
1035
|
val gap = spacing("gap", 0f)
|
|
1026
1036
|
rowGap = spacing("rowGap", gap)
|
|
1027
1037
|
columnGap = spacing("columnGap", gap)
|
|
1028
|
-
grid = style.
|
|
1038
|
+
grid = craftNativeOptionalString(style.opt("display")) == "grid"
|
|
1029
1039
|
gridColumns = craftNativeGridColumnCount(
|
|
1030
1040
|
if (style.has("gridTemplateColumns")) style.opt("gridTemplateColumns") else style.opt("gridColumns")
|
|
1031
1041
|
)
|
|
@@ -2076,7 +2086,7 @@ private class CraftNativeKeyboardAvoidingView(context: Context) : FrameLayout(co
|
|
|
2076
2086
|
}
|
|
2077
2087
|
|
|
2078
2088
|
fun configure(props: JSONObject, style: JSONObject, density: Float) {
|
|
2079
|
-
behavior = props.
|
|
2089
|
+
behavior = craftNativeOptionalString(props.opt("behavior"))?.takeIf { it in setOf("height", "position", "padding") } ?: "padding"
|
|
2080
2090
|
val authoredOffset = craftNativeFiniteDouble(props, "keyboardVerticalOffset", 0.0)
|
|
2081
2091
|
val pixelOffset = (authoredOffset * density.toDouble()).takeIf { it.isFinite() } ?: 0.0
|
|
2082
2092
|
keyboardVerticalOffset = pixelOffset.roundToInt()
|
|
@@ -2734,7 +2744,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2734
2744
|
}
|
|
2735
2745
|
|
|
2736
2746
|
fun configure(props: JSONObject) {
|
|
2737
|
-
val requestedMode = when (props.
|
|
2747
|
+
val requestedMode = when (props.opt("mode") as? String) {
|
|
2738
2748
|
"time" -> PickerMode.TIME
|
|
2739
2749
|
"datetime", "dateAndTime", "datetime-local" -> PickerMode.DATE_TIME
|
|
2740
2750
|
"countdown" -> PickerMode.COUNTDOWN
|
|
@@ -2774,7 +2784,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2774
2784
|
}
|
|
2775
2785
|
|
|
2776
2786
|
suppressChanges = true
|
|
2777
|
-
minuteInterval = props.
|
|
2787
|
+
minuteInterval = craftNativeInt(props.opt("minuteInterval"), 1).takeIf { it in 1..30 && 60 % it == 0 } ?: 1
|
|
2778
2788
|
val defaults = defaultDateBounds
|
|
2779
2789
|
datePicker?.minDate = pickerDate(props.opt("minimumDate"))?.timeInMillis ?: defaults?.first ?: datePicker?.minDate ?: 0L
|
|
2780
2790
|
datePicker?.maxDate = pickerDate(props.opt("maximumDate"))?.timeInMillis ?: defaults?.second ?: datePicker?.maxDate ?: Long.MAX_VALUE
|
|
@@ -2784,7 +2794,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2784
2794
|
} else {
|
|
2785
2795
|
pickerDate(props.opt("value"))?.let { applyDate(it) }
|
|
2786
2796
|
}
|
|
2787
|
-
val enabled = !props.
|
|
2797
|
+
val enabled = !craftNativeBoolean(props.opt("disabled"))
|
|
2788
2798
|
isEnabled = enabled
|
|
2789
2799
|
datePicker?.isEnabled = enabled
|
|
2790
2800
|
timePicker?.isEnabled = enabled
|
|
@@ -3939,6 +3949,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3939
3949
|
val bars = CraftNativeChrome(this, routes.tabs)
|
|
3940
3950
|
bars.layoutParams = FrameLayout.LayoutParams(-1, -1)
|
|
3941
3951
|
bars.onBack = { onBackPressedDispatcherCompat() }
|
|
3952
|
+
bars.onAccessory = { path -> navigateOpen(path) }
|
|
3942
3953
|
bars.onButton = { id -> visibleScreen?.takeIf { it.active }?.let { send(it, "NAV_BUTTON", JSONObject().put("id", id)) } }
|
|
3943
3954
|
bars.onTab = { index ->
|
|
3944
3955
|
haptic()
|
|
@@ -4925,8 +4936,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
4925
4936
|
val control = when (type) {
|
|
4926
4937
|
"Text" -> ((previous as? TextView)?.takeUnless { it is Button || it is EditText } ?: TextView(this)).apply {
|
|
4927
4938
|
includeFontPadding = false
|
|
4928
|
-
maxLines = props.
|
|
4929
|
-
ellipsize = when (props.
|
|
4939
|
+
maxLines = craftNativeInt(props.opt("numberOfLines"), 0).takeIf { it > 0 } ?: Int.MAX_VALUE
|
|
4940
|
+
ellipsize = when (craftNativeOptionalString(props.opt("ellipsizeMode"))) {
|
|
4930
4941
|
"head" -> TextUtils.TruncateAt.START
|
|
4931
4942
|
"middle" -> TextUtils.TruncateAt.MIDDLE
|
|
4932
4943
|
"tail" -> TextUtils.TruncateAt.END
|
|
@@ -4936,7 +4947,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4936
4947
|
}
|
|
4937
4948
|
"StatusBar" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
4938
4949
|
screen.statusBarStyle = craftNativeOptionalString(props.opt("barStyle"))
|
|
4939
|
-
screen.statusBarHidden = if (props.has("hidden") && !props.isNull("hidden")) props.
|
|
4950
|
+
screen.statusBarHidden = if (props.has("hidden") && !props.isNull("hidden")) craftNativeBoolean(props.opt("hidden")) else null
|
|
4940
4951
|
visibility = View.GONE
|
|
4941
4952
|
}
|
|
4942
4953
|
"RefreshControl" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
@@ -4944,17 +4955,18 @@ class MainActivity : AppCompatActivity() {
|
|
|
4944
4955
|
}
|
|
4945
4956
|
"Link" -> ((previous as? TextView)?.takeUnless { it is Button } ?: TextView(this)).apply {
|
|
4946
4957
|
includeFontPadding = false
|
|
4947
|
-
configureText(this,
|
|
4958
|
+
configureText(this, (props.opt("title") as? String) ?: childText(children), style)
|
|
4948
4959
|
val linkColor = craftNativeOptionalString(props.opt("color")) ?: craftNativeOptionalString(style.opt("color"))
|
|
4949
4960
|
setTextColor(color(linkColor.orEmpty(), Color.rgb(33, 150, 243)))
|
|
4950
4961
|
paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
|
4951
|
-
isEnabled = !props.
|
|
4962
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4952
4963
|
isClickable = true
|
|
4953
4964
|
isFocusable = true
|
|
4954
4965
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
4955
|
-
.
|
|
4966
|
+
.mapNotNull { it as? String }
|
|
4967
|
+
.firstOrNull { it.trim().isNotEmpty() }
|
|
4956
4968
|
?.trim()
|
|
4957
|
-
?.takeIf { !props.
|
|
4969
|
+
?.takeIf { !craftNativeBoolean(props.opt("disabled")) }
|
|
4958
4970
|
if (target != null) screen.linkTargets[this] = target else screen.linkTargets.remove(this)
|
|
4959
4971
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4960
4972
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
@@ -4975,13 +4987,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
4975
4987
|
minimumHeight = 0
|
|
4976
4988
|
setPadding(0, 0, 0, 0)
|
|
4977
4989
|
}).apply {
|
|
4978
|
-
val title =
|
|
4990
|
+
val title = (props.opt("title") as? String) ?: childText(children)
|
|
4979
4991
|
isAllCaps = false
|
|
4980
4992
|
configureText(this, title, style)
|
|
4981
4993
|
val buttonColor = craftNativeOptionalString(props.opt("color"))
|
|
4982
4994
|
?: craftNativeOptionalString(style.opt("color"))
|
|
4983
4995
|
setTextColor(buttonColor?.let { color(it, defaultTextColor) } ?: defaultTextColor)
|
|
4984
|
-
isEnabled = !props.
|
|
4996
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4985
4997
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4986
4998
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
4987
4999
|
setOnClickListener {
|
|
@@ -4990,7 +5002,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4990
5002
|
}
|
|
4991
5003
|
}
|
|
4992
5004
|
"Checkbox" -> (previous as? CheckBox ?: CheckBox(this)).apply {
|
|
4993
|
-
text =
|
|
5005
|
+
text = (props.opt("label") as? String) ?: childText(children)
|
|
4994
5006
|
configureText(this, text.toString(), style)
|
|
4995
5007
|
val checkboxChecked = props.opt("checked")
|
|
4996
5008
|
val checkboxValue = props.opt("value")
|
|
@@ -5006,7 +5018,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5006
5018
|
isChecked = checked
|
|
5007
5019
|
val indeterminate = (props.opt("indeterminate") as? Boolean) == true
|
|
5008
5020
|
isActivated = indeterminate
|
|
5009
|
-
isEnabled = !props.
|
|
5021
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5010
5022
|
val checkboxColor = craftNativeOptionalString(props.opt("color"))
|
|
5011
5023
|
?: craftNativeOptionalString(style.opt("color"))
|
|
5012
5024
|
buttonTintList = checkboxColor
|
|
@@ -5035,17 +5047,17 @@ class MainActivity : AppCompatActivity() {
|
|
|
5035
5047
|
val defaultPadding = inputPaddingDefaults.getOrPut(this) {
|
|
5036
5048
|
intArrayOf(paddingLeft, paddingTop, paddingRight, paddingBottom)
|
|
5037
5049
|
}
|
|
5038
|
-
hint =
|
|
5050
|
+
hint = (props.opt("placeholder") as? String).orEmpty()
|
|
5039
5051
|
setHintTextColor(
|
|
5040
|
-
|
|
5052
|
+
(props.opt("placeholderTextColor") as? String)
|
|
5041
5053
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5042
5054
|
?.let { ColorStateList.valueOf(it) }
|
|
5043
5055
|
?: defaultHintColors,
|
|
5044
5056
|
)
|
|
5045
|
-
highlightColor =
|
|
5057
|
+
highlightColor = (props.opt("selectionColor") as? String)
|
|
5046
5058
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5047
5059
|
?: defaultHighlightColor
|
|
5048
|
-
val maxLength = props.
|
|
5060
|
+
val maxLength = craftNativeInt(props.opt("maxLength"), 0)
|
|
5049
5061
|
val currentLength = (filters.firstOrNull { it is InputFilter.LengthFilter } as? InputFilter.LengthFilter)?.max ?: 0
|
|
5050
5062
|
if (currentLength != maxLength || (maxLength == 0 && filters.isNotEmpty())) {
|
|
5051
5063
|
filters = if (maxLength > 0) arrayOf(InputFilter.LengthFilter(maxLength)) else emptyArray()
|
|
@@ -5054,11 +5066,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5054
5066
|
if (inputType != wantedType) inputType = wantedType
|
|
5055
5067
|
val wantedIme = imeOptionsFor(props)
|
|
5056
5068
|
if (imeOptions != wantedIme) imeOptions = wantedIme
|
|
5057
|
-
val multiline = props.
|
|
5069
|
+
val multiline = craftNativeBoolean(props.opt("multiline"))
|
|
5058
5070
|
if (isSingleLine == multiline) isSingleLine = !multiline
|
|
5059
|
-
val wantedLines = if (multiline) props.
|
|
5071
|
+
val wantedLines = if (multiline) craftNativeInt(props.opt("numberOfLines"), Int.MAX_VALUE).coerceAtLeast(1) else 1
|
|
5060
5072
|
if (maxLines != wantedLines) maxLines = wantedLines
|
|
5061
|
-
isEnabled = props.
|
|
5073
|
+
isEnabled = craftNativeBoolean(props.opt("editable"), true)
|
|
5062
5074
|
configureTextStyle(this, style)
|
|
5063
5075
|
// The style's padding is where the text starts, as on iOS and
|
|
5064
5076
|
// the web; without one the field keeps its own.
|
|
@@ -5086,14 +5098,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5086
5098
|
// them after keyed reconciliation.
|
|
5087
5099
|
val autofillHint = autofillHintFor(props)
|
|
5088
5100
|
if (autofillHint == null) setAutofillHints(*emptyArray()) else setAutofillHints(autofillHint)
|
|
5089
|
-
val controlledValue = props.opt("value")
|
|
5101
|
+
val controlledValue = props.opt("value") as? String
|
|
5090
5102
|
val echoes = screen.inputEchoes.getOrPut(this) { CraftNativeInputEcho() }
|
|
5091
5103
|
if (echoes.accept(controlledValue, text.toString()) && controlledValue != null) {
|
|
5092
5104
|
screen.suppressedTextChanges.add(this)
|
|
5093
5105
|
setTextPreservingSelection(this, controlledValue)
|
|
5094
5106
|
}
|
|
5095
5107
|
else if (this !== previous && screen.drafts.containsKey(identity)) setText(screen.drafts[identity])
|
|
5096
|
-
else if (this !== previous && props.has("defaultValue") && !props.isNull("defaultValue"))
|
|
5108
|
+
else if (this !== previous && props.has("defaultValue") && !props.isNull("defaultValue")) {
|
|
5109
|
+
(props.opt("defaultValue") as? String)?.let(::setText)
|
|
5110
|
+
}
|
|
5097
5111
|
applyTextDecoration(this, style)
|
|
5098
5112
|
screen.inputIdentities[this] = identity
|
|
5099
5113
|
val handler = craftNativeEventName(events, "onChange", "onChangeText")
|
|
@@ -5128,7 +5142,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5128
5142
|
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("text", text.toString())))
|
|
5129
5143
|
true
|
|
5130
5144
|
}
|
|
5131
|
-
if (props.
|
|
5145
|
+
if (craftNativeBoolean(props.opt("autoFocus")) && !isFocused) post {
|
|
5132
5146
|
if (!isFocused) {
|
|
5133
5147
|
requestFocus()
|
|
5134
5148
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
@@ -5158,7 +5172,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5158
5172
|
val itemOptions = props.optJSONArray("items")?.let { items ->
|
|
5159
5173
|
(0 until items.length()).mapNotNull { index ->
|
|
5160
5174
|
val item = items.optJSONObject(index) ?: return@mapNotNull null
|
|
5161
|
-
val label =
|
|
5175
|
+
val label = (item.opt("label") as? String)?.takeIf { it.isNotEmpty() } ?: return@mapNotNull null
|
|
5162
5176
|
craftNativePickerValue(item.opt("value"))?.let { it to label } ?: (label to label)
|
|
5163
5177
|
}
|
|
5164
5178
|
}.orEmpty()
|
|
@@ -5166,7 +5180,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5166
5180
|
val option = children.optJSONObject(index) ?: return@mapNotNull null
|
|
5167
5181
|
if (option.optString("type") != "Text") return@mapNotNull null
|
|
5168
5182
|
val label = (option.optJSONArray("children") ?: JSONArray()).let { text ->
|
|
5169
|
-
(0 until text.length()).
|
|
5183
|
+
(0 until text.length()).mapNotNull { text.opt(it) as? String }.joinToString("")
|
|
5170
5184
|
}
|
|
5171
5185
|
if (label.isBlank()) return@mapNotNull null
|
|
5172
5186
|
val optionProps = option.optJSONObject("props")
|
|
@@ -5185,7 +5199,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5185
5199
|
val requested = craftNativePickerValue(props.opt("selectedValue"))
|
|
5186
5200
|
?: craftNativePickerValue(props.opt("value")).orEmpty()
|
|
5187
5201
|
val selected = options.indexOfFirst { it.first == requested }.takeIf { it >= 0 } ?: 0
|
|
5188
|
-
isEnabled = !props.
|
|
5202
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5189
5203
|
// Mounting a controlled picker must not look like a user
|
|
5190
5204
|
// change. Android may dispatch the initial adapter selection
|
|
5191
5205
|
// during the first layout pass; iOS only reports a choice
|
|
@@ -5215,7 +5229,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5215
5229
|
}
|
|
5216
5230
|
}
|
|
5217
5231
|
"Modal" -> (previous as? CraftNativeModalView ?: CraftNativeModalView(this)).apply {
|
|
5218
|
-
transparent = props.
|
|
5232
|
+
transparent = craftNativeBoolean(props.opt("transparent"))
|
|
5219
5233
|
content.configure(style, resources.displayMetrics.density)
|
|
5220
5234
|
reconcileChildren(screen, content, children, identity, next, style, previousControls)
|
|
5221
5235
|
// Dynamic :visible bindings arrive as a display style mutation;
|
|
@@ -5224,9 +5238,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5224
5238
|
// An explicit visible prop owns modal visibility. The
|
|
5225
5239
|
// binding runtime may leave a stale display:none style
|
|
5226
5240
|
// from the previous hidden state when it patches props.
|
|
5227
|
-
props.
|
|
5241
|
+
craftNativeBoolean(props.opt("visible"))
|
|
5228
5242
|
} else {
|
|
5229
|
-
style.has("display") && style.
|
|
5243
|
+
style.has("display") && craftNativeOptionalString(style.opt("display")) != "none"
|
|
5230
5244
|
}
|
|
5231
5245
|
val wasVisible = screen.modalVisibility[this]
|
|
5232
5246
|
screen.modalVisibility[this] = visible
|
|
@@ -5260,7 +5274,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5260
5274
|
}
|
|
5261
5275
|
"TouchableOpacity", "TouchableHighlight", "Pressable" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
5262
5276
|
configureContainer(this, style)
|
|
5263
|
-
val disabled = props.
|
|
5277
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5264
5278
|
screen.pressActiveOpacities[this] = craftNativeFiniteFloat(props, "activeOpacity", 0.5f)
|
|
5265
5279
|
screen.pressBaseOpacities[this] = craftNativeFiniteFloat(style, "opacity", 1f)
|
|
5266
5280
|
if (type == "TouchableHighlight" && !disabled) {
|
|
@@ -5281,7 +5295,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5281
5295
|
requestedChecked is Boolean -> requestedChecked
|
|
5282
5296
|
else -> false
|
|
5283
5297
|
}
|
|
5284
|
-
isEnabled = !props.
|
|
5298
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5285
5299
|
props.optJSONObject("trackColor")?.let { track ->
|
|
5286
5300
|
val falseRaw = craftNativeOptionalString(track.opt("false"))
|
|
5287
5301
|
val trueRaw = craftNativeOptionalString(track.opt("true"))
|
|
@@ -5309,11 +5323,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5309
5323
|
}
|
|
5310
5324
|
}
|
|
5311
5325
|
"Slider" -> (previous as? SeekBar ?: SeekBar(this)).apply {
|
|
5312
|
-
val minimum =
|
|
5313
|
-
val maximumCandidate =
|
|
5326
|
+
val minimum = craftNativeFiniteNumber(props, "minimumValue", 0.0)
|
|
5327
|
+
val maximumCandidate = craftNativeFiniteNumber(props, "maximumValue", 1.0)
|
|
5314
5328
|
val maximum = if (maximumCandidate > minimum) maximumCandidate else minimum + Double.MIN_VALUE
|
|
5315
5329
|
setOnSeekBarChangeListener(null)
|
|
5316
|
-
val step =
|
|
5330
|
+
val step = craftNativeFiniteNumber(props, "step", 0.0).takeIf { it > 0 }
|
|
5317
5331
|
// A stepped slider needs one native position per authored
|
|
5318
5332
|
// step; a fixed 1,000-position SeekBar silently made large
|
|
5319
5333
|
// ranges unable to represent most valid values. Continuous
|
|
@@ -5327,7 +5341,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5327
5341
|
.toInt()
|
|
5328
5342
|
} ?: 1000
|
|
5329
5343
|
max = progressMax
|
|
5330
|
-
val requested =
|
|
5344
|
+
val requested = craftNativeFiniteNumber(props, "value", minimum)
|
|
5331
5345
|
val value = (if (step != null) minimum + ((requested - minimum) / step).roundToInt() * step else requested)
|
|
5332
5346
|
.coerceIn(minimum, maximum)
|
|
5333
5347
|
progress = if (step != null) {
|
|
@@ -5345,7 +5359,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5345
5359
|
thumbTintList = craftNativeOptionalString(props.opt("thumbTintColor"))
|
|
5346
5360
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5347
5361
|
?.let { ColorStateList.valueOf(it) }
|
|
5348
|
-
isEnabled = !props.
|
|
5362
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5349
5363
|
val handler = craftNativeEventName(events, "onValueChange", "onChange")
|
|
5350
5364
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
5351
5365
|
val complete = craftNativeEventName(events, "onSlidingComplete")
|
|
@@ -5379,9 +5393,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5379
5393
|
else -> 1f
|
|
5380
5394
|
}
|
|
5381
5395
|
indicatorScale = scale
|
|
5382
|
-
val animating = props.
|
|
5396
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5383
5397
|
isIndeterminate = animating
|
|
5384
|
-
visibility = if (!animating && props.
|
|
5398
|
+
visibility = if (!animating && craftNativeBoolean(props.opt("hidesWhenStopped"), true)) View.GONE else View.VISIBLE
|
|
5385
5399
|
indeterminateTintList = craftNativeOptionalString(props.opt("color"))
|
|
5386
5400
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5387
5401
|
?.let { ColorStateList.valueOf(it) }
|
|
@@ -5437,13 +5451,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
5437
5451
|
onPictureInPictureChanged = { updatePictureInPicture() }
|
|
5438
5452
|
configure(
|
|
5439
5453
|
source = props.opt("source") ?: props.opt("src"),
|
|
5440
|
-
autoplay = props.
|
|
5441
|
-
paused = props.
|
|
5442
|
-
loop = props.
|
|
5443
|
-
muted = props.
|
|
5444
|
-
controls = props.
|
|
5454
|
+
autoplay = craftNativeBoolean(props.opt("autoplay"), craftNativeBoolean(props.opt("autoPlay"))),
|
|
5455
|
+
paused = craftNativeBoolean(props.opt("paused")),
|
|
5456
|
+
loop = craftNativeBoolean(props.opt("loop")),
|
|
5457
|
+
muted = craftNativeBoolean(props.opt("muted")),
|
|
5458
|
+
controls = craftNativeBoolean(props.opt("controls")),
|
|
5445
5459
|
resizeMode = craftNativeResizeMode(style, props),
|
|
5446
|
-
pictureInPicture = props.
|
|
5460
|
+
pictureInPicture = craftNativeBoolean(props.opt("pictureInPicture")),
|
|
5447
5461
|
)
|
|
5448
5462
|
}
|
|
5449
5463
|
"Map" -> (previous as? CraftNativeMapView ?: CraftNativeMapView(this)).apply {
|
|
@@ -5458,21 +5472,21 @@ class MainActivity : AppCompatActivity() {
|
|
|
5458
5472
|
drawing = node
|
|
5459
5473
|
}
|
|
5460
5474
|
"ScrollView", "SectionList" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
|
|
5461
|
-
val horizontal = props.
|
|
5475
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal")) || craftNativeOptionalString(style.opt("flexDirection")) in setOf("row", "row-reverse")
|
|
5462
5476
|
setHorizontal(horizontal)
|
|
5463
5477
|
setNativeNestedScrollingEnabled(true)
|
|
5464
5478
|
setScrollIndicators(
|
|
5465
|
-
props.
|
|
5466
|
-
props.
|
|
5479
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5480
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5467
5481
|
)
|
|
5468
|
-
setScrollEnabled(props.
|
|
5469
|
-
setKeyboardShouldPersistTaps(props.
|
|
5470
|
-
setBounces(props.
|
|
5482
|
+
setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5483
|
+
setKeyboardShouldPersistTaps(craftNativeOptionalString(props.opt("keyboardShouldPersistTaps")) ?: "never")
|
|
5484
|
+
setBounces(craftNativeBoolean(props.opt("bounces"), true))
|
|
5471
5485
|
setAlwaysBounce(
|
|
5472
|
-
props.
|
|
5473
|
-
props.
|
|
5486
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5487
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5474
5488
|
)
|
|
5475
|
-
setPagingEnabled(props.
|
|
5489
|
+
setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5476
5490
|
val refreshNode = (0 until children.length()).asSequence()
|
|
5477
5491
|
.mapNotNull { children.optJSONObject(it) }
|
|
5478
5492
|
.firstOrNull { canonicalType(it.optString("type")) == "RefreshControl" }
|
|
@@ -5485,7 +5499,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5485
5499
|
val refreshHandler = refreshHandlerName?.let { handler ->
|
|
5486
5500
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5487
5501
|
}
|
|
5488
|
-
setRefreshHandler(refreshHandler, refreshProps.
|
|
5502
|
+
setRefreshHandler(refreshHandler, craftNativeBoolean(refreshProps.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5489
5503
|
setRefreshAppearance(refreshProps, resources.displayMetrics.density, refreshTintFallback(screen))
|
|
5490
5504
|
val onScroll = craftNativeEventName(events, "onScroll")
|
|
5491
5505
|
if (onScroll.isNotBlank()) screen.scrollHandlers[this] = onScroll else screen.scrollHandlers.remove(this)
|
|
@@ -5528,7 +5542,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5528
5542
|
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
5529
5543
|
}
|
|
5530
5544
|
},
|
|
5531
|
-
keyboardDismissMode = props.
|
|
5545
|
+
keyboardDismissMode = craftNativeOptionalString(props.opt("keyboardDismissMode")).orEmpty(),
|
|
5532
5546
|
dismissKeyboard = {
|
|
5533
5547
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
5534
5548
|
?.hideSoftInputFromWindow(nativeScroll.windowToken, 0)
|
|
@@ -5563,11 +5577,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5563
5577
|
?: craftNativeEventName(events, "onRefresh").takeIf { it.isNotBlank() }
|
|
5564
5578
|
configureRefresh(refreshHandlerName?.let { handler ->
|
|
5565
5579
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5566
|
-
}, propRefresh.
|
|
5580
|
+
}, craftNativeBoolean(propRefresh.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5567
5581
|
setRefreshAppearance(propRefresh, refreshTintFallback(screen))
|
|
5568
|
-
nativeList.setKeyboardShouldPersistTaps(props.
|
|
5582
|
+
nativeList.setKeyboardShouldPersistTaps(craftNativeOptionalString(props.opt("keyboardShouldPersistTaps")) ?: "never")
|
|
5569
5583
|
nativeList.setContentContainerStyle(props.optJSONObject("contentContainerStyle"), resources.displayMetrics.density)
|
|
5570
|
-
nativeList.setKeyboardDismissMode(props.
|
|
5584
|
+
nativeList.setKeyboardDismissMode(craftNativeOptionalString(props.opt("keyboardDismissMode")).orEmpty()) {
|
|
5571
5585
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
5572
5586
|
?.hideSoftInputFromWindow(windowToken, 0)
|
|
5573
5587
|
}
|
|
@@ -5576,6 +5590,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5576
5590
|
"View", "SafeAreaView" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
5577
5591
|
configureContainer(this, style)
|
|
5578
5592
|
if (type == "SafeAreaView") setSafeAreaEdges(props.optJSONArray("edges"))
|
|
5593
|
+
else setWindowSafeInsets(0, 0, 0, 0)
|
|
5579
5594
|
reconcileChildren(screen, this, children, identity, next, style, previousControls)
|
|
5580
5595
|
}
|
|
5581
5596
|
"KeyboardAvoidingView" -> (previous as? CraftNativeKeyboardAvoidingView ?: CraftNativeKeyboardAvoidingView(this)).apply {
|
|
@@ -5603,7 +5618,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5603
5618
|
if (underlayHideHandler != null) screen.pressUnderlayHideHandlers[control] = underlayHideHandler else screen.pressUnderlayHideHandlers.remove(control)
|
|
5604
5619
|
if (type != "Button" && type != "Link" && type != "Checkbox" && type != "TextInput" && type != "Picker" && type != "DatePicker" && type != "Modal" && type != "Switch" && type != "Slider" && type != "ActivityIndicator" && type != "KeyboardAvoidingView" && type != "StatusBar" && type != "RefreshControl" && type != "Map") {
|
|
5605
5620
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
5606
|
-
val disabled = props.
|
|
5621
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5607
5622
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
5608
5623
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
5609
5624
|
?.trim()
|
|
@@ -5674,8 +5689,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5674
5689
|
}
|
|
5675
5690
|
control.isEnabled = !disabled
|
|
5676
5691
|
}
|
|
5677
|
-
val longPressDisabled = props.
|
|
5678
|
-
props.optJSONObject("accessibilityState")?.
|
|
5692
|
+
val longPressDisabled = craftNativeBoolean(props.opt("disabled")) ||
|
|
5693
|
+
craftNativeBoolean(props.optJSONObject("accessibilityState")?.opt("disabled"))
|
|
5679
5694
|
val longPressHandler = craftNativeEventName(events, "onLongPress").takeUnless { longPressDisabled } ?: ""
|
|
5680
5695
|
if (longPressHandler.isNotBlank()) {
|
|
5681
5696
|
screen.longPressHandlers[control] = longPressHandler
|
|
@@ -5720,9 +5735,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5720
5735
|
next[identity] = control
|
|
5721
5736
|
applyViewStyle(control, style)
|
|
5722
5737
|
if (type == "ActivityIndicator") {
|
|
5723
|
-
val animating = props.
|
|
5724
|
-
val hidesWhenStopped = props.
|
|
5725
|
-
control.visibility = if (style.
|
|
5738
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5739
|
+
val hidesWhenStopped = craftNativeBoolean(props.opt("hidesWhenStopped"), true)
|
|
5740
|
+
control.visibility = if (craftNativeOptionalString(style.opt("display")) == "none" || (!animating && hidesWhenStopped)) {
|
|
5726
5741
|
View.GONE
|
|
5727
5742
|
} else {
|
|
5728
5743
|
View.VISIBLE
|
|
@@ -5843,7 +5858,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5843
5858
|
layout.setChildStyle(childView, child.optJSONObject("style") ?: JSONObject(), resources.displayMetrics.density)
|
|
5844
5859
|
}
|
|
5845
5860
|
}
|
|
5846
|
-
if (style.
|
|
5861
|
+
if (craftNativeOptionalString(style.opt("flexDirection"))?.endsWith("-reverse") == true) desired.reverse()
|
|
5847
5862
|
val desiredSet = desired.toSet()
|
|
5848
5863
|
for ((index, childView) in desired.withIndex()) {
|
|
5849
5864
|
if (layout.getChildAt(index) !== childView) {
|
|
@@ -5892,12 +5907,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
5892
5907
|
}
|
|
5893
5908
|
val threshold = craftNativeNonNegativeDouble(props, "threshold", 0.1)
|
|
5894
5909
|
val authoredThreshold = craftNativeNonNegativeDouble(props, "onEndReachedThreshold", threshold)
|
|
5895
|
-
val horizontal = props.
|
|
5910
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal"))
|
|
5896
5911
|
list.apply(
|
|
5897
5912
|
nodes = nodes,
|
|
5898
5913
|
horizontal = horizontal,
|
|
5899
|
-
columns = props.
|
|
5900
|
-
inverted = props.
|
|
5914
|
+
columns = craftNativeInt(props.opt("numColumns"), 1),
|
|
5915
|
+
inverted = craftNativeBoolean(props.opt("inverted")),
|
|
5901
5916
|
endReachedThreshold = authoredThreshold,
|
|
5902
5917
|
renderItem = { node, itemIdentity, _ ->
|
|
5903
5918
|
val rows = screen.flatListRows.getOrPut(list) { mutableMapOf() }
|
|
@@ -5917,16 +5932,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5917
5932
|
},
|
|
5918
5933
|
)
|
|
5919
5934
|
list.setScrollIndicators(
|
|
5920
|
-
props.
|
|
5921
|
-
props.
|
|
5935
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5936
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5922
5937
|
)
|
|
5923
|
-
list.setScrollEnabled(props.
|
|
5924
|
-
list.setPagingEnabled(props.
|
|
5938
|
+
list.setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5939
|
+
list.setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5925
5940
|
list.setScrollTarget(props.opt("scrollTarget"))
|
|
5926
5941
|
list.setBounceOptions(
|
|
5927
|
-
props.
|
|
5928
|
-
props.
|
|
5929
|
-
props.
|
|
5942
|
+
craftNativeBoolean(props.opt("bounces"), true),
|
|
5943
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5944
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5930
5945
|
)
|
|
5931
5946
|
}
|
|
5932
5947
|
|
|
@@ -5970,7 +5985,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5970
5985
|
}
|
|
5971
5986
|
applyTransform(view, style.optJSONArray("transform"))
|
|
5972
5987
|
if (view !is CraftNativeModalView) {
|
|
5973
|
-
view.visibility = if (style.
|
|
5988
|
+
view.visibility = if (craftNativeOptionalString(style.opt("display")) == "none") View.GONE else View.VISIBLE
|
|
5974
5989
|
}
|
|
5975
5990
|
view.elevation = dp(craftNativeNonNegativeDouble(style, "elevation", 0.0)).toFloat()
|
|
5976
5991
|
view.z = craftNativeFiniteFloat(style, "zIndex", 0f)
|
|
@@ -5979,13 +5994,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
5979
5994
|
// background paints over the decoded picture (only rounded corners
|
|
5980
5995
|
// showed it). Keep the outline/borders, with a transparent fill.
|
|
5981
5996
|
val backgroundColor = if (view is CraftNativeVideoView) Color.TRANSPARENT
|
|
5982
|
-
else color(style.
|
|
5997
|
+
else color(craftNativeOptionalString(style.opt("backgroundColor")).orEmpty(), Color.TRANSPARENT)
|
|
5983
5998
|
val border = craftNativeNonNegativeDouble(style, "borderWidth", 0.0)
|
|
5984
5999
|
val radius = craftNativeNonNegativeDouble(style, "borderRadius", 0.0)
|
|
5985
|
-
val gradientDirection = style.
|
|
5986
|
-
val gradientFrom = style.
|
|
5987
|
-
val gradientVia = style.
|
|
5988
|
-
val gradientTo = style.
|
|
6000
|
+
val gradientDirection = craftNativeOptionalString(style.opt("gradientDirection"))
|
|
6001
|
+
val gradientFrom = craftNativeOptionalString(style.opt("gradientFrom"))
|
|
6002
|
+
val gradientVia = craftNativeOptionalString(style.opt("gradientVia"))
|
|
6003
|
+
val gradientTo = craftNativeOptionalString(style.opt("gradientTo"))
|
|
5989
6004
|
val gradientColors = if (gradientDirection != null && gradientFrom != null && gradientTo != null) {
|
|
5990
6005
|
listOfNotNull(
|
|
5991
6006
|
color(gradientFrom, Color.TRANSPARENT),
|
|
@@ -6003,7 +6018,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6003
6018
|
listOf(radius, radius)
|
|
6004
6019
|
}.toFloatArray()
|
|
6005
6020
|
val sideColors = listOf("borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor")
|
|
6006
|
-
.map { color(style.
|
|
6021
|
+
.map { color(craftNativeOptionalString(style.opt(it)) ?: craftNativeOptionalString(style.opt("borderColor")).orEmpty(), Color.TRANSPARENT) }
|
|
6007
6022
|
.toIntArray()
|
|
6008
6023
|
val defaultBackground = if (defaultBackgrounds.containsKey(view)) {
|
|
6009
6024
|
defaultBackgrounds[view]
|
|
@@ -6017,7 +6032,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6017
6032
|
cornerRadii,
|
|
6018
6033
|
sideWidths,
|
|
6019
6034
|
sideColors,
|
|
6020
|
-
style.
|
|
6035
|
+
craftNativeOptionalString(style.opt("borderStyle"))?.takeIf { it == "dashed" || it == "dotted" },
|
|
6021
6036
|
gradientDirection,
|
|
6022
6037
|
gradientColors,
|
|
6023
6038
|
)
|
|
@@ -6029,8 +6044,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6029
6044
|
} else setColor(backgroundColor)
|
|
6030
6045
|
if (hasCornerRadii) setCornerRadii(cornerRadii) else cornerRadius = dp(radius).toFloat()
|
|
6031
6046
|
if (border > 0) {
|
|
6032
|
-
val borderColor = color(style.
|
|
6033
|
-
when (style.
|
|
6047
|
+
val borderColor = color(craftNativeOptionalString(style.opt("borderColor")).orEmpty(), Color.TRANSPARENT)
|
|
6048
|
+
when (craftNativeOptionalString(style.opt("borderStyle"))) {
|
|
6034
6049
|
"dashed" -> setStroke(dp(border), borderColor, dp(border * 3).toFloat(), dp(border * 2).toFloat())
|
|
6035
6050
|
"dotted" -> setStroke(dp(border), borderColor, dp(border).toFloat(), dp(border * 1.5).toFloat())
|
|
6036
6051
|
else -> setStroke(dp(border), borderColor)
|
|
@@ -6043,13 +6058,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
6043
6058
|
// and the web.
|
|
6044
6059
|
null
|
|
6045
6060
|
} else defaultBackground
|
|
6046
|
-
|
|
6047
|
-
|
|
6061
|
+
val overflow = craftNativeOptionalString(style.opt("overflow"))
|
|
6062
|
+
view.clipToOutline = overflow == "hidden"
|
|
6063
|
+
if (view is ViewGroup) view.clipChildren = overflow == "hidden"
|
|
6048
6064
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
6049
6065
|
(style.has("shadowColor") || style.has("shadowOpacity") || style.has("shadowRadius"))
|
|
6050
6066
|
) {
|
|
6051
6067
|
val shadowOpacity = craftNativeFiniteFloat(style, "shadowOpacity", 1f).coerceIn(0f, 1f)
|
|
6052
|
-
val shadow = color(style.
|
|
6068
|
+
val shadow = color(craftNativeOptionalString(style.opt("shadowColor")).orEmpty(), Color.BLACK)
|
|
6053
6069
|
val alphaShadow = Color.argb((Color.alpha(shadow) * shadowOpacity).roundToInt(), Color.red(shadow), Color.green(shadow), Color.blue(shadow))
|
|
6054
6070
|
view.outlineAmbientShadowColor = alphaShadow
|
|
6055
6071
|
view.outlineSpotShadowColor = alphaShadow
|
|
@@ -6147,7 +6163,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6147
6163
|
}
|
|
6148
6164
|
|
|
6149
6165
|
private fun configureText(view: TextView, value: String, style: JSONObject) {
|
|
6150
|
-
view.text = when (style.
|
|
6166
|
+
view.text = when (craftNativeOptionalString(style.opt("textTransform"))) {
|
|
6151
6167
|
"uppercase" -> value.uppercase()
|
|
6152
6168
|
"lowercase" -> value.lowercase()
|
|
6153
6169
|
"capitalize" -> value.split(' ').joinToString(" ") { word -> word.replaceFirstChar(Char::uppercase) }
|
|
@@ -6191,11 +6207,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6191
6207
|
)
|
|
6192
6208
|
view.vector = vector
|
|
6193
6209
|
view.text = if (vector != null) "" else androidIconGlyph(symbol)
|
|
6194
|
-
val weight = when (style.
|
|
6195
|
-
"
|
|
6196
|
-
|
|
6210
|
+
val weight = when (craftNativeFontWeight(style.opt("fontWeight"))) {
|
|
6211
|
+
"100" -> 100
|
|
6212
|
+
"200" -> 200
|
|
6213
|
+
"300" -> 300
|
|
6214
|
+
"500" -> 500
|
|
6215
|
+
"600" -> 600
|
|
6216
|
+
"bold", "700" -> 700
|
|
6217
|
+
"800" -> 800
|
|
6218
|
+
"900" -> 900
|
|
6219
|
+
else -> 400
|
|
6220
|
+
}
|
|
6221
|
+
view.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
6222
|
+
Typeface.create("sans-serif", weight, false)
|
|
6223
|
+
} else {
|
|
6224
|
+
Typeface.create("sans-serif", if (weight >= 600) Typeface.BOLD else Typeface.NORMAL)
|
|
6197
6225
|
}
|
|
6198
|
-
view.typeface = Typeface.create("sans-serif", weight)
|
|
6199
6226
|
val hasExplicitWidth = style.has("width") && !style.isNull("width")
|
|
6200
6227
|
val hasExplicitHeight = style.has("height") && !style.isNull("height")
|
|
6201
6228
|
if (!hasExplicitWidth && !hasExplicitHeight) {
|
|
@@ -6246,20 +6273,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6246
6273
|
}
|
|
6247
6274
|
|
|
6248
6275
|
private fun configureTextStyle(view: TextView, style: JSONObject) {
|
|
6249
|
-
view.setTextColor(color(style.
|
|
6276
|
+
view.setTextColor(color(craftNativeOptionalString(style.opt("color")).orEmpty(), defaultTextColor))
|
|
6250
6277
|
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, craftNativeFiniteFloat(style, "fontSize", 16f))
|
|
6251
|
-
val
|
|
6278
|
+
val fontWeight = style.opt("fontWeight") as? String
|
|
6279
|
+
val weight = when (fontWeight) {
|
|
6252
6280
|
"bold" -> 700
|
|
6253
6281
|
"100", "200", "300", "400", "500", "600", "700", "800", "900" -> style.optInt("fontWeight")
|
|
6254
6282
|
else -> 400
|
|
6255
6283
|
}
|
|
6256
|
-
val italic = style.
|
|
6257
|
-
val family = style.
|
|
6284
|
+
val italic = craftNativeOptionalString(style.opt("fontStyle")) == "italic"
|
|
6285
|
+
val family = craftNativeOptionalString(style.opt("fontFamily"))?.takeIf { it.isNotBlank() }
|
|
6258
6286
|
view.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
6259
6287
|
Typeface.create(Typeface.create(family, Typeface.NORMAL), weight, italic)
|
|
6260
6288
|
} else Typeface.create(family, (if (weight >= 600) Typeface.BOLD else Typeface.NORMAL) or
|
|
6261
6289
|
(if (italic) Typeface.ITALIC else Typeface.NORMAL))
|
|
6262
|
-
|
|
6290
|
+
val textAlign = craftNativeOptionalString(style.opt("textAlign"))
|
|
6291
|
+
view.textAlignment = when (textAlign) {
|
|
6263
6292
|
"left" -> View.TEXT_ALIGNMENT_TEXT_START
|
|
6264
6293
|
"right" -> View.TEXT_ALIGNMENT_TEXT_END
|
|
6265
6294
|
"center" -> View.TEXT_ALIGNMENT_CENTER
|
|
@@ -6267,21 +6296,21 @@ class MainActivity : AppCompatActivity() {
|
|
|
6267
6296
|
else -> View.TEXT_ALIGNMENT_INHERIT
|
|
6268
6297
|
}
|
|
6269
6298
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
6270
|
-
view.justificationMode = if (
|
|
6299
|
+
view.justificationMode = if (textAlign == "justify") {
|
|
6271
6300
|
Layout.JUSTIFICATION_MODE_INTER_WORD
|
|
6272
6301
|
} else {
|
|
6273
6302
|
Layout.JUSTIFICATION_MODE_NONE
|
|
6274
6303
|
}
|
|
6275
6304
|
}
|
|
6276
|
-
view.letterSpacing = style
|
|
6305
|
+
view.letterSpacing = craftNativeFiniteDouble(style, "letterSpacing", Double.NaN)
|
|
6277
6306
|
.takeIf { it.isFinite() }
|
|
6278
6307
|
?.let { if (view.textSize > 0) dp(it).toFloat() / view.textSize else 0f }
|
|
6279
6308
|
?: 0f
|
|
6280
6309
|
view.setLineSpacing(
|
|
6281
|
-
style
|
|
6310
|
+
craftNativeFiniteDouble(style, "lineHeight", Double.NaN).takeIf { it.isFinite() }?.let { dp(it) - view.textSize } ?: 0f,
|
|
6282
6311
|
1f
|
|
6283
6312
|
)
|
|
6284
|
-
val shadowColor = style.
|
|
6313
|
+
val shadowColor = craftNativeOptionalString(style.opt("textShadowColor"))?.takeIf { it.isNotBlank() }
|
|
6285
6314
|
if (shadowColor == null) {
|
|
6286
6315
|
view.paint.clearShadowLayer()
|
|
6287
6316
|
} else {
|
|
@@ -6298,16 +6327,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
6298
6327
|
}
|
|
6299
6328
|
|
|
6300
6329
|
private fun applyTextDecoration(view: TextView, style: JSONObject) {
|
|
6301
|
-
val line = style.
|
|
6330
|
+
val line = craftNativeOptionalString(style.opt("textDecorationLine")).orEmpty()
|
|
6302
6331
|
if (view is EditText) {
|
|
6303
6332
|
// A field's own text, spanned in place: replacing it would move
|
|
6304
6333
|
// the caret to the start and tell the screen the text changed.
|
|
6305
6334
|
val editable = view.text ?: return
|
|
6306
6335
|
editable.getSpans(0, editable.length, CraftTextDecorationSpan::class.java).forEach(editable::removeSpan)
|
|
6307
6336
|
if (line.isBlank() || line == "none" || editable.isEmpty()) return
|
|
6308
|
-
val fieldColor = style.
|
|
6337
|
+
val fieldColor = craftNativeOptionalString(style.opt("textDecorationColor"))?.takeIf { it.isNotBlank() }?.let { color(it, Color.TRANSPARENT) }
|
|
6309
6338
|
editable.setSpan(
|
|
6310
|
-
CraftTextDecorationSpan(line, style.
|
|
6339
|
+
CraftTextDecorationSpan(line, craftNativeOptionalString(style.opt("textDecorationStyle")).orEmpty().ifBlank { "solid" }, fieldColor),
|
|
6311
6340
|
0,
|
|
6312
6341
|
editable.length,
|
|
6313
6342
|
Spannable.SPAN_INCLUSIVE_INCLUSIVE,
|
|
@@ -6319,14 +6348,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
6319
6348
|
if (view.text is Spannable) view.text = text
|
|
6320
6349
|
return
|
|
6321
6350
|
}
|
|
6322
|
-
val decorationColor = style.
|
|
6351
|
+
val decorationColor = craftNativeOptionalString(style.opt("textDecorationColor")).orEmpty()
|
|
6323
6352
|
.takeIf { it.isNotBlank() }
|
|
6324
6353
|
?.let { color(it, Color.TRANSPARENT) }
|
|
6325
6354
|
val decorated = SpannableString(text)
|
|
6326
6355
|
decorated.setSpan(
|
|
6327
6356
|
CraftTextDecorationSpan(
|
|
6328
6357
|
line,
|
|
6329
|
-
style.
|
|
6358
|
+
craftNativeOptionalString(style.opt("textDecorationStyle")).orEmpty().ifBlank { "solid" },
|
|
6330
6359
|
decorationColor,
|
|
6331
6360
|
),
|
|
6332
6361
|
0,
|
|
@@ -6510,7 +6539,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6510
6539
|
if (type == "Icon") "image" else type.lowercase()
|
|
6511
6540
|
}
|
|
6512
6541
|
val state = props.optJSONObject("accessibilityState")
|
|
6513
|
-
val disabled = props.
|
|
6542
|
+
val disabled = craftNativeBoolean(props.opt("disabled")) || craftNativeBoolean(state?.opt("disabled"))
|
|
6514
6543
|
if (type !in setOf("Button", "Link", "Checkbox", "TextInput", "Picker", "DatePicker", "Modal", "Switch", "Slider", "ActivityIndicator")) {
|
|
6515
6544
|
view.isEnabled = !disabled
|
|
6516
6545
|
} else if (disabled) {
|
|
@@ -6534,8 +6563,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
6534
6563
|
info.roleDescription = role.takeIf { it !in setOf("view", "text", "textinput", "scrollview") }
|
|
6535
6564
|
info.hintText = host.tooltipText
|
|
6536
6565
|
info.isEnabled = host.isEnabled && !disabled
|
|
6537
|
-
|
|
6538
|
-
|
|
6566
|
+
// Keep the native widget state when no accessibilityState was
|
|
6567
|
+
// authored. Overwriting it with the strict false fallback
|
|
6568
|
+
// made a checked Switch/CheckBox announce as unchecked unless
|
|
6569
|
+
// the screen redundantly mirrored its value into metadata.
|
|
6570
|
+
if (state?.has("selected") == true) {
|
|
6571
|
+
info.isSelected = craftNativeBoolean(state.opt("selected"))
|
|
6572
|
+
}
|
|
6573
|
+
if (state?.has("checked") == true) {
|
|
6574
|
+
info.isChecked = craftNativeBoolean(state.opt("checked"))
|
|
6575
|
+
}
|
|
6539
6576
|
}
|
|
6540
6577
|
})
|
|
6541
6578
|
ViewCompat.setImportantForAccessibility(
|
|
@@ -6551,11 +6588,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
6551
6588
|
private fun color(raw: String, fallback: Int) = runCatching { craftNativeColor(raw) }.getOrDefault(fallback)
|
|
6552
6589
|
|
|
6553
6590
|
private fun inputTypeFor(props: JSONObject): Int {
|
|
6554
|
-
val base = if (props.
|
|
6591
|
+
val base = if (craftNativeBoolean(props.opt("multiline"))) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
6555
6592
|
else InputType.TYPE_CLASS_TEXT
|
|
6556
6593
|
// Text variations do not carry their input class or multiline flag.
|
|
6557
6594
|
// Preserve both when selecting an email, URL, or search keyboard.
|
|
6558
|
-
val keyboard = when (props.
|
|
6595
|
+
val keyboard = when (props.opt("keyboardType") as? String) {
|
|
6559
6596
|
"email-address" -> base or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
6560
6597
|
"url" -> base or InputType.TYPE_TEXT_VARIATION_URI
|
|
6561
6598
|
"numeric", "number-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
@@ -6565,21 +6602,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6565
6602
|
"visible-password" -> base or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
6566
6603
|
else -> base
|
|
6567
6604
|
}
|
|
6568
|
-
val capitalization = when (props.
|
|
6605
|
+
val capitalization = when (props.opt("autoCapitalize") as? String) {
|
|
6569
6606
|
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
6570
6607
|
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
6571
6608
|
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
6572
6609
|
else -> 0
|
|
6573
6610
|
}
|
|
6574
|
-
val correction = if (props.
|
|
6575
|
-
val secure = if (props.
|
|
6611
|
+
val correction = if (craftNativeBoolean(props.opt("autoCorrect"), true)) InputType.TYPE_TEXT_FLAG_AUTO_CORRECT else 0
|
|
6612
|
+
val secure = if (craftNativeBoolean(props.opt("secureTextEntry"))) InputType.TYPE_TEXT_VARIATION_PASSWORD else 0
|
|
6576
6613
|
return keyboard or capitalization or correction or secure
|
|
6577
6614
|
}
|
|
6578
6615
|
|
|
6579
6616
|
/// The autofill hint for `textContentType` (iOS's names) or `autoComplete`
|
|
6580
6617
|
/// (HTML's), so a password manager fills the field.
|
|
6581
6618
|
private fun autofillHintFor(props: JSONObject): String? {
|
|
6582
|
-
val raw = props.
|
|
6619
|
+
val raw = (props.opt("textContentType") as? String)?.takeIf { it.isNotBlank() }
|
|
6620
|
+
?: (props.opt("autoComplete") as? String).orEmpty()
|
|
6583
6621
|
return when (raw.replace("-", "").replace("_", "").lowercase()) {
|
|
6584
6622
|
"username" -> View.AUTOFILL_HINT_USERNAME
|
|
6585
6623
|
"nickname" -> "personNickname"
|
|
@@ -6610,7 +6648,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6610
6648
|
}
|
|
6611
6649
|
}
|
|
6612
6650
|
|
|
6613
|
-
private fun imeOptionsFor(props: JSONObject): Int = when (props.
|
|
6651
|
+
private fun imeOptionsFor(props: JSONObject): Int = when (props.opt("returnKeyType") as? String) {
|
|
6614
6652
|
"done" -> EditorInfo.IME_ACTION_DONE
|
|
6615
6653
|
"go" -> EditorInfo.IME_ACTION_GO
|
|
6616
6654
|
"next" -> EditorInfo.IME_ACTION_NEXT
|