@craft-native/android 0.0.205 → 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
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package {{PACKAGE_NAME}}
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import org.json.JSONObject
|
|
4
5
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
5
6
|
|
|
6
7
|
/** Swipe-to-refresh host for a native FlatList. */
|
|
@@ -21,6 +22,12 @@ internal class CraftNativeRefreshableFlatList(context: Context) : SwipeRefreshLa
|
|
|
21
22
|
if (handler == null) isRefreshing = false
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
fun setRefreshAppearance(props: JSONObject, fallback: Int) {
|
|
26
|
+
val tint = props.optString("tintColor").takeIf { it.isNotBlank() }
|
|
27
|
+
val resolved = tint?.let { runCatching { craftNativeColor(it) }.getOrNull() } ?: fallback
|
|
28
|
+
setColorSchemeColors(resolved)
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
override fun onDetachedFromWindow() {
|
|
25
32
|
refreshHandler = null
|
|
26
33
|
isRefreshing = false
|
|
@@ -148,28 +148,48 @@ 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) {
|
|
156
|
+
is String -> value.takeIf { it.isNotEmpty() }
|
|
152
157
|
is Number -> value.toDouble().takeIf { it.isFinite() }?.toString()
|
|
153
|
-
else ->
|
|
158
|
+
else -> null
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
private fun craftNativeEventName(events: JSONObject, vararg names: String): String =
|
|
157
162
|
names.firstNotNullOfOrNull { name -> craftNativeOptionalString(events.opt(name)) }.orEmpty()
|
|
158
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
|
+
|
|
159
174
|
internal fun craftNativePadding(style: JSONObject?, name: String, fallback: Int, density: Float): Int {
|
|
160
|
-
val value = style?.
|
|
175
|
+
val value = craftNativeNumber(style?.opt(name)) ?: Double.NaN
|
|
161
176
|
val pixels = value * density
|
|
162
177
|
return if (value.isFinite() && value >= 0 && pixels.isFinite()) pixels.toInt() else fallback
|
|
163
178
|
}
|
|
164
179
|
|
|
165
180
|
internal fun craftNativeFiniteFloat(style: JSONObject?, name: String, fallback: Float): Float {
|
|
166
|
-
val value = style?.
|
|
181
|
+
val value = craftNativeNumber(style?.opt(name)) ?: Double.NaN
|
|
167
182
|
val narrowed = value.toFloat()
|
|
168
183
|
return if (value.isFinite() && narrowed.isFinite()) narrowed else fallback
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
internal fun craftNativeFiniteDouble(style: JSONObject?, name: String, fallback: Double): Double {
|
|
172
|
-
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
|
|
173
193
|
return value.takeIf { it.isFinite() } ?: fallback
|
|
174
194
|
}
|
|
175
195
|
|
|
@@ -220,16 +240,16 @@ private data class CraftNativeLayoutStyle(
|
|
|
220
240
|
companion object {
|
|
221
241
|
fun from(raw: JSONObject, density: Float): CraftNativeLayoutStyle {
|
|
222
242
|
fun length(name: String): CraftNativeLength? = CraftNativeLength.from(raw.opt(name), density)
|
|
223
|
-
fun
|
|
243
|
+
fun numeric(name: String): Double? {
|
|
224
244
|
if (!raw.has(name) || raw.isNull(name)) return null
|
|
225
|
-
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 ->
|
|
226
249
|
(points * density).takeIf { it.isFinite() }?.roundToInt()
|
|
227
250
|
}
|
|
228
251
|
}
|
|
229
|
-
fun decimal(name: String): Double?
|
|
230
|
-
if (!raw.has(name) || raw.isNull(name)) return null
|
|
231
|
-
return raw.optDouble(name, Double.NaN).takeIf { it.isFinite() }
|
|
232
|
-
}
|
|
252
|
+
fun decimal(name: String): Double? = numeric(name)
|
|
233
253
|
val flex = decimal("flex")
|
|
234
254
|
return CraftNativeLayoutStyle(
|
|
235
255
|
width = length("width"), height = length("height"),
|
|
@@ -352,6 +372,14 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
352
372
|
val vectorEffect: String? = null,
|
|
353
373
|
val dominantBaseline: String? = null,
|
|
354
374
|
) {
|
|
375
|
+
private fun svgTypeface(weight: String?, inherited: Typeface): Typeface = when (weight?.lowercase()) {
|
|
376
|
+
"500" -> Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
|
377
|
+
"600" -> Typeface.create("sans-serif", Typeface.BOLD)
|
|
378
|
+
"bold", "700", "800", "900" -> Typeface.DEFAULT_BOLD
|
|
379
|
+
"normal", "100", "200", "300", "400" -> Typeface.DEFAULT
|
|
380
|
+
else -> inherited
|
|
381
|
+
}
|
|
382
|
+
|
|
355
383
|
fun merge(node: JSONObject): SvgPaint {
|
|
356
384
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
357
385
|
val style = node.optJSONObject("style") ?: JSONObject()
|
|
@@ -368,7 +396,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
368
396
|
fun dashes(rawValue: Any?): List<Float>? {
|
|
369
397
|
val values = rawValue as? JSONArray ?: return null
|
|
370
398
|
return (0 until values.length()).mapNotNull {
|
|
371
|
-
values.
|
|
399
|
+
craftNativeNumber(values.opt(it))?.toFloat()?.takeIf { value -> value.isFinite() && value >= 0 }
|
|
372
400
|
}
|
|
373
401
|
}
|
|
374
402
|
// Keep the cascade explicit: computed style wins over a raw prop
|
|
@@ -387,7 +415,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
387
415
|
strokeDashoffset = number("strokeDashoffset") ?: strokeDashoffset,
|
|
388
416
|
strokeMiterlimit = number("strokeMiterlimit")?.takeIf { it > 0 } ?: strokeMiterlimit,
|
|
389
417
|
fillRule = value("fillRule") ?: fillRule,
|
|
390
|
-
typeface =
|
|
418
|
+
typeface = svgTypeface(weight, typeface),
|
|
391
419
|
strokeLinecap = value("strokeLinecap") ?: strokeLinecap,
|
|
392
420
|
strokeLinejoin = value("strokeLinejoin") ?: strokeLinejoin,
|
|
393
421
|
strokeDasharray = dashes(raw("strokeDasharray")) ?: strokeDasharray,
|
|
@@ -446,10 +474,10 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
446
474
|
private fun viewBox(): Quadruple<Float, Float, Float, Float>? {
|
|
447
475
|
val values = drawing.optJSONObject("props")?.optJSONArray("viewBox") ?: return null
|
|
448
476
|
if (values.length() != 4) return null
|
|
449
|
-
val x = values.
|
|
450
|
-
val y = values.
|
|
451
|
-
val width = values.
|
|
452
|
-
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
|
|
453
481
|
if (!x.isFinite() || !y.isFinite() || !width.isFinite() || !height.isFinite() || width <= 0f || height <= 0f) return null
|
|
454
482
|
return Quadruple(width, height, x, y)
|
|
455
483
|
}
|
|
@@ -471,12 +499,12 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
471
499
|
private fun drawPath(canvas: Canvas, node: JSONObject, style: SvgPaint) {
|
|
472
500
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
473
501
|
val geometry = props.optJSONArray("geometry") ?: return
|
|
474
|
-
if ((0 until geometry.length()).any {
|
|
502
|
+
if ((0 until geometry.length()).any { craftNativeNumber(geometry.opt(it))?.toFloat()?.isFinite() != true }) return
|
|
475
503
|
val path = Path()
|
|
476
504
|
var index = 0
|
|
477
505
|
while (index < geometry.length()) {
|
|
478
|
-
val command = geometry.
|
|
479
|
-
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
|
|
480
508
|
when (command) {
|
|
481
509
|
0 -> if (index + 2 < geometry.length()) { path.moveTo(number(1), number(2)); index += 3 } else break
|
|
482
510
|
1 -> if (index + 2 < geometry.length()) { path.lineTo(number(1), number(2)); index += 3 } else break
|
|
@@ -542,9 +570,9 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
542
570
|
for (index in 0 until children.length()) if (children.opt(index) is String) append(children.optString(index))
|
|
543
571
|
}
|
|
544
572
|
if (text.isEmpty()) return
|
|
545
|
-
val size = (if (props.has("fontSize")) props.
|
|
546
|
-
.takeIf { it.isFinite() }
|
|
573
|
+
val size = (if (props.has("fontSize")) craftNativeNumber(props.opt("fontSize")) else craftNativeNumber(nodeStyle.opt("fontSize")))
|
|
547
574
|
?.toFloat()
|
|
575
|
+
?.takeIf { it.isFinite() }
|
|
548
576
|
?: 12f
|
|
549
577
|
val paint = android.graphics.Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
550
578
|
color = resolveColor(style.fill, style.color, Color.BLACK)
|
|
@@ -552,8 +580,8 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
552
580
|
textSize = size
|
|
553
581
|
typeface = style.typeface
|
|
554
582
|
}
|
|
555
|
-
var x = props.
|
|
556
|
-
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
|
|
557
585
|
when (props.optString("textAnchor")) {
|
|
558
586
|
"middle" -> x -= paint.measureText(text) / 2f
|
|
559
587
|
"end" -> x -= paint.measureText(text)
|
|
@@ -620,16 +648,16 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
620
648
|
|
|
621
649
|
private fun applyPending() {
|
|
622
650
|
val map = googleMap ?: return
|
|
623
|
-
map.uiSettings.isScrollGesturesEnabled = pendingProps.
|
|
624
|
-
map.uiSettings.isZoomGesturesEnabled = pendingProps.
|
|
625
|
-
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"))
|
|
626
654
|
map.mapType = when (craftNativeOptionalString(pendingProps.opt("mapType")).orEmpty().lowercase()) {
|
|
627
655
|
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
|
|
628
656
|
"hybrid" -> GoogleMap.MAP_TYPE_HYBRID
|
|
629
657
|
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
|
|
630
658
|
else -> GoogleMap.MAP_TYPE_NORMAL
|
|
631
659
|
}
|
|
632
|
-
val showUserLocation = pendingProps.
|
|
660
|
+
val showUserLocation = craftNativeBoolean(pendingProps.opt("showsUserLocation")) && hasLocationPermission()
|
|
633
661
|
runCatching { map.isMyLocationEnabled = showUserLocation }
|
|
634
662
|
val nextMarkers = mutableMapOf<String, Pair<LatLng, String?>>()
|
|
635
663
|
val nextHandlers = mutableMapOf<String, String>()
|
|
@@ -645,7 +673,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
645
673
|
val points = coordinates(props.optJSONArray("geometry"))
|
|
646
674
|
if (type == "mapmarker" || type == "marker") {
|
|
647
675
|
points.firstOrNull()?.let { point ->
|
|
648
|
-
nextMarkers[id] = point to
|
|
676
|
+
nextMarkers[id] = point to (props.opt("title") as? String)
|
|
649
677
|
listOf(events.opt("onPress"), events.opt("onMarkerPress"))
|
|
650
678
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
651
679
|
?.let { nextHandlers[id] = it }
|
|
@@ -708,7 +736,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
708
736
|
moveCamera(initialRegion)
|
|
709
737
|
initialCameraKey = key
|
|
710
738
|
}
|
|
711
|
-
} else if (pendingProps.
|
|
739
|
+
} else if (craftNativeBoolean(pendingProps.opt("fitToBounds"))) {
|
|
712
740
|
val key = "fit:${overlayKey(nextMarkers, nextPolylines)}"
|
|
713
741
|
if (initialCameraKey != key) {
|
|
714
742
|
if (fitToBounds()) initialCameraKey = key
|
|
@@ -722,7 +750,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
722
750
|
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
723
751
|
// MapView can become ready before its host has a usable viewport. Do
|
|
724
752
|
// not mark a fit as complete until the camera update can be issued.
|
|
725
|
-
if (width > 0 && height > 0 && pendingProps.
|
|
753
|
+
if (width > 0 && height > 0 && craftNativeBoolean(pendingProps.opt("fitToBounds")) && initialCameraKey == null) {
|
|
726
754
|
applyPending()
|
|
727
755
|
}
|
|
728
756
|
}
|
|
@@ -1008,7 +1036,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1008
1036
|
alignContent = style.optString("alignContent", "flex-start")
|
|
1009
1037
|
justifyContent = style.optString("justifyContent", "flex-start")
|
|
1010
1038
|
fun spacing(name: String, fallback: Float): Float {
|
|
1011
|
-
val value = style.
|
|
1039
|
+
val value = craftNativeNumber(style.opt(name)) ?: Double.NaN
|
|
1012
1040
|
val pixels = value * density
|
|
1013
1041
|
val narrowed = pixels.toFloat()
|
|
1014
1042
|
return if (value.isFinite() && value >= 0 && pixels.isFinite() && narrowed.isFinite()) narrowed else fallback
|
|
@@ -2209,13 +2237,13 @@ private class CraftNativeScrollView(context: Context) : SwipeRefreshLayout(conte
|
|
|
2209
2237
|
}
|
|
2210
2238
|
}
|
|
2211
2239
|
|
|
2212
|
-
fun setRefreshAppearance(props: JSONObject, density: Float) {
|
|
2240
|
+
fun setRefreshAppearance(props: JSONObject, density: Float, fallback: Int) {
|
|
2213
2241
|
val tint = craftNativeOptionalString(props.opt("tintColor"))
|
|
2214
|
-
|
|
2215
|
-
else setColorSchemeColors(Color.WHITE)
|
|
2242
|
+
setColorSchemeColors(tint?.let { runCatching { craftNativeColor(it) }.getOrNull() } ?: fallback)
|
|
2216
2243
|
val background = craftNativeOptionalString(props.opt("progressBackgroundColor"))
|
|
2217
|
-
|
|
2218
|
-
|
|
2244
|
+
setProgressBackgroundColorSchemeColor(
|
|
2245
|
+
background?.let { runCatching { craftNativeColor(it) }.getOrNull() } ?: Color.TRANSPARENT,
|
|
2246
|
+
)
|
|
2219
2247
|
val offset = props.optDouble("progressViewOffset", Double.NaN)
|
|
2220
2248
|
val requested = if (offset.isFinite()) (offset * density).roundToInt() else null
|
|
2221
2249
|
if (configuredProgressOffset != requested) {
|
|
@@ -2725,7 +2753,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2725
2753
|
}
|
|
2726
2754
|
|
|
2727
2755
|
fun configure(props: JSONObject) {
|
|
2728
|
-
val requestedMode = when (props.
|
|
2756
|
+
val requestedMode = when (props.opt("mode") as? String) {
|
|
2729
2757
|
"time" -> PickerMode.TIME
|
|
2730
2758
|
"datetime", "dateAndTime", "datetime-local" -> PickerMode.DATE_TIME
|
|
2731
2759
|
"countdown" -> PickerMode.COUNTDOWN
|
|
@@ -2765,7 +2793,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2765
2793
|
}
|
|
2766
2794
|
|
|
2767
2795
|
suppressChanges = true
|
|
2768
|
-
minuteInterval = props.
|
|
2796
|
+
minuteInterval = craftNativeInt(props.opt("minuteInterval"), 1).takeIf { it in 1..30 && 60 % it == 0 } ?: 1
|
|
2769
2797
|
val defaults = defaultDateBounds
|
|
2770
2798
|
datePicker?.minDate = pickerDate(props.opt("minimumDate"))?.timeInMillis ?: defaults?.first ?: datePicker?.minDate ?: 0L
|
|
2771
2799
|
datePicker?.maxDate = pickerDate(props.opt("maximumDate"))?.timeInMillis ?: defaults?.second ?: datePicker?.maxDate ?: Long.MAX_VALUE
|
|
@@ -2775,7 +2803,7 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2775
2803
|
} else {
|
|
2776
2804
|
pickerDate(props.opt("value"))?.let { applyDate(it) }
|
|
2777
2805
|
}
|
|
2778
|
-
val enabled = !props.
|
|
2806
|
+
val enabled = !craftNativeBoolean(props.opt("disabled"))
|
|
2779
2807
|
isEnabled = enabled
|
|
2780
2808
|
datePicker?.isEnabled = enabled
|
|
2781
2809
|
timePicker?.isEnabled = enabled
|
|
@@ -2862,7 +2890,8 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2862
2890
|
if (text.isBlank() || text == JSONObject.NULL.toString()) return null
|
|
2863
2891
|
val formats = listOf(
|
|
2864
2892
|
"HH:mm", "HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm",
|
|
2865
|
-
"yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.
|
|
2893
|
+
"yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSS",
|
|
2894
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "yyyy-MM-dd'T'HH:mm:ssX",
|
|
2866
2895
|
)
|
|
2867
2896
|
formats.firstNotNullOfOrNull { format ->
|
|
2868
2897
|
runCatching {
|
|
@@ -4485,6 +4514,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
4485
4514
|
screen.view.setBackgroundColor(configured ?: ContextCompat.getColor(this, R.color.background))
|
|
4486
4515
|
}
|
|
4487
4516
|
|
|
4517
|
+
/** Refresh controls use a contrasting system tint when no color is authored. */
|
|
4518
|
+
private fun refreshTintFallback(screen: Screen): Int {
|
|
4519
|
+
val background = (screen.view.background as? ColorDrawable)?.color
|
|
4520
|
+
?: ContextCompat.getColor(this, R.color.background)
|
|
4521
|
+
return if (Color.luminance(background) < 0.5f) Color.WHITE else Color.BLACK
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4488
4524
|
/** The bridge's capability names: Craft's core set and this host's services. */
|
|
4489
4525
|
private fun capabilityNames(): JSONArray = capabilities.names().apply {
|
|
4490
4526
|
services.names().forEach { name -> put(name) }
|
|
@@ -4908,7 +4944,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4908
4944
|
val control = when (type) {
|
|
4909
4945
|
"Text" -> ((previous as? TextView)?.takeUnless { it is Button || it is EditText } ?: TextView(this)).apply {
|
|
4910
4946
|
includeFontPadding = false
|
|
4911
|
-
maxLines = props.
|
|
4947
|
+
maxLines = craftNativeInt(props.opt("numberOfLines"), 0).takeIf { it > 0 } ?: Int.MAX_VALUE
|
|
4912
4948
|
ellipsize = when (props.optString("ellipsizeMode")) {
|
|
4913
4949
|
"head" -> TextUtils.TruncateAt.START
|
|
4914
4950
|
"middle" -> TextUtils.TruncateAt.MIDDLE
|
|
@@ -4919,7 +4955,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4919
4955
|
}
|
|
4920
4956
|
"StatusBar" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
4921
4957
|
screen.statusBarStyle = craftNativeOptionalString(props.opt("barStyle"))
|
|
4922
|
-
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
|
|
4923
4959
|
visibility = View.GONE
|
|
4924
4960
|
}
|
|
4925
4961
|
"RefreshControl" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
@@ -4927,17 +4963,18 @@ class MainActivity : AppCompatActivity() {
|
|
|
4927
4963
|
}
|
|
4928
4964
|
"Link" -> ((previous as? TextView)?.takeUnless { it is Button } ?: TextView(this)).apply {
|
|
4929
4965
|
includeFontPadding = false
|
|
4930
|
-
configureText(this,
|
|
4966
|
+
configureText(this, (props.opt("title") as? String) ?: childText(children), style)
|
|
4931
4967
|
val linkColor = craftNativeOptionalString(props.opt("color")) ?: craftNativeOptionalString(style.opt("color"))
|
|
4932
4968
|
setTextColor(color(linkColor.orEmpty(), Color.rgb(33, 150, 243)))
|
|
4933
4969
|
paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
|
4934
|
-
isEnabled = !props.
|
|
4970
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4935
4971
|
isClickable = true
|
|
4936
4972
|
isFocusable = true
|
|
4937
4973
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
4938
|
-
.
|
|
4974
|
+
.mapNotNull { it as? String }
|
|
4975
|
+
.firstOrNull { it.trim().isNotEmpty() }
|
|
4939
4976
|
?.trim()
|
|
4940
|
-
?.takeIf { !props.
|
|
4977
|
+
?.takeIf { !craftNativeBoolean(props.opt("disabled")) }
|
|
4941
4978
|
if (target != null) screen.linkTargets[this] = target else screen.linkTargets.remove(this)
|
|
4942
4979
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4943
4980
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
@@ -4958,11 +4995,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
4958
4995
|
minimumHeight = 0
|
|
4959
4996
|
setPadding(0, 0, 0, 0)
|
|
4960
4997
|
}).apply {
|
|
4961
|
-
val title =
|
|
4998
|
+
val title = (props.opt("title") as? String) ?: childText(children)
|
|
4962
4999
|
isAllCaps = false
|
|
4963
5000
|
configureText(this, title, style)
|
|
4964
|
-
craftNativeOptionalString(props.opt("color"))
|
|
4965
|
-
|
|
5001
|
+
val buttonColor = craftNativeOptionalString(props.opt("color"))
|
|
5002
|
+
?: craftNativeOptionalString(style.opt("color"))
|
|
5003
|
+
setTextColor(buttonColor?.let { color(it, defaultTextColor) } ?: defaultTextColor)
|
|
5004
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4966
5005
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
4967
5006
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
4968
5007
|
setOnClickListener {
|
|
@@ -4971,13 +5010,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
4971
5010
|
}
|
|
4972
5011
|
}
|
|
4973
5012
|
"Checkbox" -> (previous as? CheckBox ?: CheckBox(this)).apply {
|
|
4974
|
-
text =
|
|
5013
|
+
text = (props.opt("label") as? String) ?: childText(children)
|
|
4975
5014
|
configureText(this, text.toString(), style)
|
|
4976
|
-
val
|
|
4977
|
-
val
|
|
4978
|
-
|
|
4979
|
-
is
|
|
4980
|
-
is
|
|
5015
|
+
val checkboxChecked = props.opt("checked")
|
|
5016
|
+
val checkboxValue = props.opt("value")
|
|
5017
|
+
val checked = when {
|
|
5018
|
+
checkboxChecked is Boolean -> checkboxChecked
|
|
5019
|
+
checkboxValue is Boolean -> checkboxValue
|
|
4981
5020
|
else -> false
|
|
4982
5021
|
}
|
|
4983
5022
|
setOnCheckedChangeListener(null)
|
|
@@ -4985,11 +5024,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
4985
5024
|
// an indeterminate control must announce "mixed" and its next
|
|
4986
5025
|
// user toggle should transition to checked, not unchecked.
|
|
4987
5026
|
isChecked = checked
|
|
4988
|
-
|
|
4989
|
-
|
|
5027
|
+
val indeterminate = (props.opt("indeterminate") as? Boolean) == true
|
|
5028
|
+
isActivated = indeterminate
|
|
5029
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
4990
5030
|
val checkboxColor = craftNativeOptionalString(props.opt("color"))
|
|
4991
5031
|
?: craftNativeOptionalString(style.opt("color"))
|
|
4992
|
-
buttonTintList = checkboxColor
|
|
5032
|
+
buttonTintList = checkboxColor
|
|
5033
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5034
|
+
?.let { ColorStateList.valueOf(it) }
|
|
4993
5035
|
val handler = craftNativeEventName(events, "onValueChange", "onChange")
|
|
4994
5036
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
4995
5037
|
setOnCheckedChangeListener { _, value ->
|
|
@@ -5013,16 +5055,17 @@ class MainActivity : AppCompatActivity() {
|
|
|
5013
5055
|
val defaultPadding = inputPaddingDefaults.getOrPut(this) {
|
|
5014
5056
|
intArrayOf(paddingLeft, paddingTop, paddingRight, paddingBottom)
|
|
5015
5057
|
}
|
|
5016
|
-
hint =
|
|
5058
|
+
hint = (props.opt("placeholder") as? String).orEmpty()
|
|
5017
5059
|
setHintTextColor(
|
|
5018
|
-
|
|
5019
|
-
?.let {
|
|
5060
|
+
(props.opt("placeholderTextColor") as? String)
|
|
5061
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5062
|
+
?.let { ColorStateList.valueOf(it) }
|
|
5020
5063
|
?: defaultHintColors,
|
|
5021
5064
|
)
|
|
5022
|
-
highlightColor =
|
|
5023
|
-
?.let {
|
|
5065
|
+
highlightColor = (props.opt("selectionColor") as? String)
|
|
5066
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5024
5067
|
?: defaultHighlightColor
|
|
5025
|
-
val maxLength = props.
|
|
5068
|
+
val maxLength = craftNativeInt(props.opt("maxLength"), 0)
|
|
5026
5069
|
val currentLength = (filters.firstOrNull { it is InputFilter.LengthFilter } as? InputFilter.LengthFilter)?.max ?: 0
|
|
5027
5070
|
if (currentLength != maxLength || (maxLength == 0 && filters.isNotEmpty())) {
|
|
5028
5071
|
filters = if (maxLength > 0) arrayOf(InputFilter.LengthFilter(maxLength)) else emptyArray()
|
|
@@ -5031,11 +5074,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5031
5074
|
if (inputType != wantedType) inputType = wantedType
|
|
5032
5075
|
val wantedIme = imeOptionsFor(props)
|
|
5033
5076
|
if (imeOptions != wantedIme) imeOptions = wantedIme
|
|
5034
|
-
val multiline = props.
|
|
5077
|
+
val multiline = craftNativeBoolean(props.opt("multiline"))
|
|
5035
5078
|
if (isSingleLine == multiline) isSingleLine = !multiline
|
|
5036
|
-
val wantedLines = if (multiline) props.
|
|
5079
|
+
val wantedLines = if (multiline) craftNativeInt(props.opt("numberOfLines"), Int.MAX_VALUE).coerceAtLeast(1) else 1
|
|
5037
5080
|
if (maxLines != wantedLines) maxLines = wantedLines
|
|
5038
|
-
isEnabled = props.
|
|
5081
|
+
isEnabled = craftNativeBoolean(props.opt("editable"), true)
|
|
5039
5082
|
configureTextStyle(this, style)
|
|
5040
5083
|
// The style's padding is where the text starts, as on iOS and
|
|
5041
5084
|
// the web; without one the field keeps its own.
|
|
@@ -5063,14 +5106,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5063
5106
|
// them after keyed reconciliation.
|
|
5064
5107
|
val autofillHint = autofillHintFor(props)
|
|
5065
5108
|
if (autofillHint == null) setAutofillHints(*emptyArray()) else setAutofillHints(autofillHint)
|
|
5066
|
-
val controlledValue = props.opt("value")
|
|
5109
|
+
val controlledValue = props.opt("value") as? String
|
|
5067
5110
|
val echoes = screen.inputEchoes.getOrPut(this) { CraftNativeInputEcho() }
|
|
5068
5111
|
if (echoes.accept(controlledValue, text.toString()) && controlledValue != null) {
|
|
5069
5112
|
screen.suppressedTextChanges.add(this)
|
|
5070
5113
|
setTextPreservingSelection(this, controlledValue)
|
|
5071
5114
|
}
|
|
5072
5115
|
else if (this !== previous && screen.drafts.containsKey(identity)) setText(screen.drafts[identity])
|
|
5073
|
-
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
|
+
}
|
|
5074
5119
|
applyTextDecoration(this, style)
|
|
5075
5120
|
screen.inputIdentities[this] = identity
|
|
5076
5121
|
val handler = craftNativeEventName(events, "onChange", "onChangeText")
|
|
@@ -5105,7 +5150,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5105
5150
|
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("text", text.toString())))
|
|
5106
5151
|
true
|
|
5107
5152
|
}
|
|
5108
|
-
if (props.
|
|
5153
|
+
if (craftNativeBoolean(props.opt("autoFocus")) && !isFocused) post {
|
|
5109
5154
|
if (!isFocused) {
|
|
5110
5155
|
requestFocus()
|
|
5111
5156
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
@@ -5135,7 +5180,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5135
5180
|
val itemOptions = props.optJSONArray("items")?.let { items ->
|
|
5136
5181
|
(0 until items.length()).mapNotNull { index ->
|
|
5137
5182
|
val item = items.optJSONObject(index) ?: return@mapNotNull null
|
|
5138
|
-
val label =
|
|
5183
|
+
val label = (item.opt("label") as? String)?.takeIf { it.isNotEmpty() } ?: return@mapNotNull null
|
|
5139
5184
|
craftNativePickerValue(item.opt("value"))?.let { it to label } ?: (label to label)
|
|
5140
5185
|
}
|
|
5141
5186
|
}.orEmpty()
|
|
@@ -5143,7 +5188,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5143
5188
|
val option = children.optJSONObject(index) ?: return@mapNotNull null
|
|
5144
5189
|
if (option.optString("type") != "Text") return@mapNotNull null
|
|
5145
5190
|
val label = (option.optJSONArray("children") ?: JSONArray()).let { text ->
|
|
5146
|
-
(0 until text.length()).
|
|
5191
|
+
(0 until text.length()).mapNotNull { text.opt(it) as? String }.joinToString("")
|
|
5147
5192
|
}
|
|
5148
5193
|
if (label.isBlank()) return@mapNotNull null
|
|
5149
5194
|
val optionProps = option.optJSONObject("props")
|
|
@@ -5162,7 +5207,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5162
5207
|
val requested = craftNativePickerValue(props.opt("selectedValue"))
|
|
5163
5208
|
?: craftNativePickerValue(props.opt("value")).orEmpty()
|
|
5164
5209
|
val selected = options.indexOfFirst { it.first == requested }.takeIf { it >= 0 } ?: 0
|
|
5165
|
-
isEnabled = !props.
|
|
5210
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5166
5211
|
// Mounting a controlled picker must not look like a user
|
|
5167
5212
|
// change. Android may dispatch the initial adapter selection
|
|
5168
5213
|
// during the first layout pass; iOS only reports a choice
|
|
@@ -5192,7 +5237,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5192
5237
|
}
|
|
5193
5238
|
}
|
|
5194
5239
|
"Modal" -> (previous as? CraftNativeModalView ?: CraftNativeModalView(this)).apply {
|
|
5195
|
-
transparent = props.
|
|
5240
|
+
transparent = craftNativeBoolean(props.opt("transparent"))
|
|
5196
5241
|
content.configure(style, resources.displayMetrics.density)
|
|
5197
5242
|
reconcileChildren(screen, content, children, identity, next, style, previousControls)
|
|
5198
5243
|
// Dynamic :visible bindings arrive as a display style mutation;
|
|
@@ -5201,7 +5246,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5201
5246
|
// An explicit visible prop owns modal visibility. The
|
|
5202
5247
|
// binding runtime may leave a stale display:none style
|
|
5203
5248
|
// from the previous hidden state when it patches props.
|
|
5204
|
-
props.
|
|
5249
|
+
craftNativeBoolean(props.opt("visible"))
|
|
5205
5250
|
} else {
|
|
5206
5251
|
style.has("display") && style.optString("display") != "none"
|
|
5207
5252
|
}
|
|
@@ -5237,7 +5282,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5237
5282
|
}
|
|
5238
5283
|
"TouchableOpacity", "TouchableHighlight", "Pressable" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
5239
5284
|
configureContainer(this, style)
|
|
5240
|
-
val disabled = props.
|
|
5285
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5241
5286
|
screen.pressActiveOpacities[this] = craftNativeFiniteFloat(props, "activeOpacity", 0.5f)
|
|
5242
5287
|
screen.pressBaseOpacities[this] = craftNativeFiniteFloat(style, "opacity", 1f)
|
|
5243
5288
|
if (type == "TouchableHighlight" && !disabled) {
|
|
@@ -5254,24 +5299,29 @@ class MainActivity : AppCompatActivity() {
|
|
|
5254
5299
|
val requestedValue = props.opt("value")
|
|
5255
5300
|
val requestedChecked = props.opt("checked")
|
|
5256
5301
|
isChecked = when {
|
|
5257
|
-
requestedValue != null && requestedValue != JSONObject.NULL ->
|
|
5258
|
-
requestedChecked == null || requestedChecked == JSONObject.NULL -> false
|
|
5302
|
+
requestedValue != null && requestedValue != JSONObject.NULL -> requestedValue as? Boolean ?: false
|
|
5259
5303
|
requestedChecked is Boolean -> requestedChecked
|
|
5260
|
-
|
|
5261
|
-
requestedChecked is String -> requestedChecked.isBlank() || requestedChecked.equals("true", ignoreCase = true)
|
|
5262
|
-
else -> props.optBoolean("checked", false)
|
|
5304
|
+
else -> false
|
|
5263
5305
|
}
|
|
5264
|
-
isEnabled = !props.
|
|
5306
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5265
5307
|
props.optJSONObject("trackColor")?.let { track ->
|
|
5266
|
-
val
|
|
5267
|
-
val
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5308
|
+
val falseRaw = craftNativeOptionalString(track.opt("false"))
|
|
5309
|
+
val trueRaw = craftNativeOptionalString(track.opt("true"))
|
|
5310
|
+
val falseColor = falseRaw?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5311
|
+
val trueColor = trueRaw?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5312
|
+
trackTintList = if ((falseRaw != null && falseColor == null) || (trueRaw != null && trueColor == null)) {
|
|
5313
|
+
null
|
|
5314
|
+
} else {
|
|
5315
|
+
val resolvedFalse = falseColor ?: Color.GRAY
|
|
5316
|
+
ColorStateList(
|
|
5317
|
+
arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
|
|
5318
|
+
intArrayOf(trueColor ?: resolvedFalse, resolvedFalse),
|
|
5319
|
+
)
|
|
5320
|
+
}
|
|
5272
5321
|
} ?: run { trackTintList = null }
|
|
5273
5322
|
craftNativeOptionalString(props.opt("thumbColor"))
|
|
5274
|
-
?.let {
|
|
5323
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5324
|
+
?.let { thumbTintList = ColorStateList.valueOf(it) }
|
|
5275
5325
|
?: run { thumbTintList = null }
|
|
5276
5326
|
val handler = craftNativeEventName(events, "onValueChange", "onChange")
|
|
5277
5327
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
@@ -5281,11 +5331,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5281
5331
|
}
|
|
5282
5332
|
}
|
|
5283
5333
|
"Slider" -> (previous as? SeekBar ?: SeekBar(this)).apply {
|
|
5284
|
-
val minimum =
|
|
5285
|
-
val maximumCandidate =
|
|
5334
|
+
val minimum = craftNativeFiniteNumber(props, "minimumValue", 0.0)
|
|
5335
|
+
val maximumCandidate = craftNativeFiniteNumber(props, "maximumValue", 1.0)
|
|
5286
5336
|
val maximum = if (maximumCandidate > minimum) maximumCandidate else minimum + Double.MIN_VALUE
|
|
5287
5337
|
setOnSeekBarChangeListener(null)
|
|
5288
|
-
val step =
|
|
5338
|
+
val step = craftNativeFiniteNumber(props, "step", 0.0).takeIf { it > 0 }
|
|
5289
5339
|
// A stepped slider needs one native position per authored
|
|
5290
5340
|
// step; a fixed 1,000-position SeekBar silently made large
|
|
5291
5341
|
// ranges unable to represent most valid values. Continuous
|
|
@@ -5299,7 +5349,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5299
5349
|
.toInt()
|
|
5300
5350
|
} ?: 1000
|
|
5301
5351
|
max = progressMax
|
|
5302
|
-
val requested =
|
|
5352
|
+
val requested = craftNativeFiniteNumber(props, "value", minimum)
|
|
5303
5353
|
val value = (if (step != null) minimum + ((requested - minimum) / step).roundToInt() * step else requested)
|
|
5304
5354
|
.coerceIn(minimum, maximum)
|
|
5305
5355
|
progress = if (step != null) {
|
|
@@ -5308,16 +5358,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5308
5358
|
(((value - minimum) / (maximum - minimum)) * max).roundToInt().coerceIn(0, max)
|
|
5309
5359
|
}
|
|
5310
5360
|
if (step != null) screen.sliderSteps[this] = step else screen.sliderSteps.remove(this)
|
|
5311
|
-
craftNativeOptionalString(props.opt("minimumTrackTintColor"))
|
|
5312
|
-
?.let {
|
|
5313
|
-
|
|
5314
|
-
craftNativeOptionalString(props.opt("maximumTrackTintColor"))
|
|
5315
|
-
?.let {
|
|
5316
|
-
|
|
5317
|
-
craftNativeOptionalString(props.opt("thumbTintColor"))
|
|
5318
|
-
?.let {
|
|
5319
|
-
|
|
5320
|
-
isEnabled = !props.
|
|
5361
|
+
progressTintList = craftNativeOptionalString(props.opt("minimumTrackTintColor"))
|
|
5362
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5363
|
+
?.let { ColorStateList.valueOf(it) }
|
|
5364
|
+
progressBackgroundTintList = craftNativeOptionalString(props.opt("maximumTrackTintColor"))
|
|
5365
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5366
|
+
?.let { ColorStateList.valueOf(it) }
|
|
5367
|
+
thumbTintList = craftNativeOptionalString(props.opt("thumbTintColor"))
|
|
5368
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5369
|
+
?.let { ColorStateList.valueOf(it) }
|
|
5370
|
+
isEnabled = !craftNativeBoolean(props.opt("disabled"))
|
|
5321
5371
|
val handler = craftNativeEventName(events, "onValueChange", "onChange")
|
|
5322
5372
|
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
5323
5373
|
val complete = craftNativeEventName(events, "onSlidingComplete")
|
|
@@ -5351,12 +5401,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
5351
5401
|
else -> 1f
|
|
5352
5402
|
}
|
|
5353
5403
|
indicatorScale = scale
|
|
5354
|
-
val animating = props.
|
|
5404
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5355
5405
|
isIndeterminate = animating
|
|
5356
|
-
visibility = if (!animating && props.
|
|
5357
|
-
craftNativeOptionalString(props.opt("color"))
|
|
5358
|
-
?.let {
|
|
5359
|
-
|
|
5406
|
+
visibility = if (!animating && craftNativeBoolean(props.opt("hidesWhenStopped"), true)) View.GONE else View.VISIBLE
|
|
5407
|
+
indeterminateTintList = craftNativeOptionalString(props.opt("color"))
|
|
5408
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5409
|
+
?.let { ColorStateList.valueOf(it) }
|
|
5360
5410
|
}
|
|
5361
5411
|
"Icon" -> (previous as? CraftNativeIconView ?: CraftNativeIconView(this)).apply {
|
|
5362
5412
|
configureIcon(this, props, style)
|
|
@@ -5386,9 +5436,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5386
5436
|
"center" -> ImageView.ScaleType.CENTER
|
|
5387
5437
|
else -> ImageView.ScaleType.CENTER_INSIDE
|
|
5388
5438
|
}
|
|
5389
|
-
craftNativeOptionalString(style.opt("tintColor"))
|
|
5390
|
-
?.let {
|
|
5391
|
-
|
|
5439
|
+
val tint = craftNativeOptionalString(style.opt("tintColor"))
|
|
5440
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
5441
|
+
if (tint == null) image.clearColorFilter() else image.setColorFilter(tint)
|
|
5392
5442
|
val onLoadStart = craftNativeEventName(events, "onLoadStart")
|
|
5393
5443
|
if (onLoadStart.isNotBlank()) screen.imageLoadStartHandlers[image] = onLoadStart else screen.imageLoadStartHandlers.remove(image)
|
|
5394
5444
|
val onLoad = craftNativeEventName(events, "onLoad")
|
|
@@ -5409,13 +5459,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
5409
5459
|
onPictureInPictureChanged = { updatePictureInPicture() }
|
|
5410
5460
|
configure(
|
|
5411
5461
|
source = props.opt("source") ?: props.opt("src"),
|
|
5412
|
-
autoplay = props.
|
|
5413
|
-
paused = props.
|
|
5414
|
-
loop = props.
|
|
5415
|
-
muted = props.
|
|
5416
|
-
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")),
|
|
5417
5467
|
resizeMode = craftNativeResizeMode(style, props),
|
|
5418
|
-
pictureInPicture = props.
|
|
5468
|
+
pictureInPicture = craftNativeBoolean(props.opt("pictureInPicture")),
|
|
5419
5469
|
)
|
|
5420
5470
|
}
|
|
5421
5471
|
"Map" -> (previous as? CraftNativeMapView ?: CraftNativeMapView(this)).apply {
|
|
@@ -5430,21 +5480,21 @@ class MainActivity : AppCompatActivity() {
|
|
|
5430
5480
|
drawing = node
|
|
5431
5481
|
}
|
|
5432
5482
|
"ScrollView", "SectionList" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
|
|
5433
|
-
val horizontal = props.
|
|
5483
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal")) || style.optString("flexDirection") in setOf("row", "row-reverse")
|
|
5434
5484
|
setHorizontal(horizontal)
|
|
5435
5485
|
setNativeNestedScrollingEnabled(true)
|
|
5436
5486
|
setScrollIndicators(
|
|
5437
|
-
props.
|
|
5438
|
-
props.
|
|
5487
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5488
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5439
5489
|
)
|
|
5440
|
-
setScrollEnabled(props.
|
|
5490
|
+
setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5441
5491
|
setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
5442
|
-
setBounces(props.
|
|
5492
|
+
setBounces(craftNativeBoolean(props.opt("bounces"), true))
|
|
5443
5493
|
setAlwaysBounce(
|
|
5444
|
-
props.
|
|
5445
|
-
props.
|
|
5494
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5495
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5446
5496
|
)
|
|
5447
|
-
setPagingEnabled(props.
|
|
5497
|
+
setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5448
5498
|
val refreshNode = (0 until children.length()).asSequence()
|
|
5449
5499
|
.mapNotNull { children.optJSONObject(it) }
|
|
5450
5500
|
.firstOrNull { canonicalType(it.optString("type")) == "RefreshControl" }
|
|
@@ -5457,8 +5507,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5457
5507
|
val refreshHandler = refreshHandlerName?.let { handler ->
|
|
5458
5508
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5459
5509
|
}
|
|
5460
|
-
setRefreshHandler(refreshHandler, refreshProps.
|
|
5461
|
-
setRefreshAppearance(refreshProps, resources.displayMetrics.density)
|
|
5510
|
+
setRefreshHandler(refreshHandler, craftNativeBoolean(refreshProps.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5511
|
+
setRefreshAppearance(refreshProps, resources.displayMetrics.density, refreshTintFallback(screen))
|
|
5462
5512
|
val onScroll = craftNativeEventName(events, "onScroll")
|
|
5463
5513
|
if (onScroll.isNotBlank()) screen.scrollHandlers[this] = onScroll else screen.scrollHandlers.remove(this)
|
|
5464
5514
|
val onBegin = craftNativeEventName(events, "onScrollBeginDrag")
|
|
@@ -5528,9 +5578,15 @@ class MainActivity : AppCompatActivity() {
|
|
|
5528
5578
|
}
|
|
5529
5579
|
"FlatList" -> (previous as? CraftNativeRefreshableFlatList ?: CraftNativeRefreshableFlatList(this)).apply {
|
|
5530
5580
|
val nativeList = list
|
|
5531
|
-
|
|
5581
|
+
val propRefresh = props.optJSONObject("refreshControl") ?: JSONObject()
|
|
5582
|
+
val propRefreshEvents = propRefresh.optJSONObject("events") ?: JSONObject()
|
|
5583
|
+
val refreshHandlerName = craftNativeEventName(propRefreshEvents, "onRefresh")
|
|
5584
|
+
.takeIf { it.isNotBlank() }
|
|
5585
|
+
?: craftNativeEventName(events, "onRefresh").takeIf { it.isNotBlank() }
|
|
5586
|
+
configureRefresh(refreshHandlerName?.let { handler ->
|
|
5532
5587
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5533
|
-
},
|
|
5588
|
+
}, craftNativeBoolean(propRefresh.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5589
|
+
setRefreshAppearance(propRefresh, refreshTintFallback(screen))
|
|
5534
5590
|
nativeList.setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
5535
5591
|
nativeList.setContentContainerStyle(props.optJSONObject("contentContainerStyle"), resources.displayMetrics.density)
|
|
5536
5592
|
nativeList.setKeyboardDismissMode(props.optString("keyboardDismissMode")) {
|
|
@@ -5569,7 +5625,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5569
5625
|
if (underlayHideHandler != null) screen.pressUnderlayHideHandlers[control] = underlayHideHandler else screen.pressUnderlayHideHandlers.remove(control)
|
|
5570
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") {
|
|
5571
5627
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
5572
|
-
val disabled = props.
|
|
5628
|
+
val disabled = craftNativeBoolean(props.opt("disabled"))
|
|
5573
5629
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
5574
5630
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
5575
5631
|
?.trim()
|
|
@@ -5640,8 +5696,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5640
5696
|
}
|
|
5641
5697
|
control.isEnabled = !disabled
|
|
5642
5698
|
}
|
|
5643
|
-
val longPressDisabled = props.
|
|
5644
|
-
props.optJSONObject("accessibilityState")?.
|
|
5699
|
+
val longPressDisabled = craftNativeBoolean(props.opt("disabled")) ||
|
|
5700
|
+
craftNativeBoolean(props.optJSONObject("accessibilityState")?.opt("disabled"))
|
|
5645
5701
|
val longPressHandler = craftNativeEventName(events, "onLongPress").takeUnless { longPressDisabled } ?: ""
|
|
5646
5702
|
if (longPressHandler.isNotBlank()) {
|
|
5647
5703
|
screen.longPressHandlers[control] = longPressHandler
|
|
@@ -5686,8 +5742,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
5686
5742
|
next[identity] = control
|
|
5687
5743
|
applyViewStyle(control, style)
|
|
5688
5744
|
if (type == "ActivityIndicator") {
|
|
5689
|
-
val animating = props.
|
|
5690
|
-
val hidesWhenStopped = props.
|
|
5745
|
+
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5746
|
+
val hidesWhenStopped = craftNativeBoolean(props.opt("hidesWhenStopped"), true)
|
|
5691
5747
|
control.visibility = if (style.optString("display") == "none" || (!animating && hidesWhenStopped)) {
|
|
5692
5748
|
View.GONE
|
|
5693
5749
|
} else {
|
|
@@ -5800,8 +5856,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5800
5856
|
is JSONObject -> makeView(screen, child, "$path.$index", next, previousControls)
|
|
5801
5857
|
is String -> TextView(this).apply {
|
|
5802
5858
|
includeFontPadding = false
|
|
5803
|
-
|
|
5804
|
-
setTextColor(defaultTextColor)
|
|
5859
|
+
configureText(this, child, craftNativeTypography(style))
|
|
5805
5860
|
}
|
|
5806
5861
|
else -> null
|
|
5807
5862
|
}
|
|
@@ -5859,12 +5914,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
5859
5914
|
}
|
|
5860
5915
|
val threshold = craftNativeNonNegativeDouble(props, "threshold", 0.1)
|
|
5861
5916
|
val authoredThreshold = craftNativeNonNegativeDouble(props, "onEndReachedThreshold", threshold)
|
|
5862
|
-
val horizontal = props.
|
|
5917
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal"))
|
|
5863
5918
|
list.apply(
|
|
5864
5919
|
nodes = nodes,
|
|
5865
5920
|
horizontal = horizontal,
|
|
5866
|
-
columns = props.
|
|
5867
|
-
inverted = props.
|
|
5921
|
+
columns = craftNativeInt(props.opt("numColumns"), 1),
|
|
5922
|
+
inverted = craftNativeBoolean(props.opt("inverted")),
|
|
5868
5923
|
endReachedThreshold = authoredThreshold,
|
|
5869
5924
|
renderItem = { node, itemIdentity, _ ->
|
|
5870
5925
|
val rows = screen.flatListRows.getOrPut(list) { mutableMapOf() }
|
|
@@ -5884,16 +5939,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5884
5939
|
},
|
|
5885
5940
|
)
|
|
5886
5941
|
list.setScrollIndicators(
|
|
5887
|
-
props.
|
|
5888
|
-
props.
|
|
5942
|
+
craftNativeBoolean(props.opt("showsVerticalScrollIndicator"), true),
|
|
5943
|
+
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5889
5944
|
)
|
|
5890
|
-
list.setScrollEnabled(props.
|
|
5891
|
-
list.setPagingEnabled(props.
|
|
5945
|
+
list.setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5946
|
+
list.setPagingEnabled(craftNativeBoolean(props.opt("pagingEnabled")))
|
|
5892
5947
|
list.setScrollTarget(props.opt("scrollTarget"))
|
|
5893
5948
|
list.setBounceOptions(
|
|
5894
|
-
props.
|
|
5895
|
-
props.
|
|
5896
|
-
props.
|
|
5949
|
+
craftNativeBoolean(props.opt("bounces"), true),
|
|
5950
|
+
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
5951
|
+
craftNativeBoolean(props.opt("alwaysBounceHorizontal"), horizontal),
|
|
5897
5952
|
)
|
|
5898
5953
|
}
|
|
5899
5954
|
|
|
@@ -6046,10 +6101,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
6046
6101
|
}
|
|
6047
6102
|
if (view is ImageView) {
|
|
6048
6103
|
val tint = style.opt("tintColor") as? String
|
|
6049
|
-
|
|
6104
|
+
val resolvedTint = tint
|
|
6105
|
+
?.takeUnless { it.isBlank() || it == "null" || it == "undefined" }
|
|
6106
|
+
?.let { runCatching { craftNativeColor(it) }.getOrNull() }
|
|
6107
|
+
if (resolvedTint == null) {
|
|
6050
6108
|
view.clearColorFilter()
|
|
6051
6109
|
} else {
|
|
6052
|
-
view.setColorFilter(
|
|
6110
|
+
view.setColorFilter(resolvedTint)
|
|
6053
6111
|
}
|
|
6054
6112
|
}
|
|
6055
6113
|
}
|
|
@@ -6120,6 +6178,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6120
6178
|
configureTextStyle(view, style)
|
|
6121
6179
|
}
|
|
6122
6180
|
|
|
6181
|
+
/**
|
|
6182
|
+
* Raw strings inside a native container are implicit Text nodes. Keep
|
|
6183
|
+
* their typography, but not the parent's padding or layout, just like
|
|
6184
|
+
* the iOS reconciler does for the same child shape.
|
|
6185
|
+
*/
|
|
6186
|
+
private fun craftNativeTypography(style: JSONObject): JSONObject = JSONObject().also { typography ->
|
|
6187
|
+
listOf(
|
|
6188
|
+
"color", "letterSpacing", "lineHeight", "fontSize", "fontFamily",
|
|
6189
|
+
"fontWeight", "fontStyle", "textAlign", "textTransform",
|
|
6190
|
+
"textDecorationLine", "textDecorationStyle", "textDecorationColor",
|
|
6191
|
+
"textShadowColor", "textShadowRadius", "textShadowOffset",
|
|
6192
|
+
).forEach { key ->
|
|
6193
|
+
if (style.has(key) && !style.isNull(key)) typography.put(key, style.opt(key))
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6123
6197
|
private fun configureIcon(view: CraftNativeIconView, props: JSONObject, style: JSONObject) {
|
|
6124
6198
|
val symbol = craftNativeOptionalString(props.opt("symbol"))
|
|
6125
6199
|
?: craftNativeOptionalString(props.opt("name")).orEmpty()
|
|
@@ -6139,11 +6213,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6139
6213
|
)
|
|
6140
6214
|
view.vector = vector
|
|
6141
6215
|
view.text = if (vector != null) "" else androidIconGlyph(symbol)
|
|
6142
|
-
val weight = when (style.
|
|
6143
|
-
"
|
|
6144
|
-
|
|
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)
|
|
6145
6231
|
}
|
|
6146
|
-
view.typeface = Typeface.create("sans-serif", weight)
|
|
6147
6232
|
val hasExplicitWidth = style.has("width") && !style.isNull("width")
|
|
6148
6233
|
val hasExplicitHeight = style.has("height") && !style.isNull("height")
|
|
6149
6234
|
if (!hasExplicitWidth && !hasExplicitHeight) {
|
|
@@ -6196,7 +6281,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6196
6281
|
private fun configureTextStyle(view: TextView, style: JSONObject) {
|
|
6197
6282
|
view.setTextColor(color(style.optString("color"), defaultTextColor))
|
|
6198
6283
|
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, craftNativeFiniteFloat(style, "fontSize", 16f))
|
|
6199
|
-
val
|
|
6284
|
+
val fontWeight = style.opt("fontWeight") as? String
|
|
6285
|
+
val weight = when (fontWeight) {
|
|
6200
6286
|
"bold" -> 700
|
|
6201
6287
|
"100", "200", "300", "400", "500", "600", "700", "800", "900" -> style.optInt("fontWeight")
|
|
6202
6288
|
else -> 400
|
|
@@ -6221,12 +6307,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
6221
6307
|
Layout.JUSTIFICATION_MODE_NONE
|
|
6222
6308
|
}
|
|
6223
6309
|
}
|
|
6224
|
-
view.letterSpacing = style
|
|
6310
|
+
view.letterSpacing = craftNativeFiniteDouble(style, "letterSpacing", Double.NaN)
|
|
6225
6311
|
.takeIf { it.isFinite() }
|
|
6226
6312
|
?.let { if (view.textSize > 0) dp(it).toFloat() / view.textSize else 0f }
|
|
6227
6313
|
?: 0f
|
|
6228
6314
|
view.setLineSpacing(
|
|
6229
|
-
style
|
|
6315
|
+
craftNativeFiniteDouble(style, "lineHeight", Double.NaN).takeIf { it.isFinite() }?.let { dp(it) - view.textSize } ?: 0f,
|
|
6230
6316
|
1f
|
|
6231
6317
|
)
|
|
6232
6318
|
val shadowColor = style.optString("textShadowColor").takeIf { it.isNotBlank() }
|
|
@@ -6458,7 +6544,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6458
6544
|
if (type == "Icon") "image" else type.lowercase()
|
|
6459
6545
|
}
|
|
6460
6546
|
val state = props.optJSONObject("accessibilityState")
|
|
6461
|
-
val disabled = props.
|
|
6547
|
+
val disabled = craftNativeBoolean(props.opt("disabled")) || craftNativeBoolean(state?.opt("disabled"))
|
|
6462
6548
|
if (type !in setOf("Button", "Link", "Checkbox", "TextInput", "Picker", "DatePicker", "Modal", "Switch", "Slider", "ActivityIndicator")) {
|
|
6463
6549
|
view.isEnabled = !disabled
|
|
6464
6550
|
} else if (disabled) {
|
|
@@ -6466,7 +6552,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6466
6552
|
}
|
|
6467
6553
|
ViewCompat.setStateDescription(
|
|
6468
6554
|
view,
|
|
6469
|
-
if (type == "Checkbox" && props.
|
|
6555
|
+
if (type == "Checkbox" && (props.opt("indeterminate") as? Boolean) == true) "mixed" else null,
|
|
6470
6556
|
)
|
|
6471
6557
|
ViewCompat.setAccessibilityDelegate(view, object : AccessibilityDelegateCompat() {
|
|
6472
6558
|
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
|
@@ -6482,8 +6568,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6482
6568
|
info.roleDescription = role.takeIf { it !in setOf("view", "text", "textinput", "scrollview") }
|
|
6483
6569
|
info.hintText = host.tooltipText
|
|
6484
6570
|
info.isEnabled = host.isEnabled && !disabled
|
|
6485
|
-
info.isSelected = state?.
|
|
6486
|
-
info.isChecked = state?.
|
|
6571
|
+
info.isSelected = craftNativeBoolean(state?.opt("selected"))
|
|
6572
|
+
info.isChecked = craftNativeBoolean(state?.opt("checked"))
|
|
6487
6573
|
}
|
|
6488
6574
|
})
|
|
6489
6575
|
ViewCompat.setImportantForAccessibility(
|
|
@@ -6499,11 +6585,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
6499
6585
|
private fun color(raw: String, fallback: Int) = runCatching { craftNativeColor(raw) }.getOrDefault(fallback)
|
|
6500
6586
|
|
|
6501
6587
|
private fun inputTypeFor(props: JSONObject): Int {
|
|
6502
|
-
val base = if (props.
|
|
6588
|
+
val base = if (craftNativeBoolean(props.opt("multiline"))) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
6503
6589
|
else InputType.TYPE_CLASS_TEXT
|
|
6504
6590
|
// Text variations do not carry their input class or multiline flag.
|
|
6505
6591
|
// Preserve both when selecting an email, URL, or search keyboard.
|
|
6506
|
-
val keyboard = when (props.
|
|
6592
|
+
val keyboard = when (props.opt("keyboardType") as? String) {
|
|
6507
6593
|
"email-address" -> base or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
6508
6594
|
"url" -> base or InputType.TYPE_TEXT_VARIATION_URI
|
|
6509
6595
|
"numeric", "number-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
@@ -6513,21 +6599,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6513
6599
|
"visible-password" -> base or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
6514
6600
|
else -> base
|
|
6515
6601
|
}
|
|
6516
|
-
val capitalization = when (props.
|
|
6602
|
+
val capitalization = when (props.opt("autoCapitalize") as? String) {
|
|
6517
6603
|
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
6518
6604
|
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
6519
6605
|
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
6520
6606
|
else -> 0
|
|
6521
6607
|
}
|
|
6522
|
-
val correction = if (props.
|
|
6523
|
-
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
|
|
6524
6610
|
return keyboard or capitalization or correction or secure
|
|
6525
6611
|
}
|
|
6526
6612
|
|
|
6527
6613
|
/// The autofill hint for `textContentType` (iOS's names) or `autoComplete`
|
|
6528
6614
|
/// (HTML's), so a password manager fills the field.
|
|
6529
6615
|
private fun autofillHintFor(props: JSONObject): String? {
|
|
6530
|
-
val raw = props.
|
|
6616
|
+
val raw = (props.opt("textContentType") as? String)?.takeIf { it.isNotBlank() }
|
|
6617
|
+
?: (props.opt("autoComplete") as? String).orEmpty()
|
|
6531
6618
|
return when (raw.replace("-", "").replace("_", "").lowercase()) {
|
|
6532
6619
|
"username" -> View.AUTOFILL_HINT_USERNAME
|
|
6533
6620
|
"nickname" -> "personNickname"
|
|
@@ -6558,7 +6645,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6558
6645
|
}
|
|
6559
6646
|
}
|
|
6560
6647
|
|
|
6561
|
-
private fun imeOptionsFor(props: JSONObject): Int = when (props.
|
|
6648
|
+
private fun imeOptionsFor(props: JSONObject): Int = when (props.opt("returnKeyType") as? String) {
|
|
6562
6649
|
"done" -> EditorInfo.IME_ACTION_DONE
|
|
6563
6650
|
"go" -> EditorInfo.IME_ACTION_GO
|
|
6564
6651
|
"next" -> EditorInfo.IME_ACTION_NEXT
|