@craft-native/android 0.0.207 → 0.0.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -102,7 +102,8 @@ stx native compile --config ../screens/native.config.json --format bundle --outp
|
|
|
102
102
|
craft android build --native-bundle ../native-screen.js
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Native mode maps STX `View`, `SafeAreaView`, `
|
|
105
|
+
Native mode maps STX `View`, `SafeAreaView`, `KeyboardAvoidingView`, `StatusBar`,
|
|
106
|
+
`RefreshControl`, `Text`, `Button`, `Link`, `Icon`,
|
|
106
107
|
`Pressable`, `TouchableOpacity`, `TouchableHighlight`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Modal`, `Switch`, `Slider`, `ActivityIndicator`, `Image`, `ImageBackground`, `Video`, `Svg`, `G`, `SvgText`, `ScrollView`,
|
|
107
108
|
`Map`, `SectionList`, and `FlatList` to Android views. `Map` uses the Google
|
|
108
109
|
Maps SDK for trail polylines and marker taps; its overlays carry flat
|
|
@@ -199,7 +200,12 @@ JavaScriptSandbox process. Enable `enableLocalDatabase`,
|
|
|
199
200
|
`enableLocalNotifications`, and `enableDeepLinks` explicitly. Disabled or
|
|
200
201
|
malformed calls reject with stable capability/error codes, and in-flight calls
|
|
201
202
|
have a deadline and cancellation path. The broader browser bridge remains
|
|
202
|
-
specific to WebView mode.
|
|
203
|
+
specific to WebView mode. JavaScriptSandbox has no DOM `window`; Craft aliases
|
|
204
|
+
`globalThis.window` to `globalThis` before installing the native bridge so
|
|
205
|
+
browser-safe SDK calls such as `window.craft.dialog.actionSheet()` reach the
|
|
206
|
+
native capability host. This does not add `document` or DOM rendering; routes
|
|
207
|
+
that do not opt into `native` continue using the WebView renderer. Native
|
|
208
|
+
`TextInput` applies `placeholderTextColor`,
|
|
203
209
|
`selectionColor`, positive `maxLength`, `multiline`, and `numberOfLines` values.
|
|
204
210
|
Native views include
|
|
205
211
|
`ActivityIndicator` and expose
|
|
@@ -248,7 +254,7 @@ configured start path against `nativeScreens`; invalid native route tables are
|
|
|
248
254
|
rejected before a project is generated.
|
|
249
255
|
|
|
250
256
|
- a tab bar and a navigation bar (`craft.navigation.setOptions`: title, large
|
|
251
|
-
title, right buttons with badges, `tabBarHidden`), a stack per tab, Android's
|
|
257
|
+
title, right buttons with badges, `tabAccessory`, `tabBarHidden`), a stack per tab, Android's
|
|
252
258
|
Back key popping it; completed left/right swipes switch between tab roots
|
|
253
259
|
interactively, yield to a horizontal scroll view, and can be disabled per
|
|
254
260
|
screen with `craft.navigation.setOptions({ tabSwipeEnabled: false })`;
|
package/package.json
CHANGED
|
@@ -389,6 +389,10 @@ internal object CraftNativePrelude {
|
|
|
389
389
|
routePrelude: String,
|
|
390
390
|
): String = """
|
|
391
391
|
$routePrelude
|
|
392
|
+
// JavaScriptSandbox has no DOM window, but browser-safe SDK bridges
|
|
393
|
+
// address the host through `window.craft`. Keep native screens
|
|
394
|
+
// compatible without pretending a document exists.
|
|
395
|
+
if (typeof globalThis.window === 'undefined') globalThis.window = globalThis;
|
|
392
396
|
(function() {
|
|
393
397
|
var forward = null;
|
|
394
398
|
var queued = [];
|
|
@@ -567,7 +571,7 @@ internal object CraftNativePrelude {
|
|
|
567
571
|
* The bar above a screen (back, title, the screen's buttons) and the tab bar
|
|
568
572
|
* below it, in plain Android views. A screen sets its bar with
|
|
569
573
|
* `craft.navigation.setOptions` (title, largeTitle, hidden, rightButtons,
|
|
570
|
-
* tabBarHidden); the tabs come from the app's `tabs`.
|
|
574
|
+
* tabAccessory, tabBarHidden); the tabs come from the app's `tabs`.
|
|
571
575
|
*/
|
|
572
576
|
internal class CraftNativeChrome(context: Context, private val tabs: List<CraftNativeTab>) : LinearLayout(context) {
|
|
573
577
|
val content = FrameLayout(context)
|
|
@@ -578,6 +582,11 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
578
582
|
private val largeTitleView = TextView(context)
|
|
579
583
|
private val buttons = LinearLayout(context)
|
|
580
584
|
private val tabBar = LinearLayout(context)
|
|
585
|
+
private val tabRow = LinearLayout(context)
|
|
586
|
+
private val accessory = FrameLayout(context)
|
|
587
|
+
private val accessoryIcon = ImageView(context)
|
|
588
|
+
private val accessoryBadge = TextView(context)
|
|
589
|
+
private var accessorySpec: JSONObject? = null
|
|
581
590
|
private val tabViews = mutableListOf<Pair<TextView, TextView>>()
|
|
582
591
|
private val tabIconText = mutableMapOf<String, String>()
|
|
583
592
|
private val tabImageURLs = mutableMapOf<String, String>()
|
|
@@ -596,6 +605,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
596
605
|
var onBack: (() -> Unit)? = null
|
|
597
606
|
var onButton: ((String) -> Unit)? = null
|
|
598
607
|
var onTab: ((Int) -> Unit)? = null
|
|
608
|
+
var onAccessory: ((String) -> Unit)? = null
|
|
599
609
|
var onTabSwipeStart: ((Float, Float, Float) -> Boolean)? = null
|
|
600
610
|
var onTabSwipeMove: ((Float) -> Unit)? = null
|
|
601
611
|
var onTabSwipeEnd: ((Float, Boolean) -> Unit)? = null
|
|
@@ -660,8 +670,22 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
660
670
|
tabBar.addView(cell, LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f))
|
|
661
671
|
tabViews.add(icon to label)
|
|
662
672
|
}
|
|
663
|
-
|
|
664
|
-
|
|
673
|
+
tabRow.orientation = HORIZONTAL
|
|
674
|
+
tabRow.gravity = Gravity.CENTER_VERTICAL
|
|
675
|
+
tabRow.setPadding(dp(12), dp(4), dp(12), dp(4))
|
|
676
|
+
tabRow.addView(tabBar, LayoutParams(0, dp(64), 1f))
|
|
677
|
+
accessory.addView(accessoryIcon, FrameLayout.LayoutParams(dp(26), dp(26), Gravity.CENTER))
|
|
678
|
+
accessoryBadge.textSize = 10f
|
|
679
|
+
accessoryBadge.gravity = Gravity.CENTER
|
|
680
|
+
accessoryBadge.setTextColor(Color.WHITE)
|
|
681
|
+
accessoryBadge.setPadding(dp(4), 0, dp(4), 0)
|
|
682
|
+
accessoryBadge.background = GradientDrawable().apply { setColor(Color.RED); cornerRadius = dp(9).toFloat() }
|
|
683
|
+
accessory.addView(accessoryBadge, FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp(18), Gravity.TOP or Gravity.END))
|
|
684
|
+
accessory.setOnClickListener { accessorySpec?.optString("path")?.let { onAccessory?.invoke(it) } }
|
|
685
|
+
accessory.visibility = View.GONE
|
|
686
|
+
tabRow.addView(accessory, LayoutParams(dp(64), dp(64)).apply { marginStart = dp(8) })
|
|
687
|
+
addView(tabRow, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(72)))
|
|
688
|
+
tabRow.visibility = if (tabs.isEmpty()) View.GONE else View.VISIBLE
|
|
665
689
|
refreshColors()
|
|
666
690
|
}
|
|
667
691
|
|
|
@@ -671,7 +695,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
671
695
|
val overHorizontalScroll = onTabSwipeShouldDefer?.invoke(event.x, event.y) == true
|
|
672
696
|
horizontalScrollDeferred = overHorizontalScroll
|
|
673
697
|
if (overHorizontalScroll) parent?.requestDisallowInterceptTouchEvent(true)
|
|
674
|
-
swipeTracking = tabs.size > 1 && tabSwipeEnabled && !overHorizontalScroll &&
|
|
698
|
+
swipeTracking = tabs.size > 1 && tabSwipeEnabled && !overHorizontalScroll && tabRow.visibility == View.VISIBLE && event.y < tabRow.top
|
|
675
699
|
swipeIntercepted = false
|
|
676
700
|
swipeDownX = event.x
|
|
677
701
|
swipeDownY = event.y
|
|
@@ -742,7 +766,17 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
742
766
|
buttons.addView(navButton(button))
|
|
743
767
|
}
|
|
744
768
|
val tabBarHidden = options.optBoolean("tabBarHidden", false) || depth > 0 && options.optBoolean("tabBarHiddenWhenPushed", false)
|
|
745
|
-
|
|
769
|
+
tabRow.visibility = if (tabs.isEmpty() || tabBarHidden) View.GONE else View.VISIBLE
|
|
770
|
+
accessorySpec = options.optJSONObject("tabAccessory")?.takeIf {
|
|
771
|
+
val path = it.optString("path")
|
|
772
|
+
path.startsWith("/") && !path.startsWith("//") && it.has("title")
|
|
773
|
+
}
|
|
774
|
+
accessory.visibility = if (accessorySpec == null) View.GONE else View.VISIBLE
|
|
775
|
+
accessorySpec?.let {
|
|
776
|
+
accessory.contentDescription = it.optString("accessibilityLabel").ifBlank { it.optString("title") } + it.optString("badge").takeIf { badge -> badge.isNotBlank() && badge != "null" }?.let { badge -> ", $badge" }.orEmpty()
|
|
777
|
+
accessoryBadge.text = it.optString("badge").takeUnless { badge -> badge == "null" }.orEmpty().take(8)
|
|
778
|
+
accessoryBadge.visibility = if (accessoryBadge.text.isEmpty()) View.GONE else View.VISIBLE
|
|
779
|
+
}
|
|
746
780
|
tabSwipeEnabled = options.optBoolean("tabSwipeEnabled", true)
|
|
747
781
|
refreshColors()
|
|
748
782
|
}
|
|
@@ -801,13 +835,10 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
801
835
|
titleView.setTextColor(text)
|
|
802
836
|
largeTitleView.setTextColor(text)
|
|
803
837
|
backButton.imageTintList = ColorStateList.valueOf(accent)
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
divider,
|
|
809
|
-
GradientDrawable().apply { setColor(if (dark) Color.parseColor("#121212") else Color.parseColor("#F9F9F9")) },
|
|
810
|
-
)).apply { setLayerInset(1, 0, maxOf(1, (density / 2).toInt()), 0, 0) }
|
|
838
|
+
val surface = if (dark) Color.parseColor("#1C1C1E") else Color.parseColor("#F9F9F9")
|
|
839
|
+
tabBar.background = GradientDrawable().apply { setColor(surface); cornerRadius = dp(32).toFloat() }
|
|
840
|
+
accessory.background = GradientDrawable().apply { setColor(surface); cornerRadius = dp(32).toFloat() }
|
|
841
|
+
accessorySpec?.let { accessoryIcon.setImageDrawable(CraftNativeIcons.drawable(context, null, it.optString("symbol"), text)) }
|
|
811
842
|
tabViews.forEachIndexed { index, (icon, label) ->
|
|
812
843
|
val color = if (index == selectedTab) accent else muted
|
|
813
844
|
val number = tabIconText[tabs[index].id]
|
|
@@ -256,16 +256,9 @@ private data class CraftNativeLayoutStyle(
|
|
|
256
256
|
aspectRatio = decimal("aspectRatio")?.takeIf { it.isFinite() && it > 0.0 },
|
|
257
257
|
minWidth = length("minWidth"), maxWidth = length("maxWidth"),
|
|
258
258
|
minHeight = length("minHeight"), maxHeight = length("maxHeight"),
|
|
259
|
-
position = raw.opt("position")
|
|
260
|
-
?.takeUnless { it == JSONObject.NULL }
|
|
261
|
-
?.toString()
|
|
262
|
-
?.takeIf { it.isNotBlank() }
|
|
263
|
-
?: "relative",
|
|
259
|
+
position = craftNativeOptionalString(raw.opt("position")) ?: "relative",
|
|
264
260
|
top = number("top"), right = number("right"), bottom = number("bottom"), left = number("left"),
|
|
265
|
-
alignSelf = raw.opt("alignSelf")
|
|
266
|
-
?.takeUnless { it == JSONObject.NULL }
|
|
267
|
-
?.toString()
|
|
268
|
-
?.takeIf { it.isNotBlank() && it != "auto" },
|
|
261
|
+
alignSelf = craftNativeOptionalString(raw.opt("alignSelf"))?.takeUnless { it == "auto" },
|
|
269
262
|
flexGrow = decimal("flexGrow") ?: flex?.coerceAtLeast(0.0) ?: 0.0,
|
|
270
263
|
flexShrink = decimal("flexShrink") ?: flex?.let { if (it != 0.0) 1.0 else 0.0 },
|
|
271
264
|
flexBasis = length("flexBasis")
|
|
@@ -383,11 +376,8 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
383
376
|
fun merge(node: JSONObject): SvgPaint {
|
|
384
377
|
val props = node.optJSONObject("props") ?: JSONObject()
|
|
385
378
|
val style = node.optJSONObject("style") ?: JSONObject()
|
|
386
|
-
fun value(name: String): String? =
|
|
387
|
-
|
|
388
|
-
props.has(name) && !props.isNull(name) -> props.optString(name)
|
|
389
|
-
else -> null
|
|
390
|
-
}
|
|
379
|
+
fun value(name: String): String? = craftNativeOptionalString(style.opt(name))
|
|
380
|
+
?: craftNativeOptionalString(props.opt(name))
|
|
391
381
|
fun raw(name: String): Any? = when {
|
|
392
382
|
style.has(name) && !style.isNull(name) -> style.opt(name)
|
|
393
383
|
props.has(name) && !props.isNull(name) -> props.opt(name)
|
|
@@ -486,7 +476,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
486
476
|
for (index in 0 until children.length()) {
|
|
487
477
|
val node = children.optJSONObject(index) ?: continue
|
|
488
478
|
val style = node.optJSONObject("style") ?: JSONObject()
|
|
489
|
-
if (style.
|
|
479
|
+
if (craftNativeOptionalString(style.opt("display")) == "none") continue
|
|
490
480
|
val paint = inherited.merge(node)
|
|
491
481
|
when (node.optString("type")) {
|
|
492
482
|
"G" -> drawNodes(canvas, node.optJSONArray("children") ?: JSONArray(), paint)
|
|
@@ -582,7 +572,7 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
582
572
|
}
|
|
583
573
|
var x = craftNativeNumber(props.opt("x"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
584
574
|
val y = craftNativeNumber(props.opt("y"))?.toFloat()?.takeIf { it.isFinite() } ?: 0f
|
|
585
|
-
when (props.
|
|
575
|
+
when (craftNativeOptionalString(props.opt("textAnchor"))) {
|
|
586
576
|
"middle" -> x -= paint.measureText(text) / 2f
|
|
587
577
|
"end" -> x -= paint.measureText(text)
|
|
588
578
|
}
|
|
@@ -728,12 +718,15 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
728
718
|
val explicitRegion = region(pendingProps.optJSONObject("region"))
|
|
729
719
|
val initialRegion = region(pendingProps.optJSONObject("initialRegion"))
|
|
730
720
|
if (explicitRegion != null) {
|
|
731
|
-
|
|
721
|
+
// A controlled region is an exact camera request. The padding
|
|
722
|
+
// used for fitToBounds is framing chrome, not part of the region
|
|
723
|
+
// contract, and would otherwise zoom every Android screen out.
|
|
724
|
+
moveCamera(explicitRegion, padding = 0)
|
|
732
725
|
initialCameraKey = null
|
|
733
726
|
} else if (initialRegion != null) {
|
|
734
727
|
val key = "region:${pendingProps.optJSONObject("initialRegion")}"
|
|
735
728
|
if (initialCameraKey != key) {
|
|
736
|
-
moveCamera(initialRegion)
|
|
729
|
+
moveCamera(initialRegion, padding = 0)
|
|
737
730
|
initialCameraKey = key
|
|
738
731
|
}
|
|
739
732
|
} else if (craftNativeBoolean(pendingProps.opt("fitToBounds"))) {
|
|
@@ -755,9 +748,9 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
755
748
|
}
|
|
756
749
|
}
|
|
757
750
|
|
|
758
|
-
private fun moveCamera(bounds: LatLngBounds) {
|
|
751
|
+
private fun moveCamera(bounds: LatLngBounds, padding: Int = 24) {
|
|
759
752
|
post {
|
|
760
|
-
if (width > 0 && height > 0) googleMap?.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds,
|
|
753
|
+
if (width > 0 && height > 0) googleMap?.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding))
|
|
761
754
|
}
|
|
762
755
|
}
|
|
763
756
|
|
|
@@ -1027,14 +1020,15 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1027
1020
|
}
|
|
1028
1021
|
|
|
1029
1022
|
fun configure(style: JSONObject, density: Float) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1023
|
+
val flexDirection = craftNativeOptionalString(style.opt("flexDirection")).orEmpty()
|
|
1024
|
+
orientation = if (flexDirection in setOf("row", "row-reverse")) HORIZONTAL else VERTICAL
|
|
1025
|
+
reverse = flexDirection.endsWith("-reverse")
|
|
1026
|
+
val flexWrap = craftNativeOptionalString(style.opt("flexWrap")).orEmpty()
|
|
1027
|
+
wrap = flexWrap == "wrap" || flexWrap == "wrap-reverse" || craftNativeBoolean(style.opt("flexWrap"))
|
|
1034
1028
|
wrapReverse = flexWrap == "wrap-reverse"
|
|
1035
|
-
alignItems = style.
|
|
1036
|
-
alignContent = style.
|
|
1037
|
-
justifyContent = style.
|
|
1029
|
+
alignItems = craftNativeOptionalString(style.opt("alignItems")) ?: "stretch"
|
|
1030
|
+
alignContent = craftNativeOptionalString(style.opt("alignContent")) ?: "flex-start"
|
|
1031
|
+
justifyContent = craftNativeOptionalString(style.opt("justifyContent")) ?: "flex-start"
|
|
1038
1032
|
fun spacing(name: String, fallback: Float): Float {
|
|
1039
1033
|
val value = craftNativeNumber(style.opt(name)) ?: Double.NaN
|
|
1040
1034
|
val pixels = value * density
|
|
@@ -1044,7 +1038,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
1044
1038
|
val gap = spacing("gap", 0f)
|
|
1045
1039
|
rowGap = spacing("rowGap", gap)
|
|
1046
1040
|
columnGap = spacing("columnGap", gap)
|
|
1047
|
-
grid = style.
|
|
1041
|
+
grid = craftNativeOptionalString(style.opt("display")) == "grid"
|
|
1048
1042
|
gridColumns = craftNativeGridColumnCount(
|
|
1049
1043
|
if (style.has("gridTemplateColumns")) style.opt("gridTemplateColumns") else style.opt("gridColumns")
|
|
1050
1044
|
)
|
|
@@ -2095,7 +2089,7 @@ private class CraftNativeKeyboardAvoidingView(context: Context) : FrameLayout(co
|
|
|
2095
2089
|
}
|
|
2096
2090
|
|
|
2097
2091
|
fun configure(props: JSONObject, style: JSONObject, density: Float) {
|
|
2098
|
-
behavior = props.
|
|
2092
|
+
behavior = craftNativeOptionalString(props.opt("behavior"))?.takeIf { it in setOf("height", "position", "padding") } ?: "padding"
|
|
2099
2093
|
val authoredOffset = craftNativeFiniteDouble(props, "keyboardVerticalOffset", 0.0)
|
|
2100
2094
|
val pixelOffset = (authoredOffset * density.toDouble()).takeIf { it.isFinite() } ?: 0.0
|
|
2101
2095
|
keyboardVerticalOffset = pixelOffset.roundToInt()
|
|
@@ -3958,6 +3952,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3958
3952
|
val bars = CraftNativeChrome(this, routes.tabs)
|
|
3959
3953
|
bars.layoutParams = FrameLayout.LayoutParams(-1, -1)
|
|
3960
3954
|
bars.onBack = { onBackPressedDispatcherCompat() }
|
|
3955
|
+
bars.onAccessory = { path -> navigateOpen(path) }
|
|
3961
3956
|
bars.onButton = { id -> visibleScreen?.takeIf { it.active }?.let { send(it, "NAV_BUTTON", JSONObject().put("id", id)) } }
|
|
3962
3957
|
bars.onTab = { index ->
|
|
3963
3958
|
haptic()
|
|
@@ -4945,7 +4940,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4945
4940
|
"Text" -> ((previous as? TextView)?.takeUnless { it is Button || it is EditText } ?: TextView(this)).apply {
|
|
4946
4941
|
includeFontPadding = false
|
|
4947
4942
|
maxLines = craftNativeInt(props.opt("numberOfLines"), 0).takeIf { it > 0 } ?: Int.MAX_VALUE
|
|
4948
|
-
ellipsize = when (props.
|
|
4943
|
+
ellipsize = when (craftNativeOptionalString(props.opt("ellipsizeMode"))) {
|
|
4949
4944
|
"head" -> TextUtils.TruncateAt.START
|
|
4950
4945
|
"middle" -> TextUtils.TruncateAt.MIDDLE
|
|
4951
4946
|
"tail" -> TextUtils.TruncateAt.END
|
|
@@ -5248,7 +5243,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5248
5243
|
// from the previous hidden state when it patches props.
|
|
5249
5244
|
craftNativeBoolean(props.opt("visible"))
|
|
5250
5245
|
} else {
|
|
5251
|
-
style.has("display") && style.
|
|
5246
|
+
style.has("display") && craftNativeOptionalString(style.opt("display")) != "none"
|
|
5252
5247
|
}
|
|
5253
5248
|
val wasVisible = screen.modalVisibility[this]
|
|
5254
5249
|
screen.modalVisibility[this] = visible
|
|
@@ -5480,7 +5475,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5480
5475
|
drawing = node
|
|
5481
5476
|
}
|
|
5482
5477
|
"ScrollView", "SectionList" -> (previous as? CraftNativeScrollView ?: CraftNativeScrollView(this)).apply {
|
|
5483
|
-
val horizontal = craftNativeBoolean(props.opt("horizontal")) || style.
|
|
5478
|
+
val horizontal = craftNativeBoolean(props.opt("horizontal")) || craftNativeOptionalString(style.opt("flexDirection")) in setOf("row", "row-reverse")
|
|
5484
5479
|
setHorizontal(horizontal)
|
|
5485
5480
|
setNativeNestedScrollingEnabled(true)
|
|
5486
5481
|
setScrollIndicators(
|
|
@@ -5488,7 +5483,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5488
5483
|
craftNativeBoolean(props.opt("showsHorizontalScrollIndicator"), true),
|
|
5489
5484
|
)
|
|
5490
5485
|
setScrollEnabled(craftNativeBoolean(props.opt("scrollEnabled"), true))
|
|
5491
|
-
setKeyboardShouldPersistTaps(props.
|
|
5486
|
+
setKeyboardShouldPersistTaps(craftNativeOptionalString(props.opt("keyboardShouldPersistTaps")) ?: "never")
|
|
5492
5487
|
setBounces(craftNativeBoolean(props.opt("bounces"), true))
|
|
5493
5488
|
setAlwaysBounce(
|
|
5494
5489
|
craftNativeBoolean(props.opt("alwaysBounceVertical"), !horizontal),
|
|
@@ -5550,7 +5545,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5550
5545
|
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
5551
5546
|
}
|
|
5552
5547
|
},
|
|
5553
|
-
keyboardDismissMode = props.
|
|
5548
|
+
keyboardDismissMode = craftNativeOptionalString(props.opt("keyboardDismissMode")).orEmpty(),
|
|
5554
5549
|
dismissKeyboard = {
|
|
5555
5550
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
5556
5551
|
?.hideSoftInputFromWindow(nativeScroll.windowToken, 0)
|
|
@@ -5587,9 +5582,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
5587
5582
|
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
5588
5583
|
}, craftNativeBoolean(propRefresh.opt("refreshing"), craftNativeBoolean(props.opt("refreshing"))))
|
|
5589
5584
|
setRefreshAppearance(propRefresh, refreshTintFallback(screen))
|
|
5590
|
-
nativeList.setKeyboardShouldPersistTaps(props.
|
|
5585
|
+
nativeList.setKeyboardShouldPersistTaps(craftNativeOptionalString(props.opt("keyboardShouldPersistTaps")) ?: "never")
|
|
5591
5586
|
nativeList.setContentContainerStyle(props.optJSONObject("contentContainerStyle"), resources.displayMetrics.density)
|
|
5592
|
-
nativeList.setKeyboardDismissMode(props.
|
|
5587
|
+
nativeList.setKeyboardDismissMode(craftNativeOptionalString(props.opt("keyboardDismissMode")).orEmpty()) {
|
|
5593
5588
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
5594
5589
|
?.hideSoftInputFromWindow(windowToken, 0)
|
|
5595
5590
|
}
|
|
@@ -5598,6 +5593,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5598
5593
|
"View", "SafeAreaView" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
5599
5594
|
configureContainer(this, style)
|
|
5600
5595
|
if (type == "SafeAreaView") setSafeAreaEdges(props.optJSONArray("edges"))
|
|
5596
|
+
else setWindowSafeInsets(0, 0, 0, 0)
|
|
5601
5597
|
reconcileChildren(screen, this, children, identity, next, style, previousControls)
|
|
5602
5598
|
}
|
|
5603
5599
|
"KeyboardAvoidingView" -> (previous as? CraftNativeKeyboardAvoidingView ?: CraftNativeKeyboardAvoidingView(this)).apply {
|
|
@@ -5744,7 +5740,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5744
5740
|
if (type == "ActivityIndicator") {
|
|
5745
5741
|
val animating = craftNativeBoolean(props.opt("animating"), true)
|
|
5746
5742
|
val hidesWhenStopped = craftNativeBoolean(props.opt("hidesWhenStopped"), true)
|
|
5747
|
-
control.visibility = if (style.
|
|
5743
|
+
control.visibility = if (craftNativeOptionalString(style.opt("display")) == "none" || (!animating && hidesWhenStopped)) {
|
|
5748
5744
|
View.GONE
|
|
5749
5745
|
} else {
|
|
5750
5746
|
View.VISIBLE
|
|
@@ -5865,7 +5861,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5865
5861
|
layout.setChildStyle(childView, child.optJSONObject("style") ?: JSONObject(), resources.displayMetrics.density)
|
|
5866
5862
|
}
|
|
5867
5863
|
}
|
|
5868
|
-
if (style.
|
|
5864
|
+
if (craftNativeOptionalString(style.opt("flexDirection"))?.endsWith("-reverse") == true) desired.reverse()
|
|
5869
5865
|
val desiredSet = desired.toSet()
|
|
5870
5866
|
for ((index, childView) in desired.withIndex()) {
|
|
5871
5867
|
if (layout.getChildAt(index) !== childView) {
|
|
@@ -5992,7 +5988,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5992
5988
|
}
|
|
5993
5989
|
applyTransform(view, style.optJSONArray("transform"))
|
|
5994
5990
|
if (view !is CraftNativeModalView) {
|
|
5995
|
-
view.visibility = if (style.
|
|
5991
|
+
view.visibility = if (craftNativeOptionalString(style.opt("display")) == "none") View.GONE else View.VISIBLE
|
|
5996
5992
|
}
|
|
5997
5993
|
view.elevation = dp(craftNativeNonNegativeDouble(style, "elevation", 0.0)).toFloat()
|
|
5998
5994
|
view.z = craftNativeFiniteFloat(style, "zIndex", 0f)
|
|
@@ -6001,13 +5997,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
6001
5997
|
// background paints over the decoded picture (only rounded corners
|
|
6002
5998
|
// showed it). Keep the outline/borders, with a transparent fill.
|
|
6003
5999
|
val backgroundColor = if (view is CraftNativeVideoView) Color.TRANSPARENT
|
|
6004
|
-
else color(style.
|
|
6000
|
+
else color(craftNativeOptionalString(style.opt("backgroundColor")).orEmpty(), Color.TRANSPARENT)
|
|
6005
6001
|
val border = craftNativeNonNegativeDouble(style, "borderWidth", 0.0)
|
|
6006
6002
|
val radius = craftNativeNonNegativeDouble(style, "borderRadius", 0.0)
|
|
6007
|
-
val gradientDirection = style.
|
|
6008
|
-
val gradientFrom = style.
|
|
6009
|
-
val gradientVia = style.
|
|
6010
|
-
val gradientTo = style.
|
|
6003
|
+
val gradientDirection = craftNativeOptionalString(style.opt("gradientDirection"))
|
|
6004
|
+
val gradientFrom = craftNativeOptionalString(style.opt("gradientFrom"))
|
|
6005
|
+
val gradientVia = craftNativeOptionalString(style.opt("gradientVia"))
|
|
6006
|
+
val gradientTo = craftNativeOptionalString(style.opt("gradientTo"))
|
|
6011
6007
|
val gradientColors = if (gradientDirection != null && gradientFrom != null && gradientTo != null) {
|
|
6012
6008
|
listOfNotNull(
|
|
6013
6009
|
color(gradientFrom, Color.TRANSPARENT),
|
|
@@ -6025,7 +6021,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6025
6021
|
listOf(radius, radius)
|
|
6026
6022
|
}.toFloatArray()
|
|
6027
6023
|
val sideColors = listOf("borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor")
|
|
6028
|
-
.map { color(style.
|
|
6024
|
+
.map { color(craftNativeOptionalString(style.opt(it)) ?: craftNativeOptionalString(style.opt("borderColor")).orEmpty(), Color.TRANSPARENT) }
|
|
6029
6025
|
.toIntArray()
|
|
6030
6026
|
val defaultBackground = if (defaultBackgrounds.containsKey(view)) {
|
|
6031
6027
|
defaultBackgrounds[view]
|
|
@@ -6039,7 +6035,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6039
6035
|
cornerRadii,
|
|
6040
6036
|
sideWidths,
|
|
6041
6037
|
sideColors,
|
|
6042
|
-
style.
|
|
6038
|
+
craftNativeOptionalString(style.opt("borderStyle"))?.takeIf { it == "dashed" || it == "dotted" },
|
|
6043
6039
|
gradientDirection,
|
|
6044
6040
|
gradientColors,
|
|
6045
6041
|
)
|
|
@@ -6051,8 +6047,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
6051
6047
|
} else setColor(backgroundColor)
|
|
6052
6048
|
if (hasCornerRadii) setCornerRadii(cornerRadii) else cornerRadius = dp(radius).toFloat()
|
|
6053
6049
|
if (border > 0) {
|
|
6054
|
-
val borderColor = color(style.
|
|
6055
|
-
when (style.
|
|
6050
|
+
val borderColor = color(craftNativeOptionalString(style.opt("borderColor")).orEmpty(), Color.TRANSPARENT)
|
|
6051
|
+
when (craftNativeOptionalString(style.opt("borderStyle"))) {
|
|
6056
6052
|
"dashed" -> setStroke(dp(border), borderColor, dp(border * 3).toFloat(), dp(border * 2).toFloat())
|
|
6057
6053
|
"dotted" -> setStroke(dp(border), borderColor, dp(border).toFloat(), dp(border * 1.5).toFloat())
|
|
6058
6054
|
else -> setStroke(dp(border), borderColor)
|
|
@@ -6065,13 +6061,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
6065
6061
|
// and the web.
|
|
6066
6062
|
null
|
|
6067
6063
|
} else defaultBackground
|
|
6068
|
-
|
|
6069
|
-
|
|
6064
|
+
val overflow = craftNativeOptionalString(style.opt("overflow"))
|
|
6065
|
+
view.clipToOutline = overflow == "hidden"
|
|
6066
|
+
if (view is ViewGroup) view.clipChildren = overflow == "hidden"
|
|
6070
6067
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
6071
6068
|
(style.has("shadowColor") || style.has("shadowOpacity") || style.has("shadowRadius"))
|
|
6072
6069
|
) {
|
|
6073
6070
|
val shadowOpacity = craftNativeFiniteFloat(style, "shadowOpacity", 1f).coerceIn(0f, 1f)
|
|
6074
|
-
val shadow = color(style.
|
|
6071
|
+
val shadow = color(craftNativeOptionalString(style.opt("shadowColor")).orEmpty(), Color.BLACK)
|
|
6075
6072
|
val alphaShadow = Color.argb((Color.alpha(shadow) * shadowOpacity).roundToInt(), Color.red(shadow), Color.green(shadow), Color.blue(shadow))
|
|
6076
6073
|
view.outlineAmbientShadowColor = alphaShadow
|
|
6077
6074
|
view.outlineSpotShadowColor = alphaShadow
|
|
@@ -6169,7 +6166,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6169
6166
|
}
|
|
6170
6167
|
|
|
6171
6168
|
private fun configureText(view: TextView, value: String, style: JSONObject) {
|
|
6172
|
-
view.text = when (style.
|
|
6169
|
+
view.text = when (craftNativeOptionalString(style.opt("textTransform"))) {
|
|
6173
6170
|
"uppercase" -> value.uppercase()
|
|
6174
6171
|
"lowercase" -> value.lowercase()
|
|
6175
6172
|
"capitalize" -> value.split(' ').joinToString(" ") { word -> word.replaceFirstChar(Char::uppercase) }
|
|
@@ -6279,21 +6276,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
6279
6276
|
}
|
|
6280
6277
|
|
|
6281
6278
|
private fun configureTextStyle(view: TextView, style: JSONObject) {
|
|
6282
|
-
view.setTextColor(color(style.
|
|
6279
|
+
view.setTextColor(color(craftNativeOptionalString(style.opt("color")).orEmpty(), defaultTextColor))
|
|
6283
6280
|
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, craftNativeFiniteFloat(style, "fontSize", 16f))
|
|
6284
|
-
val fontWeight = style.opt("fontWeight")
|
|
6281
|
+
val fontWeight = craftNativeFontWeight(style.opt("fontWeight"))
|
|
6285
6282
|
val weight = when (fontWeight) {
|
|
6286
6283
|
"bold" -> 700
|
|
6287
6284
|
"100", "200", "300", "400", "500", "600", "700", "800", "900" -> style.optInt("fontWeight")
|
|
6288
6285
|
else -> 400
|
|
6289
6286
|
}
|
|
6290
|
-
val italic = style.
|
|
6291
|
-
val family = style.
|
|
6287
|
+
val italic = craftNativeOptionalString(style.opt("fontStyle")) == "italic"
|
|
6288
|
+
val family = craftNativeOptionalString(style.opt("fontFamily"))?.takeIf { it.isNotBlank() }
|
|
6292
6289
|
view.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
6293
6290
|
Typeface.create(Typeface.create(family, Typeface.NORMAL), weight, italic)
|
|
6294
6291
|
} else Typeface.create(family, (if (weight >= 600) Typeface.BOLD else Typeface.NORMAL) or
|
|
6295
6292
|
(if (italic) Typeface.ITALIC else Typeface.NORMAL))
|
|
6296
|
-
|
|
6293
|
+
val textAlign = craftNativeOptionalString(style.opt("textAlign"))
|
|
6294
|
+
view.textAlignment = when (textAlign) {
|
|
6297
6295
|
"left" -> View.TEXT_ALIGNMENT_TEXT_START
|
|
6298
6296
|
"right" -> View.TEXT_ALIGNMENT_TEXT_END
|
|
6299
6297
|
"center" -> View.TEXT_ALIGNMENT_CENTER
|
|
@@ -6301,7 +6299,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6301
6299
|
else -> View.TEXT_ALIGNMENT_INHERIT
|
|
6302
6300
|
}
|
|
6303
6301
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
6304
|
-
view.justificationMode = if (
|
|
6302
|
+
view.justificationMode = if (textAlign == "justify") {
|
|
6305
6303
|
Layout.JUSTIFICATION_MODE_INTER_WORD
|
|
6306
6304
|
} else {
|
|
6307
6305
|
Layout.JUSTIFICATION_MODE_NONE
|
|
@@ -6315,7 +6313,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
6315
6313
|
craftNativeFiniteDouble(style, "lineHeight", Double.NaN).takeIf { it.isFinite() }?.let { dp(it) - view.textSize } ?: 0f,
|
|
6316
6314
|
1f
|
|
6317
6315
|
)
|
|
6318
|
-
val shadowColor = style.
|
|
6316
|
+
val shadowColor = craftNativeOptionalString(style.opt("textShadowColor"))?.takeIf { it.isNotBlank() }
|
|
6319
6317
|
if (shadowColor == null) {
|
|
6320
6318
|
view.paint.clearShadowLayer()
|
|
6321
6319
|
} else {
|
|
@@ -6332,16 +6330,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
6332
6330
|
}
|
|
6333
6331
|
|
|
6334
6332
|
private fun applyTextDecoration(view: TextView, style: JSONObject) {
|
|
6335
|
-
val line = style.
|
|
6333
|
+
val line = craftNativeOptionalString(style.opt("textDecorationLine")).orEmpty()
|
|
6336
6334
|
if (view is EditText) {
|
|
6337
6335
|
// A field's own text, spanned in place: replacing it would move
|
|
6338
6336
|
// the caret to the start and tell the screen the text changed.
|
|
6339
6337
|
val editable = view.text ?: return
|
|
6340
6338
|
editable.getSpans(0, editable.length, CraftTextDecorationSpan::class.java).forEach(editable::removeSpan)
|
|
6341
6339
|
if (line.isBlank() || line == "none" || editable.isEmpty()) return
|
|
6342
|
-
val fieldColor = style.
|
|
6340
|
+
val fieldColor = craftNativeOptionalString(style.opt("textDecorationColor"))?.takeIf { it.isNotBlank() }?.let { color(it, Color.TRANSPARENT) }
|
|
6343
6341
|
editable.setSpan(
|
|
6344
|
-
CraftTextDecorationSpan(line, style.
|
|
6342
|
+
CraftTextDecorationSpan(line, craftNativeOptionalString(style.opt("textDecorationStyle")).orEmpty().ifBlank { "solid" }, fieldColor),
|
|
6345
6343
|
0,
|
|
6346
6344
|
editable.length,
|
|
6347
6345
|
Spannable.SPAN_INCLUSIVE_INCLUSIVE,
|
|
@@ -6353,14 +6351,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
6353
6351
|
if (view.text is Spannable) view.text = text
|
|
6354
6352
|
return
|
|
6355
6353
|
}
|
|
6356
|
-
val decorationColor = style.
|
|
6354
|
+
val decorationColor = craftNativeOptionalString(style.opt("textDecorationColor")).orEmpty()
|
|
6357
6355
|
.takeIf { it.isNotBlank() }
|
|
6358
6356
|
?.let { color(it, Color.TRANSPARENT) }
|
|
6359
6357
|
val decorated = SpannableString(text)
|
|
6360
6358
|
decorated.setSpan(
|
|
6361
6359
|
CraftTextDecorationSpan(
|
|
6362
6360
|
line,
|
|
6363
|
-
style.
|
|
6361
|
+
craftNativeOptionalString(style.opt("textDecorationStyle")).orEmpty().ifBlank { "solid" },
|
|
6364
6362
|
decorationColor,
|
|
6365
6363
|
),
|
|
6366
6364
|
0,
|
|
@@ -6568,8 +6566,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
6568
6566
|
info.roleDescription = role.takeIf { it !in setOf("view", "text", "textinput", "scrollview") }
|
|
6569
6567
|
info.hintText = host.tooltipText
|
|
6570
6568
|
info.isEnabled = host.isEnabled && !disabled
|
|
6571
|
-
|
|
6572
|
-
|
|
6569
|
+
// Keep the native widget state when no accessibilityState was
|
|
6570
|
+
// authored. Overwriting it with the strict false fallback
|
|
6571
|
+
// made a checked Switch/CheckBox announce as unchecked unless
|
|
6572
|
+
// the screen redundantly mirrored its value into metadata.
|
|
6573
|
+
if (state?.has("selected") == true) {
|
|
6574
|
+
info.isSelected = craftNativeBoolean(state.opt("selected"))
|
|
6575
|
+
}
|
|
6576
|
+
if (state?.has("checked") == true) {
|
|
6577
|
+
info.isChecked = craftNativeBoolean(state.opt("checked"))
|
|
6578
|
+
}
|
|
6573
6579
|
}
|
|
6574
6580
|
})
|
|
6575
6581
|
ViewCompat.setImportantForAccessibility(
|