@craft-native/android 0.0.206 → 0.0.207
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 +1 -1
- package/templates/MainActivityNative.kt.template +149 -114
package/package.json
CHANGED
|
@@ -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,16 +240,16 @@ 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"),
|
|
@@ -377,7 +396,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
377
396
|
fun dashes(rawValue: Any?): List<Float>? {
|
|
378
397
|
val values = rawValue as? JSONArray ?: return null
|
|
379
398
|
return (0 until values.length()).mapNotNull {
|
|
380
|
-
values.
|
|
399
|
+
craftNativeNumber(values.opt(it))?.toFloat()?.takeIf { value -> value.isFinite() && value >= 0 }
|
|
381
400
|
}
|
|
382
401
|
}
|
|
383
402
|
// Keep the cascade explicit: computed style wins over a raw prop
|
|
@@ -455,10 +474,10 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
455
474
|
private fun viewBox(): Quadruple<Float, Float, Float, Float>? {
|
|
456
475
|
val values = drawing.optJSONObject("props")?.optJSONArray("viewBox") ?: return null
|
|
457
476
|
if (values.length() != 4) return null
|
|
458
|
-
val x = values.
|
|
459
|
-
val y = values.
|
|
460
|
-
val width = values.
|
|
461
|
-
val height = values.
|
|
477
|
+
val x = craftNativeNumber(values.opt(0))?.toFloat() ?: Float.NaN
|
|
478
|
+
val y = craftNativeNumber(values.opt(1))?.toFloat() ?: Float.NaN
|
|
479
|
+
val width = craftNativeNumber(values.opt(2))?.toFloat() ?: Float.NaN
|
|
480
|
+
val height = craftNativeNumber(values.opt(3))?.toFloat() ?: Float.NaN
|
|
462
481
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() || !height.isFinite() || width <= 0f || height <= 0f) return null
|
|
463
482
|
return Quadruple(width, height, x, y)
|
|
464
483
|
}
|
|
@@ -480,12 +499,12 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
480
499
|
private fun drawPath(canvas: Canvas, node: JSONObject, style: SvgPaint) {
|
|
481
500
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
482
501
|
val geometry = props.optJSONArray("geometry") ?: return
|
|
483
|
-
if ((0 until geometry.length()).any {
|
|
502
|
+
if ((0 until geometry.length()).any { craftNativeNumber(geometry.opt(it))?.toFloat()?.isFinite() != true }) return
|
|
484
503
|
val path = Path()
|
|
485
504
|
var index = 0
|
|
486
505
|
while (index < geometry.length()) {
|
|
487
|
-
val command = geometry.
|
|
488
|
-
fun number(offset: Int): Float = geometry.
|
|
506
|
+
val command = craftNativeNumber(geometry.opt(index))?.toInt() ?: break
|
|
507
|
+
fun number(offset: Int): Float = craftNativeNumber(geometry.opt(index + offset))?.toFloat() ?: Float.NaN
|
|
489
508
|
when (command) {
|
|
490
509
|
0 -> if (index + 2 < geometry.length()) { path.moveTo(number(1), number(2)); index += 3 } else break
|
|
491
510
|
1 -> if (index + 2 < geometry.length()) { path.lineTo(number(1), number(2)); index += 3 } else break
|
|
@@ -551,9 +570,9 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
551
570
|
for (index in 0 until children.length()) if (children.opt(index) is String) append(children.optString(index))
|
|
552
571
|
}
|
|
553
572
|
if (text.isEmpty()) return
|
|
554
|
-
val size = (if (props.has("fontSize")) props.
|
|
555
|
-
.takeIf { it.isFinite() }
|
|
573
|
+
val size = (if (props.has("fontSize")) craftNativeNumber(props.opt("fontSize")) else craftNativeNumber(nodeStyle.opt("fontSize")))
|
|
556
574
|
?.toFloat()
|
|
575
|
+
?.takeIf { it.isFinite() }
|
|
557
576
|
?: 12f
|
|
558
577
|
val paint = android.graphics.Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
559
578
|
color = resolveColor(style.fill, style.color, Color.BLACK)
|
|
@@ -561,8 +580,8 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
561
580
|
textSize = size
|
|
562
581
|
typeface = style.typeface
|
|
563
582
|
}
|
|
564
|
-
var x = props.
|
|
565
|
-
val y = props.
|
|
583
|
+
var x = craftNativeNumber(props.opt("x"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
584
|
+
val y = craftNativeNumber(props.opt("y"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
566
585
|
when (props.optString("textAnchor")) {
|
|
567
586
|
"middle" -> x -= paint.measureText(text) / 2f
|
|
568
587
|
"end" -> x -= paint.measureText(text)
|
|
@@ -629,16 +648,16 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
629
648
|
|
|
630
649
|
private fun applyPending() {
|
|
631
650
|
val map = googleMap ?: return
|
|
632
|
-
map.uiSettings.isScrollGesturesEnabled = pendingProps.
|
|
633
|
-
map.uiSettings.isZoomGesturesEnabled = pendingProps.
|
|
634
|
-
map.uiSettings.isRotateGesturesEnabled = pendingProps.
|
|
651
|
+
map.uiSettings.isScrollGesturesEnabled = craftNativeBoolean(pendingProps.opt("scrollEnabled"), true)
|
|
652
|
+
map.uiSettings.isZoomGesturesEnabled = craftNativeBoolean(pendingProps.opt("zoomEnabled"), true)
|
|
653
|
+
map.uiSettings.isRotateGesturesEnabled = craftNativeBoolean(pendingProps.opt("rotateEnabled"))
|
|
635
654
|
map.mapType = when (craftNativeOptionalString(pendingProps.opt("mapType")).orEmpty().lowercase()) {
|
|
636
655
|
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
|
|
637
656
|
"hybrid" -> GoogleMap.MAP_TYPE_HYBRID
|
|
638
657
|
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
|
|
639
658
|
else -> GoogleMap.MAP_TYPE_NORMAL
|
|
640
659
|
}
|
|
641
|
-
val showUserLocation = pendingProps.
|
|
660
|
+
val showUserLocation = craftNativeBoolean(pendingProps.opt("showsUserLocation")) && hasLocationPermission()
|
|
642
661
|
runCatching { map.isMyLocationEnabled = showUserLocation }
|
|
643
662
|
val nextMarkers = mutableMapOf<String, Pair<LatLng, String?>>()
|
|
644
663
|
val nextHandlers = mutableMapOf<String, String>()
|
|
@@ -654,7 +673,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
654
673
|
val points = coordinates(props.optJSONArray("geometry"))
|
|
655
674
|
if (type == "mapmarker" || type == "marker") {
|
|
656
675
|
points.firstOrNull()?.let { point ->
|
|
657
|
-
nextMarkers[id] = point to
|
|
676
|
+
nextMarkers[id] = point to (props.opt("title") as? String)
|
|
658
677
|
listOf(events.opt("onPress"), events.opt("onMarkerPress"))
|
|
659
678
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
660
679
|
?.let { nextHandlers[id] = it }
|
|
@@ -717,7 +736,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
717
736
|
moveCamera(initialRegion)
|
|
718
737
|
initialCameraKey = key
|
|
719
738
|
}
|
|
720
|
-
} else if (pendingProps.
|
|
739
|
+
} else if (craftNativeBoolean(pendingProps.opt("fitToBounds"))) {
|
|
721
740
|
val key = "fit:${overlayKey(nextMarkers, nextPolylines)}"
|
|
722
741
|
if (initialCameraKey != key) {
|
|
723
742
|
if (fitToBounds()) initialCameraKey = key
|
|
@@ -731,7 +750,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
731
750
|
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
732
751
|
// MapView can become ready before its host has a usable viewport. Do
|
|
733
752
|
// not mark a fit as complete until the camera update can be issued.
|
|
734
|
-
if (width > 0 && height > 0 && pendingProps.
|
|
753
|
+
if (width > 0 && height > 0 && craftNativeBoolean(pendingProps.opt("fitToBounds")) && initialCameraKey == null) {
|
|
735
754
|
applyPending()
|
|
736
755
|
}
|
|
737
756
|
}
|
|
@@ -1017,7 +1036,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1017
1036
|
alignContent = style.optString("alignContent", "flex-start")
|
|
1018
1037
|
justifyContent = style.optString("justifyContent", "flex-start")
|
|
1019
1038
|
fun spacing(name: String, fallback: Float): Float {
|
|
1020
|
-
val value = style.
|
|
1039
|
+
val value = craftNativeNumber(style.opt(name)) ?: Double.NaN
|
|
1021
1040
|
val pixels = value * density
|
|
1022
1041
|
val narrowed = pixels.toFloat()
|
|
1023
1042
|
return if (value.isFinite() && value >= 0 && pixels.isFinite() && narrowed.isFinite()) narrowed else fallback
|
|
@@ -2734,7 +2753,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2734
2753
|
}
|
|
2735
2754
|
|
|
2736
2755
|
fun configure(props: JSONObject) {
|
|
2737
|
-
val requestedMode = when (props.
|
|
2756
|
+
val requestedMode = when (props.opt("mode") as? String) {
|
|
2738
2757
|
"time" -> PickerMode.TIME
|
|
2739
2758
|
"datetime", "dateAndTime", "datetime-local" -> PickerMode.DATE_TIME
|
|
2740
2759
|
"countdown" -> PickerMode.COUNTDOWN
|
|
@@ -2774,7 +2793,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2774
2793
|
}
|
|
2775
2794
|
|
|
2776
2795
|
suppressChanges = true
|
|
2777
|
-
minuteInterval = props.
|
|
2796
|
+
minuteInterval = craftNativeInt(props.opt("minuteInterval"), 1).takeIf { it in 1..30 && 60 % it == 0 } ?: 1
|
|
2778
2797
|
val defaults = defaultDateBounds
|
|
2779
2798
|
datePicker?.minDate = pickerDate(props.opt("minimumDate"))?.timeInMillis ?: defaults?.first ?: datePicker?.minDate ?: 0L
|
|
2780
2799
|
datePicker?.maxDate = pickerDate(props.opt("maximumDate"))?.timeInMillis ?: defaults?.second ?: datePicker?.maxDate ?: Long.MAX_VALUE
|
|
@@ -2784,7 +2803,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2784
2803
|
} else {
|
|
2785
2804
|
pickerDate(props.opt("value"))?.let { applyDate(it) }
|
|
2786
2805
|
}
|
|
2787
|
-
val enabled = !props.
|
|
2806
|
+
val enabled = !craftNativeBoolean(props.opt("disabled"))
|
|
2788
2807
|
isEnabled = enabled
|
|
2789
2808
|
datePicker?.isEnabled = enabled
|
|
2790
2809
|
timePicker?.isEnabled = enabled
|
|
@@ -4925,7 +4944,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4925
4944
|
val control = when (type) {
|
|
4926
4945
|
"Text" -> ((previous as? TextView)?.takeUnless { it is Button || it is EditText } ?: TextView(this)).apply {
|
|
4927
4946
|
includeFontPadding = false
|
|
4928
|
-
maxLines = props.
|
|
4947
|
+
maxLines = craftNativeInt(props.opt("numberOfLines"), 0).takeIf { it > 0 } ?: Int.MAX_VALUE
|
|
4929
4948
|
ellipsize = when (props.optString("ellipsizeMode")) {
|
|
4930
4949
|
"head" -> TextUtils.TruncateAt.START
|
|
4931
4950
|
"middle" -> TextUtils.TruncateAt.MIDDLE
|
|
@@ -4936,7 +4955,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4936
4955
|
}
|
|
4937
4956
|
"StatusBar" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
4938
4957
|
screen.statusBarStyle = craftNativeOptionalString(props.opt("barStyle"))
|
|
4939
|
-
screen.statusBarHidden = if (props.has("hidden") && !props.isNull("hidden")) props.
|
|
4958
|
+
screen.statusBarHidden = if (props.has("hidden") && !props.isNull("hidden")) craftNativeBoolean(props.opt("hidden")) else null
|
|
4940
4959
|
visibility = View.GONE
|
|
4941
4960
|
}
|
|
4942
4961
|
"RefreshControl" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
@@ -4944,17 +4963,18 @@ class MainActivity : AppCompatActivity() {
|
|
|
4944
4963
|
}
|
|
4945
4964
|
"Link" -> ((previous as? TextView)?.takeUnless { it is Button } ?: TextView(this)).apply {
|
|
4946
4965
|
includeFontPadding = false
|
|
4947
|
-
configureText(this,
|
|
4966
|
+
configureText(this, (props.opt("title") as? String) ?: childText(children), style)
|
|
4948
4967
|
val linkColor = craftNativeOptionalString(props.opt("color")) ?: craftNativeOptionalString(style.opt("color"))
|
|
4949
4968
|
setTextColor(color(linkColor.orEmpty(), Color.rgb(33, 150, 243)))
|
|
4950
4969
|
paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
|
4951
|
-
isEnabled = !props.
|
|
4970
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4952
4971
|
isClickable = true
|
|
4953
4972
|
isFocusable = true
|
|
4954
4973
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
4955
|
-
.
|
|
4974
|
+
.mapNotNull { it as? String }
|
|
4975
|
+
.firstOrNull { it.trim().isNotEmpty() }
|
|
4956
4976
|
?.trim()
|
|
4957
|
-
?.takeIf { !props.
|
|
4977
|
+
?.takeIf { !craftNativeBoolean(props.opt("disabled")) }
|
|
4958
4978
|
if (target != null) screen.linkTargets[this] = target else screen.linkTargets.remove(this)
|
|
4959
4979
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4960
4980
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
@@ -4975,13 +4995,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
4975
4995
|
minimumHeight = 0
|
|
4976
4996
|
setPadding(0, 0, 0, 0)
|
|
4977
4997
|
}).apply {
|
|
4978
|
-
val title =
|
|
4998
|
+
val title = (props.opt("title") as? String) ?: childText(children)
|
|
4979
4999
|
isAllCaps = false
|
|
4980
5000
|
configureText(this, title, style)
|
|
4981
5001
|
val buttonColor = craftNativeOptionalString(props.opt("color"))
|
|
4982
5002
|
?: craftNativeOptionalString(style.opt("color"))
|
|
4983
5003
|
setTextColor(buttonColor?.let { color(it, defaultTextColor) } ?: defaultTextColor)
|
|
4984
|
-
isEnabled = !props.
|
|
5004
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4985
5005
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4986
5006
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
4987
5007
|
setOnClickListener {
|
|
@@ -4990,7 +5010,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4990
5010
|
}
|
|
4991
5011
|
}
|
|
4992
5012
|
"Checkbox" -> (previous as? CheckBox ?: CheckBox(this)).apply {
|
|
4993
|
-
text =
|
|
5013
|
+
text = (props.opt("label") as? String) ?: childText(children)
|
|
4994
5014
|
configureText(this, text.toString(), style)
|
|
4995
5015
|
val checkboxChecked = props.opt("checked")
|
|
4996
5016
|
val checkboxValue = props.opt("value")
|
|
@@ -5006,7 +5026,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5006
5026
|
isChecked = checked
|
|
5007
5027
|
val indeterminate = (props.opt("indeterminate") as? Boolean) == true
|
|
5008
5028
|
isActivated = indeterminate
|
|
5009
|
-
isEnabled = !props.
|
|
5029
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5010
5030
|
val checkboxColor = craftNativeOptionalString(props.opt("color"))
|
|
5011
5031
|
?: craftNativeOptionalString(style.opt("color"))
|
|
5012
5032
|
buttonTintList = checkboxColor
|
|
@@ -5035,17 +5055,17 @@ class MainActivity : AppCompatActivity() {
|
|
|
5035
5055
|
val defaultPadding = inputPaddingDefaults.getOrPut(this) {
|
|
5036
5056
|
intArrayOf(paddingLeft, paddingTop, paddingRight, paddingBottom)
|
|
5037
5057
|
}
|
|
5038
|
-
hint =
|
|
5058
|
+
hint = (props.opt("placeholder") as? String).orEmpty()
|
|
5039
5059
|
setHintTextColor(
|
|
5040
|
-
|
|
5060
|
+
(props.opt("placeholderTextColor") as? String)
|
|
5041
5061
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5042
5062
|
?.let { ColorStateList.valueOf(it) }
|
|
5043
5063
|
?: defaultHintColors,
|
|
5044
5064
|
)
|
|
5045
|
-
highlightColor =
|
|
5065
|
+
highlightColor = (props.opt("selectionColor") as? String)
|
|
5046
5066
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5047
5067
|
?: defaultHighlightColor
|
|
5048
|
-
val maxLength = props.
|
|
5068
|
+
val maxLength = craftNativeInt(props.opt("maxLength"), 0)
|
|
5049
5069
|
val currentLength = (filters.firstOrNull { it is InputFilter.LengthFilter } as? InputFilter.LengthFilter)?.max ?: 0
|
|
5050
5070
|
if (currentLength != maxLength || (maxLength == 0 && filters.isNotEmpty())) {
|
|
5051
5071
|
filters = if (maxLength > 0) arrayOf(InputFilter.LengthFilter(maxLength)) else emptyArray()
|
|
@@ -5054,11 +5074,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5054
5074
|
if (inputType != wantedType) inputType = wantedType
|
|
5055
5075
|
val wantedIme = imeOptionsFor(props)
|
|
5056
5076
|
if (imeOptions != wantedIme) imeOptions = wantedIme
|
|
5057
|
-
val multiline = props.
|
|
5077
|
+
val multiline = craftNativeBoolean(props.opt("multiline"))
|
|
5058
5078
|
if (isSingleLine == multiline) isSingleLine = !multiline
|
|
5059
|
-
val wantedLines = if (multiline) props.
|
|
5079
|
+
val wantedLines = if (multiline) craftNativeInt(props.opt("numberOfLines"), Int.MAX_VALUE).coerceAtLeast(1) else 1
|
|
5060
5080
|
if (maxLines != wantedLines) maxLines = wantedLines
|
|
5061
|
-
isEnabled = props.
|
|
5081
|
+
isEnabled = craftNativeBoolean(props.opt("editable"), true)
|
|
5062
5082
|
configureTextStyle(this, style)
|
|
5063
5083
|
// The style's padding is where the text starts, as on iOS and
|
|
5064
5084
|
// the web; without one the field keeps its own.
|
|
@@ -5086,14 +5106,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5086
5106
|
// them after keyed reconciliation.
|
|
5087
5107
|
val autofillHint = autofillHintFor(props)
|
|
5088
5108
|
if (autofillHint == null) setAutofillHints(*emptyArray()) else setAutofillHints(autofillHint)
|
|
5089
|
-
val controlledValue = props.opt("value")
|
|
5109
|
+
val controlledValue = props.opt("value") as? String
|
|
5090
5110
|
val echoes = screen.inputEchoes.getOrPut(this) { CraftNativeInputEcho() }
|
|
5091
5111
|
if (echoes.accept(controlledValue, text.toString()) && controlledValue != null) {
|
|
5092
5112
|
screen.suppressedTextChanges.add(this)
|
|
5093
5113
|
setTextPreservingSelection(this, controlledValue)
|
|
5094
5114
|
}
|
|
5095
5115
|
else if (this !== previous && screen.drafts.containsKey(identity)) setText(screen.drafts[identity])
|
|
5096
|
-
else if (this !== previous && props.has("defaultValue") && !props.isNull("defaultValue"))
|
|
5116
|
+
else if (this !== previous && props.has("defaultValue") && !props.isNull("defaultValue")) {
|
|
5117
|
+
(props.opt("defaultValue") as? String)?.let(::setText)
|
|
5118
|
+
}
|
|
5097
5119
|
applyTextDecoration(this, style)
|
|
5098
5120
|
screen.inputIdentities[this] = identity
|
|
5099
5121
|
val handler = craftNativeEventName(events, "onChange", "onChangeText")
|
|
@@ -5128,7 +5150,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5128
5150
|
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("text", text.toString())))
|
|
5129
5151
|
true
|
|
5130
5152
|
}
|
|
5131
|
-
if (props.
|
|
5153
|
+
if (craftNativeBoolean(props.opt("autoFocus")) && !isFocused) post {
|
|
5132
5154
|
if (!isFocused) {
|
|
5133
5155
|
requestFocus()
|
|
5134
5156
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
@@ -5158,7 +5180,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5158
5180
|
val itemOptions = props.optJSONArray("items")?.let { items ->
|
|
5159
5181
|
(0 until items.length()).mapNotNull { index ->
|
|
5160
5182
|
val item = items.optJSONObject(index) ?: return@mapNotNull null
|
|
5161
|
-
val label =
|
|
5183
|
+
val label = (item.opt("label") as? String)?.takeIf { it.isNotEmpty() } ?: return@mapNotNull null
|
|
5162
5184
|
craftNativePickerValue(item.opt("value"))?.let { it to label } ?: (label to label)
|
|
5163
5185
|
}
|
|
5164
5186
|
}.orEmpty()
|
|
@@ -5166,7 +5188,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5166
5188
|
val option = children.optJSONObject(index) ?: return@mapNotNull null
|
|
5167
5189
|
if (option.optString("type") != "Text") return@mapNotNull null
|
|
5168
5190
|
val label = (option.optJSONArray("children") ?: JSONArray()).let { text ->
|
|
5169
|
-
(0 until text.length()).
|
|
5191
|
+
(0 until text.length()).mapNotNull { text.opt(it) as? String }.joinToString("")
|
|
5170
5192
|
}
|
|
5171
5193
|
if (label.isBlank()) return@mapNotNull null
|
|
5172
5194
|
val optionProps = option.optJSONObject("props")
|
|
@@ -5185,7 +5207,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5185
5207
|
val requested = craftNativePickerValue(props.opt("selectedValue"))
|
|
5186
5208
|
?: craftNativePickerValue(props.opt("value")).orEmpty()
|
|
5187
5209
|
val selected = options.indexOfFirst { it.first == requested }.takeIf { it >= 0 } ?: 0
|
|
5188
|
-
isEnabled = !props.
|
|
5210
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5189
5211
|
// Mounting a controlled picker must not look like a user
|
|
5190
5212
|
// change. Android may dispatch the initial adapter selection
|
|
5191
5213
|
// during the first layout pass; iOS only reports a choice
|
|
@@ -5215,7 +5237,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5215
5237
|
}
|
|
5216
5238
|
}
|
|
5217
5239
|
"Modal" -> (previous as? CraftNativeModalView ?: CraftNativeModalView(this)).apply {
|
|
5218
|
-
transparent = props.
|
|
5240
|
+
transparent = craftNativeBoolean(props.opt("transparent"))
|
|
5219
5241
|
content.configure(style, resources.displayMetrics.density)
|
|
5220
5242
|
reconcileChildren(screen, content, children, identity, next, style, previousControls)
|
|
5221
5243
|
// Dynamic :visible bindings arrive as a display style mutation;
|
|
@@ -5224,7 +5246,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5224
5246
|
// An explicit visible prop owns modal visibility. The
|
|
5225
5247
|
// binding runtime may leave a stale display:none style
|
|
5226
5248
|
// from the previous hidden state when it patches props.
|
|
5227
|
-
props.
|
|
5249
|
+
craftNativeBoolean(props.opt("visible"))
|
|
5228
5250
|
} else {
|
|
5229
5251
|
style.has("display") && style.optString("display") != "none"
|
|
5230
5252
|
}
|
|
@@ -5260,7 +5282,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5260
5282
|
}
|
|
5261
5283
|
"TouchableOpacity", "TouchableHighlight", "Pressable" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
5262
5284
|
configureContainer(this, style)
|
|
5263
|
-
val disabled = props.
|
|
5285
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5264
5286
|
screen.pressActiveOpacities[this] = craftNativeFiniteFloat(props, "activeOpacity", 0.5f)
|
|
5265
5287
|
screen.pressBaseOpacities[this] = craftNativeFiniteFloat(style, "opacity", 1f)
|
|
5266
5288
|
if (type == "TouchableHighlight" && !disabled) {
|
|
@@ -5281,7 +5303,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5281
5303
|
requestedChecked is Boolean -> requestedChecked
|
|
5282
5304
|
else -> false
|
|
5283
5305
|
}
|
|
5284
|
-
isEnabled = !props.
|
|
5306
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5285
5307
|
props.optJSONObject("trackColor")?.let { track ->
|
|
5286
5308
|
val falseRaw = craftNativeOptionalString(track.opt("false"))
|
|
5287
5309
|
val trueRaw = craftNativeOptionalString(track.opt("true"))
|
|
@@ -5309,11 +5331,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5309
5331
|
}
|
|
5310
5332
|
}
|
|
5311
5333
|
"Slider" -> (previous as? SeekBar ?: SeekBar(this)).apply {
|
|
5312
|
-
val minimum =
|
|
5313
|
-
val maximumCandidate =
|
|
5334
|
+
val minimum = craftNativeFiniteNumber(props, "minimumValue", 0.0)
|
|
5335
|
+
val maximumCandidate = craftNativeFiniteNumber(props, "maximumValue", 1.0)
|
|
5314
5336
|
val maximum = if (maximumCandidate > minimum) maximumCandidate else minimum + Double.MIN_VALUE
|
|
5315
5337
|
setOnSeekBarChangeListener(null)
|
|
5316
|
-
val step =
|
|
5338
|
+
val step = craftNativeFiniteNumber(props, "step", 0.0).takeIf { it > 0 }
|
|
5317
5339
|
// A stepped slider needs one native position per authored
|
|
5318
5340
|
// step; a fixed 1,000-position SeekBar silently made large
|
|
5319
5341
|
// ranges unable to represent most valid values. Continuous
|
|
@@ -5327,7 +5349,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5327
5349
|
.toInt()
|
|
5328
5350
|
} ?: 1000
|
|
5329
5351
|
max = progressMax
|
|
5330
|
-
val requested =
|
|
5352
|
+
val requested = craftNativeFiniteNumber(props, "value", minimum)
|
|
5331
5353
|
val value = (if (step != null) minimum + ((requested - minimum) / step).roundToInt() * step else requested)
|
|
5332
5354
|
.coerceIn(minimum, maximum)
|
|
5333
5355
|
progress = if (step != null) {
|
|
@@ -5345,7 +5367,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5345
5367
|
thumbTintList = craftNativeOptionalString(props.opt("thumbTintColor"))
|
|
5346
5368
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5347
5369
|
?.let { ColorStateList.valueOf(it) }
|
|
5348
|
-
isEnabled = !props.
|
|
5370
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5349
5371
|
val handler = craftNativeEventName(events, "onValueChange", "onChange")
|
|
5350
5372
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
5351
5373
|
val complete = craftNativeEventName(events, "onSlidingComplete")
|
|
@@ -5379,9 +5401,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5379
5401
|
else -> 1f
|
|
5380
5402
|
}
|
|
5381
5403
|
indicatorScale = scale
|
|
5382
|
-
val animating = props.
|
|
5404
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5383
5405
|
isIndeterminate = animating
|
|
5384
|
-
visibility = if (!animating && props.
|
|
5406
|
+
visibility = if (!animating && craftNativeBoolean(props.opt("hidesWhenStopped"), true)) View.GONE else View.VISIBLE
|
|
5385
5407
|
indeterminateTintList = craftNativeOptionalString(props.opt("color"))
|
|
5386
5408
|
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5387
5409
|
?.let { ColorStateList.valueOf(it) }
|
|
@@ -5437,13 +5459,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
5437
5459
|
onPictureInPictureChanged = { updatePictureInPicture() }
|
|
5438
5460
|
configure(
|
|
5439
5461
|
source = props.opt("source") ?: props.opt("src"),
|
|
5440
|
-
autoplay = props.
|
|
5441
|
-
paused = props.
|
|
5442
|
-
loop = props.
|
|
5443
|
-
muted = props.
|
|
5444
|
-
controls = props.
|
|
5462
|
+
autoplay = craftNativeBoolean(props.opt("autoplay"), craftNativeBoolean(props.opt("autoPlay"))),
|
|
5463
|
+
paused = craftNativeBoolean(props.opt("paused")),
|
|
5464
|
+
loop = craftNativeBoolean(props.opt("loop")),
|
|
5465
|
+
muted = craftNativeBoolean(props.opt("muted")),
|
|
5466
|
+
controls = craftNativeBoolean(props.opt("controls")),
|
|
5445
5467
|
resizeMode = craftNativeResizeMode(style, props),
|
|
5446
|
-
pictureInPicture = props.
|
|
5468
|
+
pictureInPicture = craftNativeBoolean(props.opt("pictureInPicture")),
|
|
5447
5469
|
)
|
|
5448
5470
|
}
|
|
5449
5471
|
"Map" -> (previous as? CraftNativeMapView ?: CraftNativeMapView(this)).apply {
|
|
@@ -5458,21 +5480,21 @@ class MainActivity : AppCompatActivity() {
|
|
|
5458
5480
|
drawing = node
|
|
5459
5481
|
}
|
|
5460
5482
|
"ScrollView", "SectionList" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
|
|
5461
|
-
val horizontal = props.
|
|
5483
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal")) || style.optString("flexDirection") in setOf("row", "row-reverse")
|
|
5462
5484
|
setHorizontal(horizontal)
|
|
5463
5485
|
setNativeNestedScrollingEnabled(true)
|
|
5464
5486
|
setScrollIndicators(
|
|
5465
|
-
props.
|
|
5466
|
-
props.
|
|
5487
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5488
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5467
5489
|
)
|
|
5468
|
-
setScrollEnabled(props.
|
|
5490
|
+
setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5469
5491
|
setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
5470
|
-
setBounces(props.
|
|
5492
|
+
setBounces(craftNativeBoolean(props.opt("bounces"), true))
|
|
5471
5493
|
setAlwaysBounce(
|
|
5472
|
-
props.
|
|
5473
|
-
props.
|
|
5494
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5495
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5474
5496
|
)
|
|
5475
|
-
setPagingEnabled(props.
|
|
5497
|
+
setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5476
5498
|
val refreshNode = (0 until children.length()).asSequence()
|
|
5477
5499
|
.mapNotNull { children.optJSONObject(it) }
|
|
5478
5500
|
.firstOrNull { canonicalType(it.optString("type")) == "RefreshControl" }
|
|
@@ -5485,7 +5507,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5485
5507
|
val refreshHandler = refreshHandlerName?.let { handler ->
|
|
5486
5508
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5487
5509
|
}
|
|
5488
|
-
setRefreshHandler(refreshHandler, refreshProps.
|
|
5510
|
+
setRefreshHandler(refreshHandler, craftNativeBoolean(refreshProps.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5489
5511
|
setRefreshAppearance(refreshProps, resources.displayMetrics.density, refreshTintFallback(screen))
|
|
5490
5512
|
val onScroll = craftNativeEventName(events, "onScroll")
|
|
5491
5513
|
if (onScroll.isNotBlank()) screen.scrollHandlers[this] = onScroll else screen.scrollHandlers.remove(this)
|
|
@@ -5563,7 +5585,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5563
5585
|
?: craftNativeEventName(events, "onRefresh").takeIf { it.isNotBlank() }
|
|
5564
5586
|
configureRefresh(refreshHandlerName?.let { handler ->
|
|
5565
5587
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5566
|
-
}, propRefresh.
|
|
5588
|
+
}, craftNativeBoolean(propRefresh.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5567
5589
|
setRefreshAppearance(propRefresh, refreshTintFallback(screen))
|
|
5568
5590
|
nativeList.setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
5569
5591
|
nativeList.setContentContainerStyle(props.optJSONObject("contentContainerStyle"), resources.displayMetrics.density)
|
|
@@ -5603,7 +5625,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5603
5625
|
if (underlayHideHandler != null) screen.pressUnderlayHideHandlers[control] = underlayHideHandler else screen.pressUnderlayHideHandlers.remove(control)
|
|
5604
5626
|
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
5627
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
5606
|
-
val disabled = props.
|
|
5628
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5607
5629
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
5608
5630
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
5609
5631
|
?.trim()
|
|
@@ -5674,8 +5696,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5674
5696
|
}
|
|
5675
5697
|
control.isEnabled = !disabled
|
|
5676
5698
|
}
|
|
5677
|
-
val longPressDisabled = props.
|
|
5678
|
-
props.optJSONObject("accessibilityState")?.
|
|
5699
|
+
val longPressDisabled = craftNativeBoolean(props.opt("disabled")) ||
|
|
5700
|
+
craftNativeBoolean(props.optJSONObject("accessibilityState")?.opt("disabled"))
|
|
5679
5701
|
val longPressHandler = craftNativeEventName(events, "onLongPress").takeUnless { longPressDisabled } ?: ""
|
|
5680
5702
|
if (longPressHandler.isNotBlank()) {
|
|
5681
5703
|
screen.longPressHandlers[control] = longPressHandler
|
|
@@ -5720,8 +5742,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5720
5742
|
next[identity] = control
|
|
5721
5743
|
applyViewStyle(control, style)
|
|
5722
5744
|
if (type == "ActivityIndicator") {
|
|
5723
|
-
val animating = props.
|
|
5724
|
-
val hidesWhenStopped = props.
|
|
5745
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5746
|
+
val hidesWhenStopped = craftNativeBoolean(props.opt("hidesWhenStopped"), true)
|
|
5725
5747
|
control.visibility = if (style.optString("display") == "none" || (!animating && hidesWhenStopped)) {
|
|
5726
5748
|
View.GONE
|
|
5727
5749
|
} else {
|
|
@@ -5892,12 +5914,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
5892
5914
|
}
|
|
5893
5915
|
val threshold = craftNativeNonNegativeDouble(props, "threshold", 0.1)
|
|
5894
5916
|
val authoredThreshold = craftNativeNonNegativeDouble(props, "onEndReachedThreshold", threshold)
|
|
5895
|
-
val horizontal = props.
|
|
5917
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal"))
|
|
5896
5918
|
list.apply(
|
|
5897
5919
|
nodes = nodes,
|
|
5898
5920
|
horizontal = horizontal,
|
|
5899
|
-
columns = props.
|
|
5900
|
-
inverted = props.
|
|
5921
|
+
columns = craftNativeInt(props.opt("numColumns"), 1),
|
|
5922
|
+
inverted = craftNativeBoolean(props.opt("inverted")),
|
|
5901
5923
|
endReachedThreshold = authoredThreshold,
|
|
5902
5924
|
renderItem = { node, itemIdentity, _ ->
|
|
5903
5925
|
val rows = screen.flatListRows.getOrPut(list) { mutableMapOf() }
|
|
@@ -5917,16 +5939,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5917
5939
|
},
|
|
5918
5940
|
)
|
|
5919
5941
|
list.setScrollIndicators(
|
|
5920
|
-
props.
|
|
5921
|
-
props.
|
|
5942
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5943
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5922
5944
|
)
|
|
5923
|
-
list.setScrollEnabled(props.
|
|
5924
|
-
list.setPagingEnabled(props.
|
|
5945
|
+
list.setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5946
|
+
list.setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5925
5947
|
list.setScrollTarget(props.opt("scrollTarget"))
|
|
5926
5948
|
list.setBounceOptions(
|
|
5927
|
-
props.
|
|
5928
|
-
props.
|
|
5929
|
-
props.
|
|
5949
|
+
craftNativeBoolean(props.opt("bounces"), true),
|
|
5950
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5951
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5930
5952
|
)
|
|
5931
5953
|
}
|
|
5932
5954
|
|
|
@@ -6191,11 +6213,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6191
6213
|
)
|
|
6192
6214
|
view.vector = vector
|
|
6193
6215
|
view.text = if (vector != null) "" else androidIconGlyph(symbol)
|
|
6194
|
-
val weight = when (style.
|
|
6195
|
-
"
|
|
6196
|
-
|
|
6216
|
+
val weight = when (craftNativeFontWeight(style.opt("fontWeight"))) {
|
|
6217
|
+
"100" -> 100
|
|
6218
|
+
"200" -> 200
|
|
6219
|
+
"300" -> 300
|
|
6220
|
+
"500" -> 500
|
|
6221
|
+
"600" -> 600
|
|
6222
|
+
"bold", "700" -> 700
|
|
6223
|
+
"800" -> 800
|
|
6224
|
+
"900" -> 900
|
|
6225
|
+
else -> 400
|
|
6226
|
+
}
|
|
6227
|
+
view.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
6228
|
+
Typeface.create("sans-serif", weight, false)
|
|
6229
|
+
} else {
|
|
6230
|
+
Typeface.create("sans-serif", if (weight >= 600) Typeface.BOLD else Typeface.NORMAL)
|
|
6197
6231
|
}
|
|
6198
|
-
view.typeface = Typeface.create("sans-serif", weight)
|
|
6199
6232
|
val hasExplicitWidth = style.has("width") && !style.isNull("width")
|
|
6200
6233
|
val hasExplicitHeight = style.has("height") && !style.isNull("height")
|
|
6201
6234
|
if (!hasExplicitWidth && !hasExplicitHeight) {
|
|
@@ -6248,7 +6281,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6248
6281
|
private fun configureTextStyle(view: TextView, style: JSONObject) {
|
|
6249
6282
|
view.setTextColor(color(style.optString("color"), defaultTextColor))
|
|
6250
6283
|
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, craftNativeFiniteFloat(style, "fontSize", 16f))
|
|
6251
|
-
val
|
|
6284
|
+
val fontWeight = style.opt("fontWeight") as? String
|
|
6285
|
+
val weight = when (fontWeight) {
|
|
6252
6286
|
"bold" -> 700
|
|
6253
6287
|
"100", "200", "300", "400", "500", "600", "700", "800", "900" -> style.optInt("fontWeight")
|
|
6254
6288
|
else -> 400
|
|
@@ -6273,12 +6307,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
6273
6307
|
Layout.JUSTIFICATION_MODE_NONE
|
|
6274
6308
|
}
|
|
6275
6309
|
}
|
|
6276
|
-
view.letterSpacing = style
|
|
6310
|
+
view.letterSpacing = craftNativeFiniteDouble(style, "letterSpacing", Double.NaN)
|
|
6277
6311
|
.takeIf { it.isFinite() }
|
|
6278
6312
|
?.let { if (view.textSize > 0) dp(it).toFloat() / view.textSize else 0f }
|
|
6279
6313
|
?: 0f
|
|
6280
6314
|
view.setLineSpacing(
|
|
6281
|
-
style
|
|
6315
|
+
craftNativeFiniteDouble(style, "lineHeight", Double.NaN).takeIf { it.isFinite() }?.let { dp(it) - view.textSize } ?: 0f,
|
|
6282
6316
|
1f
|
|
6283
6317
|
)
|
|
6284
6318
|
val shadowColor = style.optString("textShadowColor").takeIf { it.isNotBlank() }
|
|
@@ -6510,7 +6544,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6510
6544
|
if (type == "Icon") "image" else type.lowercase()
|
|
6511
6545
|
}
|
|
6512
6546
|
val state = props.optJSONObject("accessibilityState")
|
|
6513
|
-
val disabled = props.
|
|
6547
|
+
val disabled = craftNativeBoolean(props.opt("disabled")) || craftNativeBoolean(state?.opt("disabled"))
|
|
6514
6548
|
if (type !in setOf("Button", "Link", "Checkbox", "TextInput", "Picker", "DatePicker", "Modal", "Switch", "Slider", "ActivityIndicator")) {
|
|
6515
6549
|
view.isEnabled = !disabled
|
|
6516
6550
|
} else if (disabled) {
|
|
@@ -6534,8 +6568,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6534
6568
|
info.roleDescription = role.takeIf { it !in setOf("view", "text", "textinput", "scrollview") }
|
|
6535
6569
|
info.hintText = host.tooltipText
|
|
6536
6570
|
info.isEnabled = host.isEnabled && !disabled
|
|
6537
|
-
info.isSelected = state?.
|
|
6538
|
-
info.isChecked = state?.
|
|
6571
|
+
info.isSelected = craftNativeBoolean(state?.opt("selected"))
|
|
6572
|
+
info.isChecked = craftNativeBoolean(state?.opt("checked"))
|
|
6539
6573
|
}
|
|
6540
6574
|
})
|
|
6541
6575
|
ViewCompat.setImportantForAccessibility(
|
|
@@ -6551,11 +6585,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
6551
6585
|
private fun color(raw: String, fallback: Int) = runCatching { craftNativeColor(raw) }.getOrDefault(fallback)
|
|
6552
6586
|
|
|
6553
6587
|
private fun inputTypeFor(props: JSONObject): Int {
|
|
6554
|
-
val base = if (props.
|
|
6588
|
+
val base = if (craftNativeBoolean(props.opt("multiline"))) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
6555
6589
|
else InputType.TYPE_CLASS_TEXT
|
|
6556
6590
|
// Text variations do not carry their input class or multiline flag.
|
|
6557
6591
|
// Preserve both when selecting an email, URL, or search keyboard.
|
|
6558
|
-
val keyboard = when (props.
|
|
6592
|
+
val keyboard = when (props.opt("keyboardType") as? String) {
|
|
6559
6593
|
"email-address" -> base or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
6560
6594
|
"url" -> base or InputType.TYPE_TEXT_VARIATION_URI
|
|
6561
6595
|
"numeric", "number-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
@@ -6565,21 +6599,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6565
6599
|
"visible-password" -> base or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
6566
6600
|
else -> base
|
|
6567
6601
|
}
|
|
6568
|
-
val capitalization = when (props.
|
|
6602
|
+
val capitalization = when (props.opt("autoCapitalize") as? String) {
|
|
6569
6603
|
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
6570
6604
|
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
6571
6605
|
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
6572
6606
|
else -> 0
|
|
6573
6607
|
}
|
|
6574
|
-
val correction = if (props.
|
|
6575
|
-
val secure = if (props.
|
|
6608
|
+
val correction = if (craftNativeBoolean(props.opt("autoCorrect"), true)) InputType.TYPE_TEXT_FLAG_AUTO_CORRECT else 0
|
|
6609
|
+
val secure = if (craftNativeBoolean(props.opt("secureTextEntry"))) InputType.TYPE_TEXT_VARIATION_PASSWORD else 0
|
|
6576
6610
|
return keyboard or capitalization or correction or secure
|
|
6577
6611
|
}
|
|
6578
6612
|
|
|
6579
6613
|
/// The autofill hint for `textContentType` (iOS's names) or `autoComplete`
|
|
6580
6614
|
/// (HTML's), so a password manager fills the field.
|
|
6581
6615
|
private fun autofillHintFor(props: JSONObject): String? {
|
|
6582
|
-
val raw = props.
|
|
6616
|
+
val raw = (props.opt("textContentType") as? String)?.takeIf { it.isNotBlank() }
|
|
6617
|
+
?: (props.opt("autoComplete") as? String).orEmpty()
|
|
6583
6618
|
return when (raw.replace("-", "").replace("_", "").lowercase()) {
|
|
6584
6619
|
"username" -> View.AUTOFILL_HINT_USERNAME
|
|
6585
6620
|
"nickname" -> "personNickname"
|
|
@@ -6610,7 +6645,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6610
6645
|
}
|
|
6611
6646
|
}
|
|
6612
6647
|
|
|
6613
|
-
private fun imeOptionsFor(props: JSONObject): Int = when (props.
|
|
6648
|
+
private fun imeOptionsFor(props: JSONObject): Int = when (props.opt("returnKeyType") as? String) {
|
|
6614
6649
|
"done" -> EditorInfo.IME_ACTION_DONE
|
|
6615
6650
|
"go" -> EditorInfo.IME_ACTION_GO
|
|
6616
6651
|
"next" -> EditorInfo.IME_ACTION_NEXT
|