@onekeyfe/react-native-native-list 3.0.115 → 3.0.116
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/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +30 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +280 -32
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +41 -1
- package/ios/NativeListCell.swift +167 -18
- package/ios/RNCNativeListView.swift +4 -0
- package/lib/module/validation.js +17 -1
- package/lib/typescript/src/models.d.ts +26 -0
- package/package.json +3 -3
- package/src/models.ts +23 -0
- package/src/validation.ts +44 -0
|
@@ -10,6 +10,8 @@ import com.facebook.react.uimanager.ThemedReactContext
|
|
|
10
10
|
import org.json.JSONObject
|
|
11
11
|
import java.util.Collections
|
|
12
12
|
import java.util.WeakHashMap
|
|
13
|
+
import kotlin.math.ceil
|
|
14
|
+
import kotlin.math.roundToInt
|
|
13
15
|
|
|
14
16
|
internal class NativeListAdapter(
|
|
15
17
|
private val context: ThemedReactContext,
|
|
@@ -39,6 +41,8 @@ internal class NativeListAdapter(
|
|
|
39
41
|
)
|
|
40
42
|
val currentList: List<NativeListItem>
|
|
41
43
|
get() = differ.currentList
|
|
44
|
+
private var marketSourceItems: List<NativeListItem>? = null
|
|
45
|
+
private var marketSourceEdges = DoubleArray(0)
|
|
42
46
|
private val createdRows = Collections.newSetFromMap(
|
|
43
47
|
WeakHashMap<NativeListRowView, Boolean>(),
|
|
44
48
|
)
|
|
@@ -122,6 +126,30 @@ internal class NativeListAdapter(
|
|
|
122
126
|
fun itemAt(position: Int): NativeListItem? =
|
|
123
127
|
reorderItems?.getOrNull(position) ?: differ.currentList.getOrNull(position)
|
|
124
128
|
|
|
129
|
+
// OneKey patch: Yoga rounds badge dimensions from unrounded cumulative row edges.
|
|
130
|
+
fun marketSourceEdgePx(position: Int): Double {
|
|
131
|
+
val items = reorderItems ?: differ.currentList
|
|
132
|
+
if (marketSourceItems !== items) {
|
|
133
|
+
val density = context.resources.displayMetrics.density.toDouble()
|
|
134
|
+
val edges = DoubleArray(items.size + 1)
|
|
135
|
+
items.forEachIndexed { index, item ->
|
|
136
|
+
val style = item.json.optJSONObject("style")
|
|
137
|
+
val height = item.json.optDouble("height", 0.0)
|
|
138
|
+
val sourceHeight = if (item.type == "market" && style != null) {
|
|
139
|
+
val titleHeight = ceil((style.optJSONObject("title")?.optDouble("lineHeight", 24.0) ?: 24.0) * density) / density
|
|
140
|
+
val subtitleHeight = if (item.json.optString("subtitle").isNotEmpty() || item.json.optJSONObject("subtitlePrefix") != null) {
|
|
141
|
+
ceil((style.optJSONObject("subtitle")?.optDouble("lineHeight", 20.0) ?: 20.0) * density) / density + style.optDouble("lineGap", 0.0)
|
|
142
|
+
} else 0.0
|
|
143
|
+
maxOf(height.roundToInt().toDouble(), titleHeight + subtitleHeight + style.optDouble("verticalPadding", 12.0) * 2).toFloat().toDouble()
|
|
144
|
+
} else height
|
|
145
|
+
edges[index + 1] = edges[index] + sourceHeight * density
|
|
146
|
+
}
|
|
147
|
+
marketSourceItems = items
|
|
148
|
+
marketSourceEdges = edges
|
|
149
|
+
}
|
|
150
|
+
return marketSourceEdges.getOrElse(position) { 0.0 }
|
|
151
|
+
}
|
|
152
|
+
|
|
125
153
|
fun positionOfKey(key: String): Int =
|
|
126
154
|
(reorderItems ?: differ.currentList).indexOfFirst { it.key == key }
|
|
127
155
|
|
|
@@ -169,6 +197,8 @@ internal class NativeListAdapter(
|
|
|
169
197
|
}
|
|
170
198
|
|
|
171
199
|
fun dispose() {
|
|
200
|
+
marketSourceItems = null
|
|
201
|
+
marketSourceEdges = DoubleArray(0)
|
|
172
202
|
reorderItems = null
|
|
173
203
|
suppressDifferUpdates = false
|
|
174
204
|
createdRows.forEach(NativeListRowView::dispose)
|
|
@@ -45,6 +45,7 @@ import com.facebook.react.uimanager.style.LogicalEdge
|
|
|
45
45
|
import com.margelo.nitro.onekeyimage.OneKeyImageReusableView
|
|
46
46
|
import androidx.core.graphics.PathParser
|
|
47
47
|
import androidx.core.widget.TextViewCompat
|
|
48
|
+
import androidx.recyclerview.widget.RecyclerView
|
|
48
49
|
import org.json.JSONArray
|
|
49
50
|
import org.json.JSONObject
|
|
50
51
|
import kotlin.math.roundToInt
|
|
@@ -119,6 +120,7 @@ internal data class NativeListActionOrigin(
|
|
|
119
120
|
val source: String,
|
|
120
121
|
val slot: Int? = null,
|
|
121
122
|
val anchorInsetPixels: Int = 0,
|
|
123
|
+
val windowPointPixels: android.graphics.PointF? = null,
|
|
122
124
|
)
|
|
123
125
|
|
|
124
126
|
/** React Native color strings use CSS #RRGGBBAA ordering; Android expects #AARRGGBB. */
|
|
@@ -209,6 +211,8 @@ private class DottedUnderlineTextView(context: android.content.Context) : TextVi
|
|
|
209
211
|
|
|
210
212
|
private class PackedTitleLineLayout(context: android.content.Context) : LinearLayout(context) {
|
|
211
213
|
var packsChildrenAtStart = false
|
|
214
|
+
// OneKey patch: optional cap for the Market subtitle's localized name.
|
|
215
|
+
var leadingTextMaxWidth = Int.MAX_VALUE
|
|
212
216
|
|
|
213
217
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
214
218
|
if (!packsChildrenAtStart || childCount < 2) {
|
|
@@ -236,7 +240,7 @@ private class PackedTitleLineLayout(context: android.content.Context) : LinearLa
|
|
|
236
240
|
val titleMargins = title.layoutParams as MarginLayoutParams
|
|
237
241
|
if (widthMode != MeasureSpec.UNSPECIFIED) {
|
|
238
242
|
(title as TextView).maxWidth = (widthSize - paddingLeft - paddingRight - accessoryWidth -
|
|
239
|
-
titleMargins.leftMargin - titleMargins.rightMargin).coerceAtLeast(0)
|
|
243
|
+
titleMargins.leftMargin - titleMargins.rightMargin).coerceAtLeast(0).coerceAtMost(leadingTextMaxWidth)
|
|
240
244
|
}
|
|
241
245
|
(title.layoutParams as LayoutParams).apply {
|
|
242
246
|
width = LayoutParams.WRAP_CONTENT
|
|
@@ -393,7 +397,16 @@ private class NativeListTableColumnView(context: android.content.Context) : Line
|
|
|
393
397
|
internal class NativeListRowView(
|
|
394
398
|
private val reactContext: ThemedReactContext,
|
|
395
399
|
) : LinearLayout(reactContext) {
|
|
396
|
-
private
|
|
400
|
+
private var marketLeadingUsesSourceClip = false
|
|
401
|
+
private val leadingFrame = object : FrameLayout(context) {
|
|
402
|
+
override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
|
|
403
|
+
if (!marketLeadingUsesSourceClip || child !== leadingImages[0]) return super.drawChild(canvas, child, drawingTime)
|
|
404
|
+
// OneKey patch: clip only the Market avatar to the source border's padding box.
|
|
405
|
+
val saved = canvas.save()
|
|
406
|
+
BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
|
|
407
|
+
return try { super.drawChild(canvas, child, drawingTime) } finally { canvas.restoreToCount(saved) }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
397
410
|
// OneKey patch: reset selector fragments and corner decorations on every bind.
|
|
398
411
|
private val selectorViews = mutableListOf<View>()
|
|
399
412
|
private var selectorUsesSourceScale = false
|
|
@@ -405,6 +418,7 @@ internal class NativeListRowView(
|
|
|
405
418
|
}
|
|
406
419
|
private val selectorOriginalFontFeatures = mutableMapOf<TextView, String?>()
|
|
407
420
|
private val selectorOriginalPaintFlags = mutableMapOf<TextView, Int>()
|
|
421
|
+
private val marketOriginalPaintFlags = mutableMapOf<TextView, Int>()
|
|
408
422
|
private val selectorLineHeights = mutableMapOf<TextView, Int>()
|
|
409
423
|
private val selectorFontSizes = mutableMapOf<TextView, Float>()
|
|
410
424
|
private val selectorImages = mutableListOf<OneKeyImageReusableView>()
|
|
@@ -427,6 +441,8 @@ internal class NativeListRowView(
|
|
|
427
441
|
private val titleLine = PackedTitleLineLayout(context)
|
|
428
442
|
private val title = DottedUnderlineTextView(context)
|
|
429
443
|
private val subtitle = TextView(context)
|
|
444
|
+
// OneKey patch: the first text shrinks while the volume retains its width.
|
|
445
|
+
private val marketSubtitleLine = PackedTitleLineLayout(context)
|
|
430
446
|
private val tertiary = TextView(context)
|
|
431
447
|
private val status = TextView(context)
|
|
432
448
|
private val metricSubtitle = TextView(context)
|
|
@@ -479,6 +495,8 @@ internal class NativeListRowView(
|
|
|
479
495
|
private var marketLongPressRunnable: Runnable? = null
|
|
480
496
|
private var marketTouchStartX = 0f
|
|
481
497
|
private var marketTouchStartY = 0f
|
|
498
|
+
private var marketTouchX = 0f
|
|
499
|
+
private var marketTouchY = 0f
|
|
482
500
|
private var marketLongPressFired = false
|
|
483
501
|
private var reorderActive = false
|
|
484
502
|
private var checkboxCheckedColor = Color.rgb(32, 32, 32)
|
|
@@ -610,6 +628,8 @@ internal class NativeListRowView(
|
|
|
610
628
|
if (item?.type == "market") {
|
|
611
629
|
marketTouchStartX = event.x
|
|
612
630
|
marketTouchStartY = event.y
|
|
631
|
+
marketTouchX = event.x
|
|
632
|
+
marketTouchY = event.y
|
|
613
633
|
item.json.optString("pressInActionKey").takeIf(String::isNotEmpty)?.let { actionKey ->
|
|
614
634
|
emitAction(item, actionKey, null, this, "row")
|
|
615
635
|
}
|
|
@@ -620,7 +640,11 @@ internal class NativeListRowView(
|
|
|
620
640
|
if (touchPressed && current?.key == expectedKey && current.type == "market") {
|
|
621
641
|
marketLongPressRunnable = null
|
|
622
642
|
marketLongPressFired = true
|
|
623
|
-
|
|
643
|
+
val location = IntArray(2)
|
|
644
|
+
getLocationInWindow(location)
|
|
645
|
+
onAction?.invoke(current, actionKey, null, actionOrigin(this, "row").copy(
|
|
646
|
+
windowPointPixels = android.graphics.PointF(location[0] + marketTouchX, location[1] + marketTouchY),
|
|
647
|
+
))
|
|
624
648
|
}
|
|
625
649
|
}
|
|
626
650
|
marketLongPressRunnable = runnable
|
|
@@ -634,6 +658,8 @@ internal class NativeListRowView(
|
|
|
634
658
|
}
|
|
635
659
|
}
|
|
636
660
|
MotionEvent.ACTION_MOVE -> {
|
|
661
|
+
marketTouchX = event.x
|
|
662
|
+
marketTouchY = event.y
|
|
637
663
|
val outside = event.x < 0 || event.y < 0 || event.x >= width || event.y >= height
|
|
638
664
|
val movedMarket = (tag as? NativeListItem)?.type == "market" &&
|
|
639
665
|
(kotlin.math.abs(event.x - marketTouchStartX) > dp(10) ||
|
|
@@ -713,6 +739,22 @@ internal class NativeListRowView(
|
|
|
713
739
|
}
|
|
714
740
|
|
|
715
741
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
742
|
+
val marketItem = (tag as? NativeListItem)?.takeIf { it.type == "market" }
|
|
743
|
+
val marketStyle = marketItem?.json?.optJSONObject("style")
|
|
744
|
+
if (marketStyle?.has("horizontalPadding") == true) {
|
|
745
|
+
// OneKey patch: Yoga rounds the two absolute edges, not both padding values.
|
|
746
|
+
val sourcePadding = marketStyle.optDouble("horizontalPadding") * resources.displayMetrics.density
|
|
747
|
+
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
748
|
+
val rowHeight = selectorHeight
|
|
749
|
+
val sourceVerticalPadding = marketStyle.takeIf { it.has("verticalPadding") && rowHeight != null }
|
|
750
|
+
?.optDouble("verticalPadding")?.times(resources.displayMetrics.density)
|
|
751
|
+
setPadding(
|
|
752
|
+
sourcePadding.roundToInt(),
|
|
753
|
+
sourceVerticalPadding?.roundToInt() ?: paddingTop,
|
|
754
|
+
width - (width - sourcePadding).roundToInt(),
|
|
755
|
+
if (sourceVerticalPadding != null && rowHeight != null) rowHeight - (rowHeight - sourceVerticalPadding).roundToInt() else paddingBottom,
|
|
756
|
+
)
|
|
757
|
+
}
|
|
716
758
|
if (isMediaTile) {
|
|
717
759
|
val availableWidth = (MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight)
|
|
718
760
|
.coerceAtLeast(0)
|
|
@@ -737,6 +779,63 @@ internal class NativeListRowView(
|
|
|
737
779
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
738
780
|
super.onLayout(changed, left, top, right, bottom)
|
|
739
781
|
val item = tag as? NativeListItem ?: return
|
|
782
|
+
if (item.type == "market" && item.json.optJSONObject("style") != null) {
|
|
783
|
+
// OneKey patch: preserve Yoga's half-pixel centering for Market columns and badges.
|
|
784
|
+
for (column in listOf(leadingFrame, mainColumn, trailingColumn)) {
|
|
785
|
+
if (column.parent === this && column.visibility != GONE) {
|
|
786
|
+
column.offsetTopAndBottom((height - column.height + 1) / 2 - column.top)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// OneKey patch: token avatars stay centered in the row when text rounding adds a pixel.
|
|
790
|
+
if (item.json.optString("variant") != "token" && leadingFrame.parent === this && mainColumn.parent === this) {
|
|
791
|
+
leadingFrame.offsetTopAndBottom(mainColumn.top + (mainColumn.height - leadingFrame.height + 1) / 2 - leadingFrame.top)
|
|
792
|
+
}
|
|
793
|
+
// OneKey patch: preserve Yoga's absolute-edge rounding for the Market network badge.
|
|
794
|
+
if (leadingOverlayBackground.visibility == VISIBLE && item.json.optString("variant") == "token") {
|
|
795
|
+
val recycler = parent as? RecyclerView
|
|
796
|
+
val adapter = recycler?.adapter as? NativeListAdapter
|
|
797
|
+
val position = recycler?.getChildAdapterPosition(this) ?: RecyclerView.NO_POSITION
|
|
798
|
+
if (recycler != null && adapter != null && position != RecyclerView.NO_POSITION && adapter.itemAt(position)?.key == item.key) {
|
|
799
|
+
val density = resources.displayMetrics.density.toDouble()
|
|
800
|
+
val rowTop = adapter.marketSourceEdgePx(position)
|
|
801
|
+
val rowHeight = adapter.marketSourceEdgePx(position + 1) - rowTop
|
|
802
|
+
val imageHeight = item.json.optJSONObject("style")?.optJSONObject("image")?.optDouble("height", 32.0) ?: 32.0
|
|
803
|
+
val contentTop = (recycler.paddingTop / density).roundToInt() * density
|
|
804
|
+
val badgeTop = contentTop + rowTop + (rowHeight - imageHeight * density) / 2 + (imageHeight - 16) * density
|
|
805
|
+
val badgeHeight = (badgeTop + 20 * density).roundToInt() - badgeTop.roundToInt()
|
|
806
|
+
val top = ((imageHeight - 16) * density).roundToInt()
|
|
807
|
+
leadingOverlayBackground.layout(leadingOverlayBackground.left, top, leadingOverlayBackground.right, top + badgeHeight)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
for (badge in marketBadgeViews) {
|
|
811
|
+
if (badge.parent === titleLine && badge.visibility != GONE) {
|
|
812
|
+
badge.offsetTopAndBottom((titleLine.height - badge.height + 1) / 2 - badge.top)
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
// OneKey patch: preserve fractional text advances and gaps until the final edge.
|
|
816
|
+
val style = item.json.optJSONObject("style")
|
|
817
|
+
for ((line, gap) in listOf(
|
|
818
|
+
titleLine to style.optDouble("titleBadgeGap", 4.0),
|
|
819
|
+
marketSubtitleLine to (item.json.optJSONObject("subtitlePrefix")?.optDouble("gap", 4.0) ?: 0.0),
|
|
820
|
+
)) {
|
|
821
|
+
var cursor = 0f
|
|
822
|
+
var hasPrevious = false
|
|
823
|
+
for (index in 0 until line.childCount) {
|
|
824
|
+
val child = line.getChildAt(index)
|
|
825
|
+
if (child.visibility == GONE) continue
|
|
826
|
+
if (hasPrevious) {
|
|
827
|
+
cursor += (gap * resources.displayMetrics.density).toFloat()
|
|
828
|
+
child.offsetLeftAndRight(cursor.roundToInt() - child.left)
|
|
829
|
+
}
|
|
830
|
+
val advance = if (index == 0 && child is TextView) {
|
|
831
|
+
val textWidth = if (line === titleLine) child.paint.measureText(child.text.toString()) else child.layout?.getLineWidth(0)
|
|
832
|
+
textWidth?.coerceAtMost(child.width.toFloat()) ?: child.width.toFloat()
|
|
833
|
+
} else child.width.toFloat()
|
|
834
|
+
cursor = if (hasPrevious) cursor + advance else child.left + advance
|
|
835
|
+
hasPrevious = true
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
740
839
|
val accessory = item.json.optJSONArray("trailing")?.optJSONObject(0)
|
|
741
840
|
if (item.type == "identity" && item.json.has("height") && item.json.optString("presentation") == "accountSelector" && accessory?.optString("kind") == "icon" && accessory.optString("name") == "PlusSmallOutline") {
|
|
742
841
|
// OneKey patch: the borderless Plus retains the source's fixed top18/negative7 slot.
|
|
@@ -785,7 +884,7 @@ internal class NativeListRowView(
|
|
|
785
884
|
touchPressed = shouldRestorePressed
|
|
786
885
|
currentLayout = layout
|
|
787
886
|
tag = item
|
|
788
|
-
selectorUsesSourceScale = item.type == "market" || item.usesSelectorSourceScale || useSourceScale
|
|
887
|
+
selectorUsesSourceScale = item.type == "market" || item.type == "system" && item.json.optString("presentation") == "market" && item.json.optString("variant") == "retry" || item.usesSelectorSourceScale || useSourceScale
|
|
789
888
|
reorderActive = false
|
|
790
889
|
leadingImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
791
890
|
secondaryImage.prepareForReuse()
|
|
@@ -1079,6 +1178,8 @@ internal class NativeListRowView(
|
|
|
1079
1178
|
selectorOriginalFontFeatures.clear()
|
|
1080
1179
|
selectorOriginalPaintFlags.forEach { (view, flags) -> view.paintFlags = flags }
|
|
1081
1180
|
selectorOriginalPaintFlags.clear()
|
|
1181
|
+
marketOriginalPaintFlags.forEach { (view, flags) -> view.paintFlags = flags }
|
|
1182
|
+
marketOriginalPaintFlags.clear()
|
|
1082
1183
|
selectorLineHeights.clear()
|
|
1083
1184
|
selectorFontSizes.clear()
|
|
1084
1185
|
// OneKey patch: selector-only views cannot survive a recycled binding.
|
|
@@ -1095,7 +1196,17 @@ internal class NativeListRowView(
|
|
|
1095
1196
|
badge.setOnClickListener(null)
|
|
1096
1197
|
badge.contentDescription = null
|
|
1097
1198
|
marketBadgeLabels[index].apply {
|
|
1199
|
+
// OneKey patch: restore every optional Market badge typography property on reuse.
|
|
1098
1200
|
text = ""
|
|
1201
|
+
layoutParams = wrap()
|
|
1202
|
+
fontFeatureSettings = null
|
|
1203
|
+
textSize = sp(11f)
|
|
1204
|
+
typeface = NativeListFonts.medium(context)
|
|
1205
|
+
setLineSpacing(0f, 1f)
|
|
1206
|
+
includeFontPadding = false
|
|
1207
|
+
maxLines = 1
|
|
1208
|
+
ellipsize = TextUtils.TruncateAt.END
|
|
1209
|
+
gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
1099
1210
|
setTextColor(color(null, "secondaryText", "#0000009B"))
|
|
1100
1211
|
}
|
|
1101
1212
|
marketBadgeImages[index].prepareForReuse()
|
|
@@ -1140,6 +1251,9 @@ internal class NativeListRowView(
|
|
|
1140
1251
|
boundCheckboxData = null
|
|
1141
1252
|
mainColumn.orientation = VERTICAL
|
|
1142
1253
|
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
1254
|
+
// OneKey patch: remove the Market group before restoring shared labels.
|
|
1255
|
+
marketSubtitleLine.removeAllViews()
|
|
1256
|
+
mainColumn.removeView(marketSubtitleLine)
|
|
1143
1257
|
mediaMetadataRow.removeView(subtitle)
|
|
1144
1258
|
mediaMetadataRow.removeView(mediaNetworkImage)
|
|
1145
1259
|
mainColumn.removeView(mediaMetadataRow)
|
|
@@ -1165,6 +1279,10 @@ internal class NativeListRowView(
|
|
|
1165
1279
|
badgeLine.text = ""
|
|
1166
1280
|
title.setLineSpacing(0f, 1f)
|
|
1167
1281
|
subtitle.setLineSpacing(0f, 1f)
|
|
1282
|
+
tertiary.setLineSpacing(0f, 1f)
|
|
1283
|
+
tertiary.maxWidth = Int.MAX_VALUE
|
|
1284
|
+
tertiary.ellipsize = TextUtils.TruncateAt.END
|
|
1285
|
+
tertiary.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1168
1286
|
title.letterSpacing = 0f
|
|
1169
1287
|
title.showsDottedUnderline = false
|
|
1170
1288
|
title.setPadding(0, 0, 0, 0)
|
|
@@ -1218,6 +1336,7 @@ internal class NativeListRowView(
|
|
|
1218
1336
|
spinner.visibility = GONE
|
|
1219
1337
|
spinner.layoutParams = LayoutParams(dp(20), dp(20)).apply { gravity = Gravity.END }
|
|
1220
1338
|
spinner.alpha = 1f
|
|
1339
|
+
marketLeadingUsesSourceClip = false
|
|
1221
1340
|
leadingFrame.visibility = GONE
|
|
1222
1341
|
leadingFrame.background = null
|
|
1223
1342
|
leadingFrame.clipChildren = false
|
|
@@ -1932,6 +2051,25 @@ internal class NativeListRowView(
|
|
|
1932
2051
|
)
|
|
1933
2052
|
}
|
|
1934
2053
|
|
|
2054
|
+
// OneKey patch: opt-in Market text uses the same pixel rounding and line box as RN.
|
|
2055
|
+
private fun applyMarketTextMetrics(view: TextView, style: JSONObject?) {
|
|
2056
|
+
if (style == null || view.visibility != VISIBLE || view.text.isEmpty()) return
|
|
2057
|
+
marketOriginalPaintFlags.putIfAbsent(view, view.paintFlags)
|
|
2058
|
+
view.paintFlags = view.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
|
|
2059
|
+
if (style.has("fontSize")) {
|
|
2060
|
+
val sourceSize = sp(style.optDouble("fontSize").toFloat()) * resources.displayMetrics.density
|
|
2061
|
+
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, kotlin.math.ceil(sourceSize.toDouble()).toFloat())
|
|
2062
|
+
}
|
|
2063
|
+
val text = SpannableStringBuilder(view.text)
|
|
2064
|
+
text.getSpans(0, text.length, SelectorLineHeightSpan::class.java).forEach(text::removeSpan)
|
|
2065
|
+
if (style.has("lineHeight")) {
|
|
2066
|
+
val lineHeight = kotlin.math.ceil(style.optDouble("lineHeight") * (if (selectorUsesSourceScale) 1f else NativeListScale.factor(resources)) * resources.displayMetrics.density).toInt()
|
|
2067
|
+
text.setSpan(SelectorLineHeightSpan(lineHeight), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
2068
|
+
view.setLineSpacing(0f, 1f)
|
|
2069
|
+
}
|
|
2070
|
+
view.text = text
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1935
2073
|
private fun marketText(value: String, segments: JSONArray?, fontSize: Float): CharSequence {
|
|
1936
2074
|
if (segments == null || segments.length() == 0) return value
|
|
1937
2075
|
val result = SpannableStringBuilder()
|
|
@@ -1989,7 +2127,11 @@ internal class NativeListRowView(
|
|
|
1989
2127
|
cornerRadiusDp = cornerRadius,
|
|
1990
2128
|
)
|
|
1991
2129
|
|
|
1992
|
-
|
|
2130
|
+
// OneKey patch: retain the source gap without changing other templates.
|
|
2131
|
+
// addView(mainColumn, weighted())
|
|
2132
|
+
addView(mainColumn, weighted().apply {
|
|
2133
|
+
marginEnd = dp(style?.optDouble("contentTrailingGap", 0.0)?.roundToInt() ?: 0)
|
|
2134
|
+
})
|
|
1993
2135
|
titleLine.packsChildrenAtStart = true
|
|
1994
2136
|
showText(title, item.json.optString("title"), style?.optJSONObject("title")?.optInt("lines", 1) ?: 1)
|
|
1995
2137
|
applyMarketTextStyle(
|
|
@@ -2008,6 +2150,7 @@ internal class NativeListRowView(
|
|
|
2008
2150
|
val badge = badges.getJSONObject(index)
|
|
2009
2151
|
val badgeView = marketBadgeViews[index]
|
|
2010
2152
|
val badgeLabel = marketBadgeLabels[index]
|
|
2153
|
+
val badgeStyle = badge.optJSONObject("style")
|
|
2011
2154
|
val hasGlyph = badge.optString("iconName") == "verified"
|
|
2012
2155
|
val remoteIcon = badge.optJSONObject("icon")
|
|
2013
2156
|
val hasIcon = hasGlyph || remoteIcon != null
|
|
@@ -2022,15 +2165,28 @@ internal class NativeListRowView(
|
|
|
2022
2165
|
val foreground = safeColor(badge.optString("textColor"), toneColor)
|
|
2023
2166
|
badgeView.visibility = VISIBLE
|
|
2024
2167
|
val iconOnly = hasIcon && text.isEmpty()
|
|
2025
|
-
|
|
2168
|
+
// OneKey patch: Market callers can request the original badge padding.
|
|
2169
|
+
val padding = badgeStyle?.optDouble("horizontalPadding", 5.0)?.roundToInt() ?: 5
|
|
2170
|
+
val leftPadding = if (badgeStyle?.has("horizontalPadding") == true) padding else if (hasIcon) 2 else 5
|
|
2171
|
+
// badgeView.setPadding(dp(if (iconOnly) 0 else if (hasIcon) 2 else 5), 0, dp(if (iconOnly) 0 else 5), 0)
|
|
2172
|
+
badgeView.setPadding(dp(if (iconOnly) 0 else leftPadding), 0, dp(if (iconOnly) 0 else padding), 0)
|
|
2026
2173
|
badgeView.background = roundedFill(
|
|
2027
2174
|
safeColor(
|
|
2028
2175
|
badge.optString("backgroundColor"),
|
|
2029
2176
|
if (hasIcon && text.isEmpty()) Color.TRANSPARENT else color(theme, "strongBackground", "#0000000F"),
|
|
2030
2177
|
),
|
|
2031
2178
|
4f,
|
|
2032
|
-
)
|
|
2179
|
+
).apply {
|
|
2180
|
+
if (badgeStyle != null) this.cornerRadius = 4f * resources.displayMetrics.density
|
|
2181
|
+
}
|
|
2033
2182
|
badgeLabel.text = text
|
|
2183
|
+
// OneKey patch: match SizableText's tabular numerals only for explicit Market metrics.
|
|
2184
|
+
badgeLabel.fontFeatureSettings = if (badgeStyle != null) "tnum" else null
|
|
2185
|
+
badgeLabel.textSize = sp(badgeStyle?.optDouble("fontSize", 11.0)?.toFloat() ?: 11f)
|
|
2186
|
+
badgeLabel.typeface = marketTypeface(badgeStyle?.optString("fontWeight").orEmpty(), "medium")
|
|
2187
|
+
if (badgeStyle?.has("lineHeight") == true) {
|
|
2188
|
+
TextViewCompat.setLineHeight(badgeLabel, dp(badgeStyle.optDouble("lineHeight").roundToInt()))
|
|
2189
|
+
}
|
|
2034
2190
|
badgeLabel.setTextColor(foreground)
|
|
2035
2191
|
badgeLabel.visibility = if (text.isEmpty()) GONE else VISIBLE
|
|
2036
2192
|
if (hasGlyph) {
|
|
@@ -2056,7 +2212,9 @@ internal class NativeListRowView(
|
|
|
2056
2212
|
badgeView.contentDescription = badge.optString("accessibilityLabel", text)
|
|
2057
2213
|
titleLine.addView(
|
|
2058
2214
|
badgeView,
|
|
2059
|
-
|
|
2215
|
+
// OneKey patch: preserve 18dp unless the Market row opts into source metrics.
|
|
2216
|
+
// LayoutParams(LayoutParams.WRAP_CONTENT, dp(18)).apply { marginStart = dp(titleBadgeGap) },
|
|
2217
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, dp(badgeStyle?.optDouble("height", 18.0)?.roundToInt() ?: 18)).apply { marginStart = dp(titleBadgeGap) },
|
|
2060
2218
|
)
|
|
2061
2219
|
}
|
|
2062
2220
|
}
|
|
@@ -2082,6 +2240,31 @@ internal class NativeListRowView(
|
|
|
2082
2240
|
style?.optJSONObject("subtitle")?.optDouble("fontSize", 14.0)?.toFloat() ?: 14f,
|
|
2083
2241
|
)
|
|
2084
2242
|
}
|
|
2243
|
+
// OneKey patch: preserve independent name metrics, truncation, and volume.
|
|
2244
|
+
val subtitlePrefix = item.json.optJSONObject("subtitlePrefix")
|
|
2245
|
+
val subtitlePadding = style?.optDouble("subtitleTrailingPadding", 0.0)?.roundToInt() ?: 0
|
|
2246
|
+
if (subtitlePrefix != null || subtitlePadding > 0) {
|
|
2247
|
+
mainColumn.removeView(subtitle)
|
|
2248
|
+
mainColumn.removeView(tertiary)
|
|
2249
|
+
marketSubtitleLine.orientation = HORIZONTAL
|
|
2250
|
+
marketSubtitleLine.gravity = Gravity.CENTER_VERTICAL
|
|
2251
|
+
marketSubtitleLine.packsChildrenAtStart = true
|
|
2252
|
+
marketSubtitleLine.leadingTextMaxWidth = subtitlePrefix?.takeIf { it.has("maxWidth") }
|
|
2253
|
+
?.optDouble("maxWidth")?.roundToInt()?.let(::dp) ?: Int.MAX_VALUE
|
|
2254
|
+
marketSubtitleLine.setPadding(0, 0, dp(subtitlePadding), 0)
|
|
2255
|
+
showText(tertiary, subtitlePrefix?.optString("text") ?: "", 1)
|
|
2256
|
+
applyMarketTextStyle(tertiary, subtitlePrefix?.optJSONObject("style"), 12f, 16, "regular", color(theme, "secondaryText", "#646464"), "start")
|
|
2257
|
+
marketSubtitleLine.addView(tertiary, wrap())
|
|
2258
|
+
marketSubtitleLine.addView(subtitle, wrap().apply {
|
|
2259
|
+
if (tertiary.visibility == VISIBLE && subtitle.visibility == VISIBLE) {
|
|
2260
|
+
marginStart = dp(subtitlePrefix?.optDouble("gap", 4.0)?.roundToInt() ?: 4)
|
|
2261
|
+
}
|
|
2262
|
+
})
|
|
2263
|
+
mainColumn.addView(marketSubtitleLine, 1, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2264
|
+
topMargin = dp(style?.optDouble("lineGap", 0.0)?.roundToInt() ?: 0)
|
|
2265
|
+
})
|
|
2266
|
+
marketSubtitleLine.visibility = if (tertiary.visibility == VISIBLE || subtitle.visibility == VISIBLE) VISIBLE else GONE
|
|
2267
|
+
}
|
|
2085
2268
|
trailingColumn.orientation = HORIZONTAL
|
|
2086
2269
|
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
2087
2270
|
addView(trailingColumn, wrap())
|
|
@@ -2150,6 +2333,24 @@ internal class NativeListRowView(
|
|
|
2150
2333
|
dp(style?.optDouble("changeWidth", 80.0)?.roundToInt() ?: 80),
|
|
2151
2334
|
dp(style?.optDouble("changeHeight", 32.0)?.roundToInt() ?: 32),
|
|
2152
2335
|
)
|
|
2336
|
+
applyMarketTextMetrics(title, style?.optJSONObject("title"))
|
|
2337
|
+
applyMarketTextMetrics(subtitle, style?.optJSONObject("subtitle"))
|
|
2338
|
+
applyMarketTextMetrics(tertiary, item.json.optJSONObject("subtitlePrefix")?.optJSONObject("style"))
|
|
2339
|
+
applyMarketTextMetrics(price, priceStyle)
|
|
2340
|
+
applyMarketTextMetrics(change, changeStyle)
|
|
2341
|
+
val badges = item.json.optJSONArray("badges")
|
|
2342
|
+
marketBadgeLabels.forEachIndexed { index, label ->
|
|
2343
|
+
val badge = badges?.optJSONObject(index)
|
|
2344
|
+
val badgeStyle = badge?.optJSONObject("style")
|
|
2345
|
+
applyMarketTextMetrics(label, badgeStyle)
|
|
2346
|
+
if (badge != null && label.visibility == VISIBLE && badgeStyle?.has("horizontalPadding") == true &&
|
|
2347
|
+
badge.optJSONObject("icon") == null && badge.optString("iconName").isEmpty()) {
|
|
2348
|
+
// OneKey patch: match RN's intrinsic text width and one rounded padding pair.
|
|
2349
|
+
label.layoutParams = wrap().apply { width = label.paint.measureText(label.text.toString()).roundToInt() }
|
|
2350
|
+
val padding = badgeStyle.optDouble("horizontalPadding") * resources.displayMetrics.density
|
|
2351
|
+
marketBadgeViews[index].setPadding(kotlin.math.floor(padding).toInt(), 0, (padding * 2).roundToInt() - kotlin.math.floor(padding).toInt(), 0)
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2153
2354
|
contentDescription = item.json.optString(
|
|
2154
2355
|
"accessibilityLabel",
|
|
2155
2356
|
listOf(
|
|
@@ -2817,16 +3018,41 @@ internal class NativeListRowView(
|
|
|
2817
3018
|
)
|
|
2818
3019
|
return
|
|
2819
3020
|
}
|
|
3021
|
+
if (isMarket && variant == "retry") {
|
|
3022
|
+
val message = item.json.optString("message")
|
|
3023
|
+
orientation = VERTICAL
|
|
3024
|
+
gravity = Gravity.CENTER
|
|
3025
|
+
setPadding(dp(32), dp(if (message.isEmpty()) 11 else 32), dp(32), dp(if (message.isEmpty()) 11 else 27))
|
|
3026
|
+
if (message.isNotEmpty()) {
|
|
3027
|
+
showText(title, message, 2)
|
|
3028
|
+
val style = JSONObject().put("fontSize", 16).put("lineHeight", 24)
|
|
3029
|
+
applyMarketTextStyle(title, style, 16f, 24, "regular", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3030
|
+
applyMarketTextMetrics(title, style)
|
|
3031
|
+
addView(mainColumn, wrap().apply { bottomMargin = kotlin.math.ceil(7.0 * resources.displayMetrics.density).toInt() })
|
|
3032
|
+
}
|
|
3033
|
+
showTrailing(0, item.json.optString("actionText", "Retry"), true, item.json.optString("actionKey"))
|
|
3034
|
+
val button = trailingViews[0]
|
|
3035
|
+
val style = JSONObject().put("fontSize", 14).put("lineHeight", 20)
|
|
3036
|
+
applyMarketTextStyle(button, style, 14f, 20, "medium", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3037
|
+
applyMarketTextMetrics(button, style)
|
|
3038
|
+
button.background = null
|
|
3039
|
+
// Match Yoga's text rounding before adding the tertiary Button's border/padding.
|
|
3040
|
+
val density = resources.displayMetrics.density
|
|
3041
|
+
val buttonWidth = (kotlin.math.ceil(button.paint.measureText(button.text.toString()).toDouble()) + 18 * density).roundToInt()
|
|
3042
|
+
val buttonHeight = kotlin.math.ceil(kotlin.math.ceil(20.0 * density) + 10 * density).toInt()
|
|
3043
|
+
button.setPadding(0, 0, 0, 0)
|
|
3044
|
+
button.layoutParams = LayoutParams(buttonWidth, buttonHeight)
|
|
3045
|
+
addView(trailingColumn, wrap())
|
|
3046
|
+
return
|
|
3047
|
+
}
|
|
2820
3048
|
if (isMarket && variant == "noMatch") {
|
|
2821
3049
|
gravity = Gravity.CENTER
|
|
2822
3050
|
setPadding(dp(32), dp(32), dp(32), dp(32))
|
|
2823
|
-
title.textSize = sp(16f)
|
|
2824
|
-
title.typeface = NativeListFonts.regular(context)
|
|
2825
|
-
title.gravity = Gravity.CENTER
|
|
2826
|
-
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
2827
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
2828
3051
|
showText(title, item.json.optString("message"), 1)
|
|
2829
|
-
|
|
3052
|
+
val style = JSONObject().put("fontSize", 16).put("lineHeight", 24)
|
|
3053
|
+
applyMarketTextStyle(title, style, 16f, 24, "regular", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3054
|
+
applyMarketTextMetrics(title, style)
|
|
3055
|
+
addView(mainColumn, wrap())
|
|
2830
3056
|
return
|
|
2831
3057
|
}
|
|
2832
3058
|
// OneKey patch: warning title/description wrap inside the actual scroll content.
|
|
@@ -2972,12 +3198,19 @@ internal class NativeListRowView(
|
|
|
2972
3198
|
if (tokenPair) {
|
|
2973
3199
|
leadingOverlayBackground.visibility = VISIBLE
|
|
2974
3200
|
leadingOverlayBackground.background = roundedFill(visualBackdropColor, 10f)
|
|
3201
|
+
val item = tag as? NativeListItem
|
|
3202
|
+
val marketPadding = if (item?.type == "market") item.json.optJSONObject("style")?.optDouble("horizontalPadding", 20.0) ?: 20.0 else null
|
|
3203
|
+
val density = resources.displayMetrics.density
|
|
3204
|
+
// Yoga rounds the absolute horizontal edges, including the avatar's fractional origin.
|
|
3205
|
+
val badgeRight = marketPadding?.let { ((it + sizeDp + 4) * density).roundToInt() }
|
|
3206
|
+
val badgeLeft = marketPadding?.let { ((it + sizeDp - 16) * density).roundToInt() }
|
|
3207
|
+
val avatarRight = marketPadding?.let { ((it + sizeDp) * density).roundToInt() }
|
|
2975
3208
|
leadingOverlayBackground.layoutParams = FrameLayout.LayoutParams(
|
|
2976
|
-
dp(20),
|
|
3209
|
+
if (badgeRight != null && badgeLeft != null) badgeRight - badgeLeft else dp(20),
|
|
2977
3210
|
dp(20),
|
|
2978
3211
|
Gravity.END or Gravity.BOTTOM,
|
|
2979
3212
|
).apply {
|
|
2980
|
-
marginEnd = -dp(4)
|
|
3213
|
+
marginEnd = if (badgeRight != null && avatarRight != null) avatarRight - badgeRight else -dp(4)
|
|
2981
3214
|
bottomMargin = -dp(4)
|
|
2982
3215
|
}
|
|
2983
3216
|
}
|
|
@@ -3019,21 +3252,26 @@ internal class NativeListRowView(
|
|
|
3019
3252
|
image.clipToOutline = true
|
|
3020
3253
|
if ((tag as? NativeListItem)?.type == "market" && index == 0 && visual.optString("borderColor").isNotEmpty()) {
|
|
3021
3254
|
val inset = dp(1)
|
|
3022
|
-
val
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3255
|
+
val density = resources.displayMetrics.density
|
|
3256
|
+
val sourceRadius = cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp))
|
|
3257
|
+
// OneKey patch: retain the source's fractional border and background rendering.
|
|
3258
|
+
leadingFrame.background = null
|
|
3259
|
+
BackgroundStyleApplicator.setBackgroundColor(leadingFrame, visualBackground)
|
|
3260
|
+
// Match the source's physical edges: React Native rasterizes ALL as a different stroke path.
|
|
3261
|
+
for (edge in arrayOf(LogicalEdge.LEFT, LogicalEdge.TOP, LogicalEdge.RIGHT, LogicalEdge.BOTTOM)) {
|
|
3262
|
+
BackgroundStyleApplicator.setBorderWidth(leadingFrame, edge, 1f)
|
|
3263
|
+
BackgroundStyleApplicator.setBorderColor(leadingFrame, edge, safeColor(visual.optString("borderColor"), Color.TRANSPARENT))
|
|
3027
3264
|
}
|
|
3028
|
-
|
|
3265
|
+
BackgroundStyleApplicator.setBorderRadius(leadingFrame, BorderRadiusProp.BORDER_RADIUS, LengthPercentage(sourceRadius, LengthPercentageType.POINT))
|
|
3266
|
+
// OneKey patch: round the source image's absolute edges inside its border.
|
|
3267
|
+
val sourceLeft = ((tag as? NativeListItem)?.json?.optJSONObject("style")?.optDouble("horizontalPadding", 20.0) ?: 20.0) * density
|
|
3268
|
+
val imageWidth = (sourceLeft + (sizeDp - 1) * density).roundToInt() - (sourceLeft + density).roundToInt()
|
|
3269
|
+
image.layoutParams = FrameLayout.LayoutParams(imageWidth, dp(heightDp - 2)).apply {
|
|
3029
3270
|
leftMargin = inset
|
|
3030
3271
|
topMargin = inset
|
|
3031
3272
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
outline.setRoundRect(-inset, -inset, view.width + inset, view.height + inset, radius)
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3273
|
+
marketLeadingUsesSourceClip = true
|
|
3274
|
+
image.clipToOutline = false
|
|
3037
3275
|
}
|
|
3038
3276
|
val fallbackIcon = if (index == 0) visual.optJSONObject("fallbackIcon") else null
|
|
3039
3277
|
val expectedEpoch = bindingEpoch
|
|
@@ -3497,6 +3735,13 @@ internal class NativeListRowView(
|
|
|
3497
3735
|
}
|
|
3498
3736
|
|
|
3499
3737
|
private fun applySize(item: NativeListItem) {
|
|
3738
|
+
if (item.type == "system" && item.json.optString("presentation") == "market" &&
|
|
3739
|
+
item.json.optString("variant") in setOf("retry", "noMatch")) {
|
|
3740
|
+
val defaultHeight = if (item.json.optString("variant") == "noMatch") 88.0 else if (item.json.optString("message").isEmpty()) 52.0 else 120.0
|
|
3741
|
+
minimumHeight = (item.json.optDouble("height", defaultHeight).toFloat() * resources.displayMetrics.density).roundToInt()
|
|
3742
|
+
selectorHeight = if (item.json.has("height")) minimumHeight else null
|
|
3743
|
+
return
|
|
3744
|
+
}
|
|
3500
3745
|
if (item.type == "market") {
|
|
3501
3746
|
val style = item.json.optJSONObject("style")
|
|
3502
3747
|
val imageHeight = style?.optJSONObject("image")?.optDouble(
|
|
@@ -3505,12 +3750,15 @@ internal class NativeListRowView(
|
|
|
3505
3750
|
) ?: if (item.json.optString("variant") == "stock") 40.0 else 32.0
|
|
3506
3751
|
val verticalPadding = style?.optDouble("verticalPadding", 12.0) ?: 12.0
|
|
3507
3752
|
val defaultHeight = if (item.json.optString("variant") == "stock") 72.0 else 68.0
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
).roundToInt(),
|
|
3753
|
+
// OneKey patch: preserve fractional DP until the final physical pixel edge.
|
|
3754
|
+
val rowHeight = item.json.optDouble(
|
|
3755
|
+
"height",
|
|
3756
|
+
maxOf(defaultHeight, imageHeight + verticalPadding * 2),
|
|
3513
3757
|
)
|
|
3758
|
+
val sourceScale = if (selectorUsesSourceScale) 1f else NativeListScale.factor(resources)
|
|
3759
|
+
minimumHeight = (rowHeight * sourceScale * resources.displayMetrics.density).roundToInt()
|
|
3760
|
+
// OneKey patch: explicit Market row heights must not grow after child pixel rounding.
|
|
3761
|
+
selectorHeight = if (item.json.has("height")) minimumHeight else null
|
|
3514
3762
|
return
|
|
3515
3763
|
}
|
|
3516
3764
|
val isWalletSidebar =
|
|
@@ -94,6 +94,8 @@ class NativeListView(
|
|
|
94
94
|
private var configuredTopPaddingPx = 0
|
|
95
95
|
private var configuredBottomPaddingPx = 0
|
|
96
96
|
private val refreshLayout = SwipeRefreshLayout(context)
|
|
97
|
+
private val refreshIndicatorTravelPx = refreshLayout.progressViewEndOffset
|
|
98
|
+
private var refreshIndicatorOffsetPx = 0
|
|
97
99
|
private val contentContainer = FrameLayout(context)
|
|
98
100
|
private val adapter = NativeListAdapter(reactContext)
|
|
99
101
|
private val layoutManager = GridLayoutManager(context, 1)
|
|
@@ -251,6 +253,11 @@ class NativeListView(
|
|
|
251
253
|
JSONObject().put("actionKey", "nativeList.refresh"),
|
|
252
254
|
)
|
|
253
255
|
}
|
|
256
|
+
recyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
257
|
+
// OneKey patch: a collapsible pager owns the extra padding above the rows.
|
|
258
|
+
// Keep the refresh control below that header without moving ordinary lists.
|
|
259
|
+
updateRefreshIndicatorOffset((recyclerView.paddingTop - configuredTopPaddingPx).coerceAtLeast(0))
|
|
260
|
+
}
|
|
254
261
|
|
|
255
262
|
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
256
263
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
|
@@ -274,6 +281,12 @@ class NativeListView(
|
|
|
274
281
|
|
|
275
282
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
276
283
|
super.onLayout(changed, left, top, right, bottom)
|
|
284
|
+
val first = config?.items?.firstOrNull()
|
|
285
|
+
if (recyclerView.isLayoutRequested && (first?.type == "market" || first?.json?.optString("presentation") == "market")) {
|
|
286
|
+
// A retained Market page can keep cached parent measurements after its diff.
|
|
287
|
+
// Drain the child's pending layout so its committed rows replace the old cells.
|
|
288
|
+
relayoutRecyclerViewImmediately()
|
|
289
|
+
}
|
|
277
290
|
val nextWidth = right - left
|
|
278
291
|
val nextHeight = bottom - top
|
|
279
292
|
if (
|
|
@@ -288,6 +301,28 @@ class NativeListView(
|
|
|
288
301
|
performPendingScrollIfNeeded()
|
|
289
302
|
}
|
|
290
303
|
|
|
304
|
+
private fun updateRefreshIndicatorOffset(offset: Int) {
|
|
305
|
+
if (refreshIndicatorOffsetPx == offset) return
|
|
306
|
+
refreshIndicatorOffsetPx = offset
|
|
307
|
+
val refreshing = refreshLayout.isRefreshing
|
|
308
|
+
val start = offset - refreshLayout.progressCircleDiameter
|
|
309
|
+
refreshLayout.setProgressViewOffset(false, start, start + refreshIndicatorTravelPx)
|
|
310
|
+
refreshLayout.isRefreshing = refreshing
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
override fun onAttachedToWindow() {
|
|
314
|
+
super.onAttachedToWindow()
|
|
315
|
+
val first = config?.items?.firstOrNull()
|
|
316
|
+
if (recyclerView.isLayoutRequested && (first?.type == "market" || first?.json?.optString("presentation") == "market")) {
|
|
317
|
+
relayoutContents()
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
override fun onDetachedFromWindow() {
|
|
322
|
+
updateRefreshIndicatorOffset(0)
|
|
323
|
+
super.onDetachedFromWindow()
|
|
324
|
+
}
|
|
325
|
+
|
|
291
326
|
fun applySnapshot(snapshotJson: String) {
|
|
292
327
|
val next = try {
|
|
293
328
|
NativeListConfig.parse(snapshotJson)
|
|
@@ -1121,7 +1156,12 @@ class NativeListView(
|
|
|
1121
1156
|
.put("source", origin.source)
|
|
1122
1157
|
.put("generation", generation)
|
|
1123
1158
|
.put("layoutDirection", if (origin.sourceView.layoutDirection == LAYOUT_DIRECTION_RTL) "rtl" else "ltr")
|
|
1124
|
-
.also { anchor ->
|
|
1159
|
+
.also { anchor ->
|
|
1160
|
+
origin.slot?.let { anchor.put("slot", it) }
|
|
1161
|
+
origin.windowPointPixels?.let { point ->
|
|
1162
|
+
anchor.put("windowPoint", JSONObject().put("x", point.x / density).put("y", point.y / density))
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1125
1165
|
}
|
|
1126
1166
|
|
|
1127
1167
|
private fun isOriginValid(origin: NativeListActionOrigin): Boolean =
|
package/ios/NativeListCell.swift
CHANGED
|
@@ -79,6 +79,7 @@ final class NativeListActionOrigin {
|
|
|
79
79
|
let slot: Int?
|
|
80
80
|
// OneKey patch: expose the layout slot while preserving the larger hit target.
|
|
81
81
|
let anchorInset: CGFloat
|
|
82
|
+
var windowPoint: CGPoint?
|
|
82
83
|
|
|
83
84
|
init(
|
|
84
85
|
sourceView: UIView,
|
|
@@ -443,12 +444,17 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
443
444
|
private let titleRowStack = UIStackView()
|
|
444
445
|
private let titleLabel = NativeListDottedUnderlineLabel()
|
|
445
446
|
private let subtitleLabel = UILabel()
|
|
447
|
+
// OneKey patch: keep Market name and volume in independent line boxes.
|
|
448
|
+
private let marketSubtitleStack = UIStackView()
|
|
449
|
+
private let marketSubtitleSpacer = UIView()
|
|
446
450
|
private let tertiaryLabel = UILabel()
|
|
447
451
|
private let statusLabel = NativeListInsetLabel()
|
|
448
452
|
private let metricSubtitleLabel = UILabel()
|
|
449
453
|
private let metricCompositeStack = UIStackView()
|
|
450
454
|
private let badgeLabel = NativeListInsetLabel()
|
|
451
|
-
|
|
455
|
+
// OneKey patch: reuse the existing explicit line-box layout for styled Market badges.
|
|
456
|
+
// private let marketBadgeButtons = (0..<3).map { _ in UIButton(type: .system) }
|
|
457
|
+
private let marketBadgeButtons = (0..<3).map { _ in NativeListAccessoryButton(type: .system) }
|
|
452
458
|
private let marketBadgeImages = (0..<3).map { _ in OneKeyImageReusableView(frame: .zero) }
|
|
453
459
|
private let actionStack = UIStackView()
|
|
454
460
|
private let actionButtons = (0..<3).map { _ in UIButton(type: .system) }
|
|
@@ -797,6 +803,19 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
797
803
|
|
|
798
804
|
override func layoutSubviews() {
|
|
799
805
|
super.layoutSubviews()
|
|
806
|
+
if let item = currentItem, item.type == "system", item.data.string("presentation") == "market",
|
|
807
|
+
["noMatch", "retry"].contains(item.data.string("variant")), !titleLabel.isHidden {
|
|
808
|
+
// The cell content view owns the root constraints and must settle before reading descendants.
|
|
809
|
+
contentView.layoutIfNeeded()
|
|
810
|
+
// React Native floors text origins to physical pixels after centering the line box.
|
|
811
|
+
titleLabel.transform = .identity
|
|
812
|
+
let scale = max(1, window?.screen.scale ?? traitCollection.displayScale)
|
|
813
|
+
let origin = titleLabel.convert(titleLabel.bounds, to: contentView).origin
|
|
814
|
+
let x = floor((contentView.bounds.width - titleLabel.bounds.width) / 2 * scale) / scale
|
|
815
|
+
let contentHeight: CGFloat = item.data.string("variant") == "retry" ? 56 : 24
|
|
816
|
+
let y = floor(max(32, (contentView.bounds.height - contentHeight) / 2) * scale) / scale
|
|
817
|
+
titleLabel.transform = CGAffineTransform(translationX: x - origin.x, y: y - origin.y)
|
|
818
|
+
}
|
|
800
819
|
// OneKey patch: extend only the background across the section list outer inset.
|
|
801
820
|
if currentItem?.data.bool("backgroundFullWidth") == true {
|
|
802
821
|
selectorFullWidthBackground.frame = CGRect(x: -frame.minX, y: 0, width: superview?.bounds.width ?? bounds.width, height: bounds.height)
|
|
@@ -1140,6 +1159,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1140
1159
|
}
|
|
1141
1160
|
|
|
1142
1161
|
private func reset() {
|
|
1162
|
+
titleLabel.transform = .identity
|
|
1143
1163
|
restoreSelectorTypography()
|
|
1144
1164
|
// OneKey patch: remove selector decorations before rebinding recycled cells.
|
|
1145
1165
|
selectorViews.forEach { $0.removeFromSuperview() }
|
|
@@ -1202,6 +1222,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1202
1222
|
headerValueIconImageView.removeFromSuperview()
|
|
1203
1223
|
headerValueIconImageView.image = nil
|
|
1204
1224
|
headerValueIconImageView.isHidden = true
|
|
1225
|
+
// OneKey patch: restore the shared labels before any recycled row binds.
|
|
1226
|
+
marketSubtitleStack.arrangedSubviews.forEach {
|
|
1227
|
+
marketSubtitleStack.removeArrangedSubview($0)
|
|
1228
|
+
$0.removeFromSuperview()
|
|
1229
|
+
}
|
|
1230
|
+
mainStack.removeArrangedSubview(marketSubtitleStack)
|
|
1231
|
+
marketSubtitleStack.removeFromSuperview()
|
|
1232
|
+
mainStack.removeArrangedSubview(tertiaryLabel)
|
|
1233
|
+
tertiaryLabel.removeFromSuperview()
|
|
1205
1234
|
mediaMetadataStack.removeArrangedSubview(subtitleLabel)
|
|
1206
1235
|
mediaMetadataStack.removeArrangedSubview(mediaNetworkImage)
|
|
1207
1236
|
mediaMetadataStack.removeFromSuperview()
|
|
@@ -1211,6 +1240,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1211
1240
|
subtitleLabel.removeFromSuperview()
|
|
1212
1241
|
mainStack.insertArrangedSubview(titleRowStack, at: 0)
|
|
1213
1242
|
mainStack.insertArrangedSubview(subtitleLabel, at: 1)
|
|
1243
|
+
mainStack.insertArrangedSubview(tertiaryLabel, at: 2)
|
|
1214
1244
|
leadingWidth.constant = 40
|
|
1215
1245
|
leadingHeight.constant = 40
|
|
1216
1246
|
leadingIconWidth.constant = 18
|
|
@@ -1265,6 +1295,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1265
1295
|
titleLabel.textAlignment = .natural
|
|
1266
1296
|
subtitleLabel.text = nil
|
|
1267
1297
|
subtitleLabel.lineBreakMode = .byTruncatingTail
|
|
1298
|
+
subtitleLabel.attributedText = nil
|
|
1299
|
+
subtitleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1300
|
+
subtitleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
1301
|
+
tertiaryLabel.attributedText = nil
|
|
1302
|
+
tertiaryLabel.lineBreakMode = .byTruncatingTail
|
|
1303
|
+
tertiaryLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1304
|
+
tertiaryLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
1268
1305
|
tertiaryLabel.text = nil
|
|
1269
1306
|
statusLabel.text = nil
|
|
1270
1307
|
statusLabel.topInset = 0
|
|
@@ -1291,6 +1328,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1291
1328
|
$0.isHidden = true
|
|
1292
1329
|
$0.isEnabled = true
|
|
1293
1330
|
$0.isUserInteractionEnabled = false
|
|
1331
|
+
// OneKey patch: a recycled badge must not retain an attributed title or font.
|
|
1332
|
+
$0.setAttributedTitle(nil, for: .normal)
|
|
1333
|
+
$0.marketLineHeight = nil
|
|
1334
|
+
$0.selectorSummaryLineHeight = nil
|
|
1335
|
+
$0.titleLabel?.font = nativeListFont(ofSize: 11, weight: .medium)
|
|
1336
|
+
$0.titleLabel?.numberOfLines = 1
|
|
1337
|
+
$0.contentHorizontalAlignment = .center
|
|
1338
|
+
$0.accessibilityLabel = nil
|
|
1339
|
+
$0.accessibilityTraits = .staticText
|
|
1294
1340
|
$0.setTitle(nil, for: .normal)
|
|
1295
1341
|
$0.setImage(nil, for: .normal)
|
|
1296
1342
|
$0.setTitleColor(nil, for: .normal)
|
|
@@ -2295,15 +2341,29 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2295
2341
|
}
|
|
2296
2342
|
}
|
|
2297
2343
|
rootStack.addArrangedSubview(mainStack)
|
|
2298
|
-
|
|
2344
|
+
// OneKey patch: opt in to the source Market row's content gap.
|
|
2345
|
+
// rootStack.setCustomSpacing(0, after: mainStack)
|
|
2346
|
+
rootStack.setCustomSpacing(CGFloat(style?.double("contentTrailingGap", default: 0) ?? 0), after: mainStack)
|
|
2299
2347
|
mainStack.spacing = CGFloat(style?.double("lineGap", default: 0) ?? 0)
|
|
2300
2348
|
titleRowStack.spacing = CGFloat(style?.double("titleBadgeGap", default: 4) ?? 4)
|
|
2349
|
+
// OneKey patch: opt in without changing the other row templates' filled layout.
|
|
2350
|
+
if style?.string("titleBadgeLayout") == "inline" {
|
|
2351
|
+
mainStack.alignment = .leading
|
|
2352
|
+
titleRowStack.setContentHuggingPriority(.required, for: .horizontal)
|
|
2353
|
+
}
|
|
2301
2354
|
show(titleLabel, item.data.string("title"), lines: style?.dictionary("title")?.int("lines", default: 1) ?? 1)
|
|
2302
2355
|
applyMarketTextStyle(titleLabel, data: style?.dictionary("title"), theme: theme, defaultSize: 16, defaultLineHeight: 24, defaultWeight: .medium, defaultColor: nativeListColor(theme, "primaryText", "#202020"))
|
|
2303
2356
|
let badges = Array(item.data.dictionaries("badges").prefix(marketBadgeButtons.count))
|
|
2304
2357
|
marketBadgeActionKeys = badges.map { $0["actionKey"] as? String }
|
|
2305
2358
|
for (index, badge) in badges.enumerated() {
|
|
2306
2359
|
let button = marketBadgeButtons[index]
|
|
2360
|
+
let badgeStyle = badge.dictionary("style")
|
|
2361
|
+
let badgeFontSize = CGFloat(badgeStyle?.double("fontSize", default: 11) ?? 11)
|
|
2362
|
+
let badgeFontWeight = marketFontWeight(badgeStyle?.string("fontWeight") ?? "", fallback: .medium)
|
|
2363
|
+
// OneKey patch: SizableText supplies tabular numerals for explicit Market metrics.
|
|
2364
|
+
let badgeFont = badgeStyle == nil
|
|
2365
|
+
? nativeListFont(ofSize: badgeFontSize, weight: badgeFontWeight)
|
|
2366
|
+
: nativeListTabularFont(ofSize: badgeFontSize, weight: badgeFontWeight)
|
|
2307
2367
|
let hasBuiltInIcon = badge.string("iconName") == "verified"
|
|
2308
2368
|
let hasRemoteIcon = badge.dictionary("icon") != nil
|
|
2309
2369
|
let hasIcon = hasBuiltInIcon || hasRemoteIcon
|
|
@@ -2326,6 +2386,12 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2326
2386
|
button.accessibilityTraits = button.isUserInteractionEnabled ? .button : .staticText
|
|
2327
2387
|
button.setTitle(text, for: .normal)
|
|
2328
2388
|
button.setTitleColor(foreground, for: .normal)
|
|
2389
|
+
button.titleLabel?.font = badgeFont
|
|
2390
|
+
if let lineHeight = badgeStyle?["lineHeight"] as? Double {
|
|
2391
|
+
setButtonLine(button, text: text, font: badgeFont, color: foreground, lineHeight: CGFloat(lineHeight))
|
|
2392
|
+
// The explicit text-only line box must not cover an adjacent icon.
|
|
2393
|
+
if hasIcon { button.marketLineHeight = nil }
|
|
2394
|
+
}
|
|
2329
2395
|
button.tintColor = foreground
|
|
2330
2396
|
button.backgroundColor = UIColor(
|
|
2331
2397
|
nativeListHex: badge.string("backgroundColor", default: ""),
|
|
@@ -2335,7 +2401,12 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2335
2401
|
)
|
|
2336
2402
|
let iconOnly = hasIcon && text.isEmpty
|
|
2337
2403
|
let iconSize: CGFloat = hasBuiltInIcon ? 16 : 14
|
|
2338
|
-
|
|
2404
|
+
// OneKey patch: preserve the native defaults unless the caller supplies padding.
|
|
2405
|
+
let padding = CGFloat(badgeStyle?.double("horizontalPadding", default: 5) ?? 5)
|
|
2406
|
+
let hasCustomPadding = badgeStyle?["horizontalPadding"] != nil
|
|
2407
|
+
let leftPadding = hasCustomPadding ? padding + (hasRemoteIcon ? iconSize + 2 : 0) : hasRemoteIcon ? 20 : hasIcon ? 3 : 5
|
|
2408
|
+
// button.contentEdgeInsets = UIEdgeInsets(top: 0, left: iconOnly ? 0 : hasRemoteIcon ? 20 : hasIcon ? 3 : 5, bottom: 0, right: iconOnly ? 0 : 5)
|
|
2409
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: iconOnly ? 0 : leftPadding, bottom: 0, right: iconOnly ? 0 : padding)
|
|
2339
2410
|
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: text.isEmpty ? 0 : 3)
|
|
2340
2411
|
button.titleEdgeInsets = .zero
|
|
2341
2412
|
button.imageView?.contentMode = .scaleAspectFit
|
|
@@ -2345,11 +2416,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2345
2416
|
for: .normal
|
|
2346
2417
|
)
|
|
2347
2418
|
}
|
|
2348
|
-
|
|
2349
|
-
let
|
|
2350
|
-
|
|
2351
|
-
).width
|
|
2352
|
-
let
|
|
2419
|
+
// OneKey patch: match source badge metrics at physical-pixel precision.
|
|
2420
|
+
// let height = button.heightAnchor.constraint(equalToConstant: 18)
|
|
2421
|
+
let height = button.heightAnchor.constraint(equalToConstant: CGFloat(badgeStyle?.double("height", default: 18) ?? 18))
|
|
2422
|
+
let textWidth = (text as NSString).size(withAttributes: [.font: badgeFont]).width
|
|
2423
|
+
let scale = max(1, traitCollection.displayScale)
|
|
2424
|
+
let roundedTextWidth = badgeStyle == nil ? ceil(textWidth) : ceil(textWidth * scale) / scale
|
|
2425
|
+
let extraWidth = hasCustomPadding ? padding * 2 + (hasIcon ? iconSize + 2 : 0) : hasIcon ? iconSize + 11 : 10
|
|
2426
|
+
// let width = button.widthAnchor.constraint(equalToConstant: iconOnly ? iconSize : ceil(textWidth) + (hasIcon ? iconSize + 11 : 10))
|
|
2427
|
+
let width = button.widthAnchor.constraint(equalToConstant: iconOnly ? iconSize : roundedTextWidth + extraWidth)
|
|
2353
2428
|
NSLayoutConstraint.activate([width, height])
|
|
2354
2429
|
selectorConstraints.append(contentsOf: [width, height])
|
|
2355
2430
|
if let icon = badge.dictionary("icon") {
|
|
@@ -2370,6 +2445,41 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2370
2445
|
subtitleLabel.attributedText = marketAttributedText(item.data.string("subtitle"), segments: item.data.dictionaries("subtitleSegments"), style: style?.dictionary("subtitle"), defaultSize: 14, defaultLineHeight: 20, defaultWeight: .regular, color: nativeListColor(theme, "secondaryText", "#646464"), defaultAlignment: .natural)
|
|
2371
2446
|
}
|
|
2372
2447
|
}
|
|
2448
|
+
// OneKey patch: preserve volume width while the localized name truncates.
|
|
2449
|
+
let subtitlePrefix = item.data.dictionary("subtitlePrefix")
|
|
2450
|
+
let subtitlePadding = CGFloat(style?.double("subtitleTrailingPadding", default: 0) ?? 0)
|
|
2451
|
+
if subtitlePrefix != nil || subtitlePadding > 0 {
|
|
2452
|
+
mainStack.removeArrangedSubview(subtitleLabel)
|
|
2453
|
+
subtitleLabel.removeFromSuperview()
|
|
2454
|
+
mainStack.removeArrangedSubview(tertiaryLabel)
|
|
2455
|
+
tertiaryLabel.removeFromSuperview()
|
|
2456
|
+
marketSubtitleStack.axis = .horizontal
|
|
2457
|
+
marketSubtitleStack.alignment = .center
|
|
2458
|
+
marketSubtitleStack.spacing = 0
|
|
2459
|
+
marketSubtitleStack.clipsToBounds = true
|
|
2460
|
+
show(tertiaryLabel, subtitlePrefix?.string("text") ?? "", lines: 1)
|
|
2461
|
+
applyMarketTextStyle(tertiaryLabel, data: subtitlePrefix?.dictionary("style"), theme: theme, defaultSize: 12, defaultLineHeight: 16, defaultWeight: .regular, defaultColor: nativeListColor(theme, "secondaryText", "#646464"))
|
|
2462
|
+
tertiaryLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
2463
|
+
tertiaryLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
2464
|
+
subtitleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
2465
|
+
subtitleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
2466
|
+
marketSubtitleStack.addArrangedSubview(tertiaryLabel)
|
|
2467
|
+
marketSubtitleStack.addArrangedSubview(subtitleLabel)
|
|
2468
|
+
marketSubtitleStack.addArrangedSubview(marketSubtitleSpacer)
|
|
2469
|
+
if !tertiaryLabel.isHidden && !subtitleLabel.isHidden {
|
|
2470
|
+
marketSubtitleStack.setCustomSpacing(CGFloat(subtitlePrefix?.double("gap", default: 4) ?? 4), after: tertiaryLabel)
|
|
2471
|
+
}
|
|
2472
|
+
mainStack.insertArrangedSubview(marketSubtitleStack, at: 1)
|
|
2473
|
+
let width = marketSubtitleStack.widthAnchor.constraint(equalTo: mainStack.widthAnchor, constant: -subtitlePadding)
|
|
2474
|
+
width.isActive = true
|
|
2475
|
+
selectorConstraints.append(width)
|
|
2476
|
+
if let maxWidth = subtitlePrefix?["maxWidth"] as? Double {
|
|
2477
|
+
let limit = tertiaryLabel.widthAnchor.constraint(lessThanOrEqualToConstant: CGFloat(maxWidth))
|
|
2478
|
+
limit.isActive = true
|
|
2479
|
+
selectorConstraints.append(limit)
|
|
2480
|
+
}
|
|
2481
|
+
marketSubtitleStack.isHidden = tertiaryLabel.isHidden && subtitleLabel.isHidden
|
|
2482
|
+
}
|
|
2373
2483
|
rootStack.addArrangedSubview(trailingStack)
|
|
2374
2484
|
trailingStack.axis = .horizontal
|
|
2375
2485
|
trailingStack.alignment = .center
|
|
@@ -3039,6 +3149,41 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3039
3149
|
rootTopConstraint.constant = 12
|
|
3040
3150
|
rootBottomConstraint.constant = -12
|
|
3041
3151
|
}
|
|
3152
|
+
if isMarket && variant == "retry" {
|
|
3153
|
+
let message = item.data.string("message")
|
|
3154
|
+
let text = item.data.string("actionText", default: "Retry")
|
|
3155
|
+
rootStack.axis = .vertical
|
|
3156
|
+
// The source tertiary Button has -5 vertical margins around its 30pt frame.
|
|
3157
|
+
rootStack.spacing = 7
|
|
3158
|
+
rootLeadingConstraint.constant = 32
|
|
3159
|
+
rootTrailingConstraint.constant = -32
|
|
3160
|
+
let height = CGFloat(item.data.double("height", default: message.isEmpty ? 52 : 120))
|
|
3161
|
+
let top = message.isEmpty ? 11 : max(32, (height - 56) / 2)
|
|
3162
|
+
rootTopConstraint.constant = top
|
|
3163
|
+
rootBottomConstraint.constant = -(height - top - (message.isEmpty ? 30 : 61))
|
|
3164
|
+
if !message.isEmpty {
|
|
3165
|
+
show(titleLabel, message, lines: 2)
|
|
3166
|
+
titleLabel.font = nativeListTabularFont(ofSize: 16)
|
|
3167
|
+
titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
|
|
3168
|
+
titleLabel.textAlignment = .center
|
|
3169
|
+
setLineHeight(titleLabel, text: message, lineHeight: 24)
|
|
3170
|
+
rootStack.addArrangedSubview(mainStack)
|
|
3171
|
+
}
|
|
3172
|
+
showAccessory(0, text, action: (item.data.string("actionKey"), nil))
|
|
3173
|
+
let button = accessoryButtons[0]
|
|
3174
|
+
button.backgroundColor = .clear
|
|
3175
|
+
button.layer.cornerRadius = 15
|
|
3176
|
+
setButtonLine(button, text: text, font: nativeListTabularFont(ofSize: 14, weight: .medium),
|
|
3177
|
+
color: nativeListColor(theme, "secondaryText", "#646464"), lineHeight: 20)
|
|
3178
|
+
let textWidth = button.intrinsicContentSize.width
|
|
3179
|
+
selectorConstraints.append(contentsOf: [
|
|
3180
|
+
button.widthAnchor.constraint(equalToConstant: textWidth + 18),
|
|
3181
|
+
button.heightAnchor.constraint(equalToConstant: 30),
|
|
3182
|
+
])
|
|
3183
|
+
NSLayoutConstraint.activate(selectorConstraints)
|
|
3184
|
+
rootStack.addArrangedSubview(trailingStack)
|
|
3185
|
+
return
|
|
3186
|
+
}
|
|
3042
3187
|
if variant == "loading" && item.data.string("loadingStyle") == "skeleton" {
|
|
3043
3188
|
rootLeadingConstraint.constant = 20
|
|
3044
3189
|
rootTrailingConstraint.constant = -20
|
|
@@ -3065,12 +3210,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3065
3210
|
return
|
|
3066
3211
|
}
|
|
3067
3212
|
if isMarket && variant == "noMatch" {
|
|
3068
|
-
|
|
3069
|
-
|
|
3213
|
+
let padding = max(32, (CGFloat(item.data.double("height", default: 88)) - 24) / 2)
|
|
3214
|
+
rootTopConstraint.constant = padding
|
|
3215
|
+
rootBottomConstraint.constant = -padding
|
|
3070
3216
|
rootStack.addArrangedSubview(mainStack)
|
|
3071
3217
|
mainStack.alignment = .center
|
|
3072
3218
|
mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
3073
|
-
titleLabel.font =
|
|
3219
|
+
titleLabel.font = nativeListTabularFont(ofSize: 16)
|
|
3074
3220
|
titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
|
|
3075
3221
|
titleLabel.textAlignment = .center
|
|
3076
3222
|
show(titleLabel, item.data.string("message"), lines: 1)
|
|
@@ -3080,6 +3226,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3080
3226
|
if isMarket && variant == "end" {
|
|
3081
3227
|
rootTopConstraint.constant = 16
|
|
3082
3228
|
rootBottomConstraint.constant = -16
|
|
3229
|
+
// OneKey patch: an empty title stack must not consume the dot's line height.
|
|
3230
|
+
titleRowStack.isHidden = true
|
|
3083
3231
|
rootStack.addArrangedSubview(mainStack)
|
|
3084
3232
|
mainStack.alignment = .center
|
|
3085
3233
|
mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
@@ -3558,7 +3706,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3558
3706
|
.foregroundColor: label.textColor as Any,
|
|
3559
3707
|
.paragraphStyle: paragraphStyle,
|
|
3560
3708
|
]
|
|
3561
|
-
if currentItem?.type == "market" || (currentItem?.data["height"] != nil && (["accountSelector", "walletSidebar"].contains(currentItem?.data.string("presentation") ?? "") || currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector")) || currentItem?.type == "system" && currentItem?.data.string("variant") == "warning" {
|
|
3709
|
+
if currentItem?.type == "market" || currentItem?.type == "system" && currentItem?.data.string("presentation") == "market" && currentItem?.data.string("variant") == "noMatch" || (currentItem?.data["height"] != nil && (["accountSelector", "walletSidebar"].contains(currentItem?.data.string("presentation") ?? "") || currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector")) || currentItem?.type == "system" && currentItem?.data.string("variant") == "warning" {
|
|
3562
3710
|
// OneKey patch: React Native centers font metrics inside explicit line heights.
|
|
3563
3711
|
let baselineOffset = max(0, (lineHeight - label.font.lineHeight) / 2)
|
|
3564
3712
|
// OneKey patch: TextKit's 14/20 headings align their baseline to the upper physical pixel.
|
|
@@ -3566,7 +3714,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3566
3714
|
let scale = window?.screen.scale ?? traitCollection.displayScale
|
|
3567
3715
|
attributes[.baselineOffset] = isSelectorHeading && scale > 0 ? ceil(baselineOffset * scale) / scale : baselineOffset
|
|
3568
3716
|
}
|
|
3569
|
-
if letterSpacing != 0 || currentItem?.type == "market" { attributes[.kern] = letterSpacing }
|
|
3717
|
+
if letterSpacing != 0 || currentItem?.type == "market" || currentItem?.data.string("presentation") == "market" { attributes[.kern] = letterSpacing }
|
|
3570
3718
|
label.attributedText = NSAttributedString(string: text, attributes: attributes)
|
|
3571
3719
|
}
|
|
3572
3720
|
|
|
@@ -3581,13 +3729,14 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3581
3729
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
3582
3730
|
paragraphStyle.minimumLineHeight = lineHeight
|
|
3583
3731
|
paragraphStyle.maximumLineHeight = lineHeight
|
|
3732
|
+
let isMarketText = currentItem?.type == "market" || currentItem?.type == "system" && currentItem?.data.string("presentation") == "market" && currentItem?.data.string("variant") == "retry"
|
|
3584
3733
|
let isSelectorValue = currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") != "summary"
|
|
3585
3734
|
let isSelectorSummary = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") == "summary"
|
|
3586
3735
|
(button as? NativeListAccessoryButton)?.selectorSummaryLineHeight = isSelectorSummary ? lineHeight : nil
|
|
3587
|
-
(button as? NativeListAccessoryButton)?.marketLineHeight =
|
|
3736
|
+
(button as? NativeListAccessoryButton)?.marketLineHeight = isMarketText ? lineHeight : nil
|
|
3588
3737
|
// OneKey patch: summary text uses its source line box; currency retains trailing alignment.
|
|
3589
3738
|
// Market's line box already handles alignment; source text starts at its origin.
|
|
3590
|
-
paragraphStyle.alignment = isSelectorValue ? .right : isSelectorSummary ||
|
|
3739
|
+
paragraphStyle.alignment = isSelectorValue ? .right : isSelectorSummary || isMarketText ? .natural : .center
|
|
3591
3740
|
if isSelectorSummary {
|
|
3592
3741
|
button.contentHorizontalAlignment = .leading
|
|
3593
3742
|
button.titleLabel?.textAlignment = .natural
|
|
@@ -3596,14 +3745,14 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3596
3745
|
button.contentHorizontalAlignment = .trailing
|
|
3597
3746
|
button.titleLabel?.textAlignment = .right
|
|
3598
3747
|
}
|
|
3599
|
-
let baselineOffset: CGFloat =
|
|
3748
|
+
let baselineOffset: CGFloat = isMarketText || currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil ? max(0, (lineHeight - font.lineHeight) / 2) : 0
|
|
3600
3749
|
var attributes: [NSAttributedString.Key: Any] = [
|
|
3601
3750
|
.font: font,
|
|
3602
3751
|
.foregroundColor: color,
|
|
3603
3752
|
.paragraphStyle: paragraphStyle,
|
|
3604
|
-
.baselineOffset:
|
|
3753
|
+
.baselineOffset: isMarketText && lineHeight == 20 && font.pointSize == 14 ? ceil(baselineOffset * max(1, traitCollection.displayScale)) / max(1, traitCollection.displayScale) : baselineOffset,
|
|
3605
3754
|
]
|
|
3606
|
-
if
|
|
3755
|
+
if isMarketText { attributes[.kern] = 0 }
|
|
3607
3756
|
button.setAttributedTitle(
|
|
3608
3757
|
NSAttributedString(string: text, attributes: attributes),
|
|
3609
3758
|
for: .normal
|
|
@@ -1052,6 +1052,7 @@ final class NativeListView: UIView {
|
|
|
1052
1052
|
let actionKey = item.data.string("longPressActionKey")
|
|
1053
1053
|
guard !actionKey.isEmpty else { return }
|
|
1054
1054
|
let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
|
|
1055
|
+
origin?.windowPoint = gesture.location(in: window)
|
|
1055
1056
|
handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
|
|
1056
1057
|
}
|
|
1057
1058
|
|
|
@@ -1478,6 +1479,9 @@ final class NativeListView: UIView {
|
|
|
1478
1479
|
? "rtl"
|
|
1479
1480
|
: "ltr",
|
|
1480
1481
|
]
|
|
1482
|
+
if let point = origin.windowPoint {
|
|
1483
|
+
anchor["windowPoint"] = ["x": point.x, "y": point.y]
|
|
1484
|
+
}
|
|
1481
1485
|
if let slot = origin.slot { anchor["slot"] = slot }
|
|
1482
1486
|
return anchor
|
|
1483
1487
|
}
|
package/lib/module/validation.js
CHANGED
|
@@ -84,9 +84,13 @@ function assertMarketTextStyle(style, path) {
|
|
|
84
84
|
}
|
|
85
85
|
function assertMarketStyle(style, path) {
|
|
86
86
|
if (!style) return;
|
|
87
|
-
for (const field of ['horizontalPadding', 'verticalPadding', 'leadingGap', 'titleBadgeGap', 'trailingGap']) {
|
|
87
|
+
for (const field of ['horizontalPadding', 'verticalPadding', 'leadingGap', 'titleBadgeGap', 'trailingGap', 'contentTrailingGap', 'subtitleTrailingPadding']) {
|
|
88
88
|
assertBoundedStyleNumber(style[field], `${path}.${field}`, 0, 64);
|
|
89
89
|
}
|
|
90
|
+
// OneKey patch: validate the opt-in Market badge layout.
|
|
91
|
+
if (style.titleBadgeLayout !== undefined && style.titleBadgeLayout !== 'inline') {
|
|
92
|
+
fail(`${path}.titleBadgeLayout`, 'must be inline when provided');
|
|
93
|
+
}
|
|
90
94
|
assertBoundedStyleNumber(style.lineGap, `${path}.lineGap`, 0, 16);
|
|
91
95
|
assertBoundedStyleNumber(style.changeWidth, `${path}.changeWidth`, 1, 160);
|
|
92
96
|
assertBoundedStyleNumber(style.changeHeight, `${path}.changeHeight`, 1, 160);
|
|
@@ -111,6 +115,13 @@ function assertMarketRow(row, path) {
|
|
|
111
115
|
}
|
|
112
116
|
assertText(row.title, `${path}.title`);
|
|
113
117
|
assertText(row.subtitle, `${path}.subtitle`);
|
|
118
|
+
// OneKey patch: validate the independently laid out Market name.
|
|
119
|
+
if (row.subtitlePrefix) {
|
|
120
|
+
assertText(row.subtitlePrefix.text, `${path}.subtitlePrefix.text`);
|
|
121
|
+
assertBoundedStyleNumber(row.subtitlePrefix.gap, `${path}.subtitlePrefix.gap`, 0, 64);
|
|
122
|
+
assertBoundedStyleNumber(row.subtitlePrefix.maxWidth, `${path}.subtitlePrefix.maxWidth`, 1, 320);
|
|
123
|
+
assertMarketTextStyle(row.subtitlePrefix.style, `${path}.subtitlePrefix.style`);
|
|
124
|
+
}
|
|
114
125
|
assertText(row.price, `${path}.price`);
|
|
115
126
|
assertText(row.change.text, `${path}.change.text`);
|
|
116
127
|
assertLeadingVisual(row.leading, `${path}.leading`);
|
|
@@ -140,6 +151,10 @@ function assertMarketRow(row, path) {
|
|
|
140
151
|
}
|
|
141
152
|
badgeKeys.add(badge.key);
|
|
142
153
|
assertText(badge.text, `${badgePath}.text`);
|
|
154
|
+
// OneKey patch: share typography bounds with Market text styles.
|
|
155
|
+
assertMarketTextStyle(badge.style, `${badgePath}.style`);
|
|
156
|
+
assertBoundedStyleNumber(badge.style?.height, `${badgePath}.style.height`, 1, 64);
|
|
157
|
+
assertBoundedStyleNumber(badge.style?.horizontalPadding, `${badgePath}.style.horizontalPadding`, 0, 32);
|
|
143
158
|
if (badge.iconName !== undefined && badge.iconName !== 'verified') {
|
|
144
159
|
fail(`${badgePath}.iconName`, 'must be verified when provided');
|
|
145
160
|
}
|
|
@@ -451,6 +466,7 @@ function assertRow(row, index, path = `rows[${index}]`) {
|
|
|
451
466
|
}
|
|
452
467
|
if (row.variant === 'retry') {
|
|
453
468
|
assertKey(row.actionKey, `${path}.actionKey`);
|
|
469
|
+
assertText(row.actionText, `${path}.actionText`);
|
|
454
470
|
}
|
|
455
471
|
break;
|
|
456
472
|
}
|
|
@@ -50,7 +50,12 @@ export type MarketRowStyle = Readonly<{
|
|
|
50
50
|
/** Space between title and subtitle; 0 by default, bounded to 0..16. */
|
|
51
51
|
lineGap?: number;
|
|
52
52
|
titleBadgeGap?: number;
|
|
53
|
+
/** OneKey patch: keep badges next to the intrinsic title width. */
|
|
54
|
+
titleBadgeLayout?: 'inline';
|
|
53
55
|
trailingGap?: number;
|
|
56
|
+
/** OneKey patch: preserve separate Market content and subtitle insets. */
|
|
57
|
+
contentTrailingGap?: number;
|
|
58
|
+
subtitleTrailingPadding?: number;
|
|
54
59
|
image?: MarketImageStyle;
|
|
55
60
|
title?: MarketTextStyle;
|
|
56
61
|
subtitle?: MarketTextStyle;
|
|
@@ -71,6 +76,14 @@ export type MarketBadgeModel = Readonly<{
|
|
|
71
76
|
backgroundColor?: string;
|
|
72
77
|
actionKey?: string;
|
|
73
78
|
accessibilityLabel?: string;
|
|
79
|
+
/** OneKey patch: optional Market badge metrics; legacy native defaults remain unchanged. */
|
|
80
|
+
style?: Readonly<{
|
|
81
|
+
fontSize?: number;
|
|
82
|
+
fontWeight?: MarketTextStyle['fontWeight'];
|
|
83
|
+
lineHeight?: number;
|
|
84
|
+
height?: number;
|
|
85
|
+
horizontalPadding?: number;
|
|
86
|
+
}>;
|
|
74
87
|
}>;
|
|
75
88
|
export type MarketChangeModel = Readonly<{
|
|
76
89
|
text: string;
|
|
@@ -319,6 +332,13 @@ export type MarketRow = RowBase & Readonly<{
|
|
|
319
332
|
leading: LeadingVisual;
|
|
320
333
|
title: string;
|
|
321
334
|
subtitle?: string;
|
|
335
|
+
/** OneKey patch: localized name shrinks independently of the volume. */
|
|
336
|
+
subtitlePrefix?: Readonly<{
|
|
337
|
+
text: string;
|
|
338
|
+
gap?: number;
|
|
339
|
+
maxWidth?: number;
|
|
340
|
+
style?: MarketTextStyle;
|
|
341
|
+
}>;
|
|
322
342
|
subtitleSegments?: readonly ValueTextSegment[];
|
|
323
343
|
price: string;
|
|
324
344
|
priceSegments?: readonly ValueTextSegment[];
|
|
@@ -415,6 +435,7 @@ export type SystemRow = RowBase & (Readonly<{
|
|
|
415
435
|
presentation?: 'market';
|
|
416
436
|
message: string;
|
|
417
437
|
actionKey: string;
|
|
438
|
+
actionText?: string;
|
|
418
439
|
}> | Readonly<{
|
|
419
440
|
type: 'system';
|
|
420
441
|
variant: 'warning';
|
|
@@ -562,6 +583,11 @@ export type NativeListActionAnchor = Readonly<{
|
|
|
562
583
|
token: string;
|
|
563
584
|
/** Window-relative logical units: CSS px on Web, points on iOS, dp on Android. */
|
|
564
585
|
windowRect: NativeListWindowRect;
|
|
586
|
+
/** Actual long-press point, in the same logical units as windowRect. */
|
|
587
|
+
windowPoint?: Readonly<{
|
|
588
|
+
x: number;
|
|
589
|
+
y: number;
|
|
590
|
+
}>;
|
|
565
591
|
source: NativeListActionSource;
|
|
566
592
|
slot?: number;
|
|
567
593
|
generation: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-list",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.116",
|
|
4
4
|
"description": "Template-driven native RecyclerView and UICollectionView for React Native",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"typescript": "^5.9.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@onekeyfe/react-native-image": "3.0.
|
|
87
|
-
"@onekeyfe/react-native-native-logger": "3.0.
|
|
86
|
+
"@onekeyfe/react-native-image": "3.0.116",
|
|
87
|
+
"@onekeyfe/react-native-native-logger": "3.0.116",
|
|
88
88
|
"react": "*",
|
|
89
89
|
"react-native": "*",
|
|
90
90
|
"react-native-nitro-modules": "0.37.0"
|
package/src/models.ts
CHANGED
|
@@ -57,7 +57,12 @@ export type MarketRowStyle = Readonly<{
|
|
|
57
57
|
/** Space between title and subtitle; 0 by default, bounded to 0..16. */
|
|
58
58
|
lineGap?: number;
|
|
59
59
|
titleBadgeGap?: number;
|
|
60
|
+
/** OneKey patch: keep badges next to the intrinsic title width. */
|
|
61
|
+
titleBadgeLayout?: 'inline';
|
|
60
62
|
trailingGap?: number;
|
|
63
|
+
/** OneKey patch: preserve separate Market content and subtitle insets. */
|
|
64
|
+
contentTrailingGap?: number;
|
|
65
|
+
subtitleTrailingPadding?: number;
|
|
61
66
|
image?: MarketImageStyle;
|
|
62
67
|
title?: MarketTextStyle;
|
|
63
68
|
subtitle?: MarketTextStyle;
|
|
@@ -79,6 +84,14 @@ export type MarketBadgeModel = Readonly<{
|
|
|
79
84
|
backgroundColor?: string;
|
|
80
85
|
actionKey?: string;
|
|
81
86
|
accessibilityLabel?: string;
|
|
87
|
+
/** OneKey patch: optional Market badge metrics; legacy native defaults remain unchanged. */
|
|
88
|
+
style?: Readonly<{
|
|
89
|
+
fontSize?: number;
|
|
90
|
+
fontWeight?: MarketTextStyle['fontWeight'];
|
|
91
|
+
lineHeight?: number;
|
|
92
|
+
height?: number;
|
|
93
|
+
horizontalPadding?: number;
|
|
94
|
+
}>;
|
|
82
95
|
}>;
|
|
83
96
|
|
|
84
97
|
export type MarketChangeModel = Readonly<{
|
|
@@ -336,6 +349,13 @@ export type MarketRow = RowBase &
|
|
|
336
349
|
leading: LeadingVisual;
|
|
337
350
|
title: string;
|
|
338
351
|
subtitle?: string;
|
|
352
|
+
/** OneKey patch: localized name shrinks independently of the volume. */
|
|
353
|
+
subtitlePrefix?: Readonly<{
|
|
354
|
+
text: string;
|
|
355
|
+
gap?: number;
|
|
356
|
+
maxWidth?: number;
|
|
357
|
+
style?: MarketTextStyle;
|
|
358
|
+
}>;
|
|
339
359
|
subtitleSegments?: readonly ValueTextSegment[];
|
|
340
360
|
price: string;
|
|
341
361
|
priceSegments?: readonly ValueTextSegment[];
|
|
@@ -434,6 +454,7 @@ export type SystemRow = RowBase &
|
|
|
434
454
|
presentation?: 'market';
|
|
435
455
|
message: string;
|
|
436
456
|
actionKey: string;
|
|
457
|
+
actionText?: string;
|
|
437
458
|
}>
|
|
438
459
|
| Readonly<{
|
|
439
460
|
type: 'system';
|
|
@@ -764,6 +785,8 @@ export type NativeListActionAnchor = Readonly<{
|
|
|
764
785
|
token: string;
|
|
765
786
|
/** Window-relative logical units: CSS px on Web, points on iOS, dp on Android. */
|
|
766
787
|
windowRect: NativeListWindowRect;
|
|
788
|
+
/** Actual long-press point, in the same logical units as windowRect. */
|
|
789
|
+
windowPoint?: Readonly<{ x: number; y: number }>;
|
|
767
790
|
source: NativeListActionSource;
|
|
768
791
|
slot?: number;
|
|
769
792
|
generation: number;
|
package/src/validation.ts
CHANGED
|
@@ -162,9 +162,18 @@ function assertMarketStyle(
|
|
|
162
162
|
'leadingGap',
|
|
163
163
|
'titleBadgeGap',
|
|
164
164
|
'trailingGap',
|
|
165
|
+
'contentTrailingGap',
|
|
166
|
+
'subtitleTrailingPadding',
|
|
165
167
|
] as const) {
|
|
166
168
|
assertBoundedStyleNumber(style[field], `${path}.${field}`, 0, 64);
|
|
167
169
|
}
|
|
170
|
+
// OneKey patch: validate the opt-in Market badge layout.
|
|
171
|
+
if (
|
|
172
|
+
style.titleBadgeLayout !== undefined &&
|
|
173
|
+
style.titleBadgeLayout !== 'inline'
|
|
174
|
+
) {
|
|
175
|
+
fail(`${path}.titleBadgeLayout`, 'must be inline when provided');
|
|
176
|
+
}
|
|
168
177
|
assertBoundedStyleNumber(style.lineGap, `${path}.lineGap`, 0, 16);
|
|
169
178
|
assertBoundedStyleNumber(style.changeWidth, `${path}.changeWidth`, 1, 160);
|
|
170
179
|
assertBoundedStyleNumber(style.changeHeight, `${path}.changeHeight`, 1, 160);
|
|
@@ -211,6 +220,26 @@ function assertMarketRow(row: MarketRow, path: string): void {
|
|
|
211
220
|
}
|
|
212
221
|
assertText(row.title, `${path}.title`);
|
|
213
222
|
assertText(row.subtitle, `${path}.subtitle`);
|
|
223
|
+
// OneKey patch: validate the independently laid out Market name.
|
|
224
|
+
if (row.subtitlePrefix) {
|
|
225
|
+
assertText(row.subtitlePrefix.text, `${path}.subtitlePrefix.text`);
|
|
226
|
+
assertBoundedStyleNumber(
|
|
227
|
+
row.subtitlePrefix.gap,
|
|
228
|
+
`${path}.subtitlePrefix.gap`,
|
|
229
|
+
0,
|
|
230
|
+
64
|
|
231
|
+
);
|
|
232
|
+
assertBoundedStyleNumber(
|
|
233
|
+
row.subtitlePrefix.maxWidth,
|
|
234
|
+
`${path}.subtitlePrefix.maxWidth`,
|
|
235
|
+
1,
|
|
236
|
+
320
|
|
237
|
+
);
|
|
238
|
+
assertMarketTextStyle(
|
|
239
|
+
row.subtitlePrefix.style,
|
|
240
|
+
`${path}.subtitlePrefix.style`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
214
243
|
assertText(row.price, `${path}.price`);
|
|
215
244
|
assertText(row.change.text, `${path}.change.text`);
|
|
216
245
|
assertLeadingVisual(row.leading, `${path}.leading`);
|
|
@@ -246,6 +275,20 @@ function assertMarketRow(row: MarketRow, path: string): void {
|
|
|
246
275
|
}
|
|
247
276
|
badgeKeys.add(badge.key);
|
|
248
277
|
assertText(badge.text, `${badgePath}.text`);
|
|
278
|
+
// OneKey patch: share typography bounds with Market text styles.
|
|
279
|
+
assertMarketTextStyle(badge.style, `${badgePath}.style`);
|
|
280
|
+
assertBoundedStyleNumber(
|
|
281
|
+
badge.style?.height,
|
|
282
|
+
`${badgePath}.style.height`,
|
|
283
|
+
1,
|
|
284
|
+
64
|
|
285
|
+
);
|
|
286
|
+
assertBoundedStyleNumber(
|
|
287
|
+
badge.style?.horizontalPadding,
|
|
288
|
+
`${badgePath}.style.horizontalPadding`,
|
|
289
|
+
0,
|
|
290
|
+
32
|
|
291
|
+
);
|
|
249
292
|
if (badge.iconName !== undefined && badge.iconName !== 'verified') {
|
|
250
293
|
fail(`${badgePath}.iconName`, 'must be verified when provided');
|
|
251
294
|
}
|
|
@@ -797,6 +840,7 @@ function assertRow(
|
|
|
797
840
|
}
|
|
798
841
|
if (row.variant === 'retry') {
|
|
799
842
|
assertKey(row.actionKey, `${path}.actionKey`);
|
|
843
|
+
assertText(row.actionText, `${path}.actionText`);
|
|
800
844
|
}
|
|
801
845
|
break;
|
|
802
846
|
}
|