@onekeyfe/react-native-native-list 6.7.0-alpha.403 → 6.7.0-alpha.405
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/NativeListIdentityRenderer.kt +3 -82
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +3 -6
- package/docs/ROW_TEMPLATES.md +1 -8
- package/docs/SPEC.md +8 -84
- package/ios/NativeListIdentityRenderer.swift +6 -86
- package/lib/module/models.js +0 -2
- package/lib/module/validation.js +5 -71
- package/lib/module/web/NativeListWebEngine.js +1 -1
- package/lib/module/web/templates/IdentityRowRenderer.js +0 -91
- package/lib/typescript/src/models.d.ts +1 -21
- package/package.json +3 -3
- package/src/models.ts +0 -23
- package/src/validation.ts +4 -121
- package/src/web/NativeListWebEngine.ts +1 -1
- package/src/web/templates/IdentityRowRenderer.ts +0 -138
- package/android/src/main/java/com/onekey/nativelist/NativeListTitleSegmentLayout.kt +0 -38
- package/android/src/main/java/com/onekey/nativelist/NativeListTitleSegmentsView.kt +0 -105
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListTitleSegmentLayoutTest.kt +0 -65
- package/ios/NativeListTitleSegmentLayout.swift +0 -32
- package/ios/NativeListTitleSegmentsLabel.swift +0 -80
|
@@ -31,8 +31,6 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
31
31
|
private val titleLine = PackedTitleLineLayout(context)
|
|
32
32
|
private val title = NativeListTextView(context)
|
|
33
33
|
private val titleIcon = OneKeyIconView(context)
|
|
34
|
-
private val titleLeadingIcon = OneKeyIconView(context)
|
|
35
|
-
private val titleSegments = NativeListTitleSegmentsView(context)
|
|
36
34
|
private var titleImage: NativeListImageSlot? = null
|
|
37
35
|
private val subtitle = NativeListTextView(context)
|
|
38
36
|
private val tertiary = NativeListTextView(context)
|
|
@@ -111,13 +109,8 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
111
109
|
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
112
110
|
titleLine.orientation = HORIZONTAL
|
|
113
111
|
titleLine.gravity = Gravity.CENTER_VERTICAL
|
|
114
|
-
titleLine.addView(titleLeadingIcon, LayoutParams(dp(16), dp(16)).apply { marginEnd = dp(6) })
|
|
115
112
|
titleLine.addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
116
|
-
titleLine.addView(titleSegments, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
117
113
|
titleLine.addView(titleIcon, LayoutParams(dp(20), dp(20)).apply { marginStart = dp(4) })
|
|
118
|
-
titleLine.titleChild = title
|
|
119
|
-
titleLeadingIcon.visibility = GONE
|
|
120
|
-
titleSegments.visibility = GONE
|
|
121
114
|
titleLine.addView(badgeLine)
|
|
122
115
|
trailingColumn.onAction = { key, view, slot, target ->
|
|
123
116
|
emitAction(key, view, "trailingAccessory", slot, target, trailingColumn.anchorInset(view))
|
|
@@ -230,12 +223,6 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
230
223
|
titleIcon.visibility = GONE
|
|
231
224
|
titleIcon.contentDescription = null
|
|
232
225
|
titleIcon.setOnClickListener(null)
|
|
233
|
-
titleLeadingIcon.visibility = GONE
|
|
234
|
-
titleLeadingIcon.contentDescription = null
|
|
235
|
-
titleSegments.visibility = GONE
|
|
236
|
-
titleSegments.clearRuns()
|
|
237
|
-
titleSegments.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
|
238
|
-
titleLine.titleChild = title
|
|
239
226
|
for (view in listOf(title, subtitle, tertiary, badgeLine)) {
|
|
240
227
|
view.text = ""
|
|
241
228
|
view.visibility = GONE
|
|
@@ -356,46 +343,6 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
356
343
|
}
|
|
357
344
|
// Legacy: overhanging selector icons/checkboxes must not be clipped by row padding.
|
|
358
345
|
if (trailingColumn.parent === this && trailingColumn.requestsUnclippedHost) clipToPadding = false
|
|
359
|
-
if (titleSegments.visibility == VISIBLE) bindTitleRuns(item, theme)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Runs take the title's final text appearance and color unless they set
|
|
363
|
-
// their own tone or weight.
|
|
364
|
-
private fun bindTitleRuns(item: NativeListItem, theme: JSONObject?) {
|
|
365
|
-
titleSegments.typeface = title.typeface
|
|
366
|
-
titleSegments.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, title.textSize)
|
|
367
|
-
titleSegments.includeFontPadding = title.includeFontPadding
|
|
368
|
-
titleSegments.letterSpacing = title.letterSpacing
|
|
369
|
-
titleSegments.opticalOffsetY = title.opticalOffsetY
|
|
370
|
-
titleSegments.maxLines = 1
|
|
371
|
-
titleSegments.ellipsize = TextUtils.TruncateAt.END
|
|
372
|
-
TextViewCompat.setLineHeight(titleSegments, title.lineHeight)
|
|
373
|
-
val baseColor = title.currentTextColor
|
|
374
|
-
titleSegments.setTextColor(baseColor)
|
|
375
|
-
val segments = item.json.optJSONArray("titleSegments") ?: return
|
|
376
|
-
val runs =
|
|
377
|
-
(0 until segments.length()).mapNotNull { index ->
|
|
378
|
-
val segment = segments.optJSONObject(index) ?: return@mapNotNull null
|
|
379
|
-
NativeListTitleSegmentsView.Run(
|
|
380
|
-
text = segment.optString("text"),
|
|
381
|
-
typeface =
|
|
382
|
-
when (segment.optString("fontWeight")) {
|
|
383
|
-
"medium" -> fonts.medium(context)
|
|
384
|
-
"regular" -> fonts.regular(context)
|
|
385
|
-
else -> null
|
|
386
|
-
},
|
|
387
|
-
color =
|
|
388
|
-
when (segment.optString("tone")) {
|
|
389
|
-
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
390
|
-
"primary" -> color(theme, "primaryText", "#000000DF")
|
|
391
|
-
else -> baseColor
|
|
392
|
-
},
|
|
393
|
-
shrink = segment.optBoolean("shrink", false),
|
|
394
|
-
maxWidthRatio =
|
|
395
|
-
if (segment.has("maxWidthRatio")) segment.optDouble("maxWidthRatio").toFloat() else null,
|
|
396
|
-
)
|
|
397
|
-
}
|
|
398
|
-
titleSegments.setRuns(runs, item.json.optString("title"))
|
|
399
346
|
}
|
|
400
347
|
|
|
401
348
|
private fun styleGap(column: LinearLayout, gap: Int) {
|
|
@@ -529,8 +476,7 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
529
476
|
val imageWidth = titleImage?.view?.takeIf { it.visibility == VISIBLE }?.let {
|
|
530
477
|
it.layoutParams.width + dp(4)
|
|
531
478
|
} ?: 0
|
|
532
|
-
val icon = (if (titleIcon.visibility == VISIBLE) dp(24) else 0) + imageWidth
|
|
533
|
-
(if (titleLeadingIcon.visibility == VISIBLE) dp(22) else 0)
|
|
479
|
+
val icon = (if (titleIcon.visibility == VISIBLE) dp(24) else 0) + imageWidth
|
|
534
480
|
(inner * ratio).roundToInt()
|
|
535
481
|
.minus(leading.width + leading.marginEnd + icon)
|
|
536
482
|
.coerceAtLeast(0)
|
|
@@ -541,10 +487,9 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
541
487
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
542
488
|
if (titleSuffixLabels.isNotEmpty()) {
|
|
543
489
|
val fixedWidth =
|
|
544
|
-
(
|
|
490
|
+
(1 until titleLine.childCount).sumOf { index ->
|
|
545
491
|
val view = titleLine.getChildAt(index)
|
|
546
|
-
if (view.visibility == GONE ||
|
|
547
|
-
titleSuffixLabels.any { it === view }) 0
|
|
492
|
+
if (view.visibility == GONE || titleSuffixLabels.any { it === view }) 0
|
|
548
493
|
else {
|
|
549
494
|
val margins = view.layoutParams as MarginLayoutParams
|
|
550
495
|
view.measuredWidth + margins.leftMargin + margins.rightMargin
|
|
@@ -808,30 +753,6 @@ internal class NativeListIdentityRowView(context: ThemedReactContext, fonts: Nat
|
|
|
808
753
|
titleLine.packsChildrenAtStart = true
|
|
809
754
|
title.ellipsize = TextUtils.TruncateAt.END
|
|
810
755
|
showText(title, item.json.optString("title"), item.json.optInt("titleLines", 1))
|
|
811
|
-
// Runs replace the drawn title except where `title` itself is highlighted;
|
|
812
|
-
// their content is bound once the title style is final.
|
|
813
|
-
if ((item.json.optJSONArray("titleSegments")?.length() ?: 0) > 0 &&
|
|
814
|
-
(item.json.optJSONArray("titleMatch")?.length() ?: 0) == 0
|
|
815
|
-
) {
|
|
816
|
-
title.visibility = GONE
|
|
817
|
-
titleSegments.visibility = VISIBLE
|
|
818
|
-
titleLine.titleChild = titleSegments
|
|
819
|
-
}
|
|
820
|
-
item.json.optJSONObject("titleLeadingIcon")?.let { icon ->
|
|
821
|
-
titleLeadingIcon.iconName = icon.optString("name")
|
|
822
|
-
titleLeadingIcon.glyphSizeDp = 16
|
|
823
|
-
titleLeadingIcon.useSourceScale = sourceScale
|
|
824
|
-
titleLeadingIcon.tintColor = safeColor(
|
|
825
|
-
icon.optString("tintColor").takeIf(String::isNotEmpty),
|
|
826
|
-
color(theme, "iconSubdued", "#00000072"),
|
|
827
|
-
)
|
|
828
|
-
titleLeadingIcon.contentDescription =
|
|
829
|
-
icon.optString("accessibilityLabel").takeIf(String::isNotEmpty)
|
|
830
|
-
titleLeadingIcon.importantForAccessibility =
|
|
831
|
-
if (titleLeadingIcon.contentDescription == null) View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
832
|
-
else View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
|
833
|
-
titleLeadingIcon.visibility = VISIBLE
|
|
834
|
-
}
|
|
835
756
|
item.json.optJSONArray("titleSuffixes")?.let { suffixes ->
|
|
836
757
|
for (index in 0 until suffixes.length()) {
|
|
837
758
|
val suffix = suffixes.optJSONObject(index) ?: continue
|
|
@@ -111,9 +111,6 @@ internal class PackedTitleLineLayout(context: android.content.Context) : LinearL
|
|
|
111
111
|
var packsChildrenAtStart = false
|
|
112
112
|
// OneKey patch: optional cap for the Market subtitle's localized name.
|
|
113
113
|
var leadingTextMaxWidth = Int.MAX_VALUE
|
|
114
|
-
// The text view that takes what the other children leave; the first child
|
|
115
|
-
// when unset, so a glyph may precede the title once this names it.
|
|
116
|
-
var titleChild: TextView? = null
|
|
117
114
|
|
|
118
115
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
119
116
|
if (!packsChildrenAtStart || childCount < 2) {
|
|
@@ -121,13 +118,13 @@ internal class PackedTitleLineLayout(context: android.content.Context) : LinearL
|
|
|
121
118
|
return
|
|
122
119
|
}
|
|
123
120
|
|
|
124
|
-
val title =
|
|
121
|
+
val title = getChildAt(0)
|
|
125
122
|
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
|
126
123
|
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
|
127
124
|
var accessoryWidth = 0
|
|
128
|
-
for (index in
|
|
125
|
+
for (index in 1 until childCount) {
|
|
129
126
|
val child = getChildAt(index)
|
|
130
|
-
if (child
|
|
127
|
+
if (child.visibility == GONE) continue
|
|
131
128
|
measureChildWithMargins(
|
|
132
129
|
child,
|
|
133
130
|
widthMeasureSpec,
|
package/docs/ROW_TEMPLATES.md
CHANGED
|
@@ -54,14 +54,7 @@ separately from source implementation in STYLE_SPEC §9.
|
|
|
54
54
|
|
|
55
55
|
- **Use:** an identifiable asset, account, wallet, network or setting.
|
|
56
56
|
- **Required data:** `leading`, `title`; optional `subtitle`, `tertiary`, `badges`,
|
|
57
|
-
`trailing`, `leadingAction`, `titleIcon`, Web-only `secondaryTitleIcon`,
|
|
58
|
-
`titleLeadingIcon`, match ranges and structured subtitle segments.
|
|
59
|
-
- **Title runs:** optional `titleSegments` draw 1...4 runs with their own tone and
|
|
60
|
-
weight in place of `title`, on one line. One run may shrink; the others keep
|
|
61
|
-
their width under an optional `maxWidthRatio` cap. `title` stays the spoken
|
|
62
|
-
text. See SPEC.md.
|
|
63
|
-
- **Leading title icon:** optional `titleLeadingIcon` draws a 16-point glyph 6
|
|
64
|
-
points before the title; it is decorative unless labelled.
|
|
57
|
+
`trailing`, `leadingAction`, `titleIcon`, Web-only `secondaryTitleIcon`, match ranges and structured subtitle segments.
|
|
65
58
|
- **Static title icon:** optional 20-point icon immediately after the title with
|
|
66
59
|
a 4-point gap. It has no click action; an optional Web `hoverActionKey`
|
|
67
60
|
emits an icon-anchored tooltip request on hover or touch. A trailing `button` is the fixed small
|
package/docs/SPEC.md
CHANGED
|
@@ -101,90 +101,6 @@ and Web acceptance must cover narrow addresses, dynamic type/fonts, long press
|
|
|
101
101
|
versus tap, keyboard interaction, and the horizontal warning border. Source or
|
|
102
102
|
build success alone is not runtime evidence.
|
|
103
103
|
|
|
104
|
-
### Identity title segments and leading title icon
|
|
105
|
-
|
|
106
|
-
Status: implemented in source for iOS, Android and Web; device and Web runtime
|
|
107
|
-
acceptance is pending. These fields travel in snapshot/patch JSON and do not
|
|
108
|
-
change the Nitro ABI. They serve recipient rows whose title is a wallet and an
|
|
109
|
-
account name in two styles, and address book rows marked by an icon before the
|
|
110
|
-
name.
|
|
111
|
-
|
|
112
|
-
`identity.titleSegments` is an optional list of 1...4 runs drawn on the title
|
|
113
|
-
line in place of `title`:
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
titleSegments?: readonly {
|
|
117
|
-
text: string;
|
|
118
|
-
tone?: 'primary' | 'secondary';
|
|
119
|
-
fontWeight?: 'regular' | 'medium';
|
|
120
|
-
shrink?: boolean;
|
|
121
|
-
maxWidthRatio?: number; // (0, 1]
|
|
122
|
-
}[];
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
- `title` stays required. It is the spoken label and the fallback text, so it
|
|
126
|
-
carries the same words as the runs; every renderer exposes `title`, not the
|
|
127
|
-
runs, to accessibility.
|
|
128
|
-
- Each run uses the title's font family, size and line height, including
|
|
129
|
-
`style.title` and the configured fonts. `tone` picks the primary or secondary
|
|
130
|
-
text color and defaults to the title color; `fontWeight` picks the weight and
|
|
131
|
-
defaults to the title weight.
|
|
132
|
-
- The runs share one line. `titleLines` and `style.title.lines` do not apply.
|
|
133
|
-
- Layout. The runs take the whole width the title line leaves after the
|
|
134
|
-
leading title icon and the title accessories (title icon, title image,
|
|
135
|
-
suffixes, badges), so those accessories sit at the line's end rather than
|
|
136
|
-
right after the text. W is that width:
|
|
137
|
-
1. A run's base width is its natural width, capped at `maxWidthRatio x W`
|
|
138
|
-
when given. A run longer than its cap truncates at the cap with a tail
|
|
139
|
-
ellipsis.
|
|
140
|
-
2. At most one run sets `shrink: true`. Every other run keeps its base width.
|
|
141
|
-
3. The shrinking run gets `min(base, W - sum of the other runs)` and
|
|
142
|
-
truncates with a tail ellipsis; it may shrink to zero width.
|
|
143
|
-
4. When the runs that do not shrink exceed W on their own, the line is cut at
|
|
144
|
-
its end. Native renderers end the last visible run with an ellipsis; Web
|
|
145
|
-
clips it. This divergence is accepted: callers avoid the case with
|
|
146
|
-
`maxWidthRatio`.
|
|
147
|
-
- Development validation rejects an empty or oversized list, blank or
|
|
148
|
-
oversized text (the 4096-code-unit text bound), unknown keys, tones or
|
|
149
|
-
weights, a ratio outside `(0, 1]`, more than one shrinking run, `titleMatch`
|
|
150
|
-
on the same row (its ranges index `title`), and
|
|
151
|
-
`presentation: 'walletSidebar'`. Production treats the runs as decoration:
|
|
152
|
-
an invalid list is dropped and the row shows `title`. Renderers draw `title`
|
|
153
|
-
instead of runs on a row that also has `titleMatch`, and ignore runs and the
|
|
154
|
-
leading icon with `presentation: 'walletSidebar'`.
|
|
155
|
-
|
|
156
|
-
`identity.titleLeadingIcon` is an optional `{ name, tintColor?,
|
|
157
|
-
accessibilityLabel? }`: a 16-point glyph before the title text or its runs,
|
|
158
|
-
with a 6-point gap, vertically centered on the title line. The name comes from
|
|
159
|
-
the same closed icon set as `titleIcon`; an unmapped name leaves an empty
|
|
160
|
-
16-point slot. `tintColor` is a hex `#RRGGBB` or `#RRGGBBAA` color and defaults
|
|
161
|
-
to the theme's `iconSubdued`. Without `accessibilityLabel` the glyph is hidden
|
|
162
|
-
from accessibility. It has no action of its own. Development validation
|
|
163
|
-
rejects unknown keys, a blank name, a malformed color and
|
|
164
|
-
`presentation: 'walletSidebar'`; production drops an invalid icon.
|
|
165
|
-
|
|
166
|
-
Both fields are identity patch fields. Omission in a full snapshot removes
|
|
167
|
-
them; omission in a patch keeps the current value. Rebind and recycle remove the
|
|
168
|
-
previous runs and glyph, so a row without them never shows another row's.
|
|
169
|
-
|
|
170
|
-
| Platform | Title runs | Leading icon |
|
|
171
|
-
| --- | --- | --- |
|
|
172
|
-
| iOS | `NativeListTitleSegmentsLabel` draws the runs at the widths that `NativeListTitleSegmentLayout.swift` allocates for its bounds; it fills the title row in place of the row's filler. | `UIImageView`, first in the title row stack. |
|
|
173
|
-
| Android | `NativeListTitleSegmentsView`, a `TextView` whose text is rebuilt for each measured width from `NativeListTitleSegmentLayout.kt` and which measures to the width it is offered. `PackedTitleLineLayout.titleChild` names the flexible title view, so a view may precede it. | `OneKeyIconView`, first in the title line. |
|
|
174
|
-
| Web | A flex line that fills the title line: the shrinking run is `flex: 0 1 auto; min-width: 0`, the others `flex: 0 0 auto` with a `max-width` percentage for a cap. | An icon element before the title text. |
|
|
175
|
-
|
|
176
|
-
Conformance: `src/validation.ts` (`assertTitleSegments`,
|
|
177
|
-
`assertTitleLeadingIcon`, production sanitizing) with `validation.test.ts`;
|
|
178
|
-
`src/web/templates/IdentityRowRenderer.ts` with `IdentityTitleRunsWeb.test.ts`
|
|
179
|
-
(run styles, flex behavior, accessibility, rebind);
|
|
180
|
-
`ios/NativeListTitleSegmentLayout.swift` with the SwiftPM
|
|
181
|
-
`NativeListTitleSegmentLayoutTests`; `NativeListTitleSegmentLayout.kt` with the
|
|
182
|
-
JVM `NativeListTitleSegmentLayoutTest`. iOS, Android and Web runtime acceptance is pending: recipient rows
|
|
183
|
-
with long wallet and account names in both truncation directions, narrow rows,
|
|
184
|
-
dark mode, dynamic type and configured fonts, and recycling between rows with
|
|
185
|
-
and without runs or a leading icon. Source and build success alone are not
|
|
186
|
-
runtime evidence.
|
|
187
|
-
|
|
188
104
|
### DeFi position row controls
|
|
189
105
|
|
|
190
106
|
Status: implemented in local source, unreleased. Focused validation and Web
|
|
@@ -637,6 +553,14 @@ existing placeholders, and positive title-image dimensions are clamped to a
|
|
|
637
553
|
square of 1...40 logical units. Image headers are string dictionaries, never
|
|
638
554
|
interpreted as geometry. Unsafe object graphs are rejected before normalization.
|
|
639
555
|
|
|
556
|
+
Shared image URI validation accepts the entire `onekey-avatar:` scheme,
|
|
557
|
+
case-insensitively, in snapshots and row patches on iOS, Android and Web. It does
|
|
558
|
+
not restrict the host, version, path, query string or fragment, and preserves the
|
|
559
|
+
URI without decoding. Existing image URI schemes retain their acceptance rules.
|
|
560
|
+
Unsupported image URIs fail development assertions and are sanitized out in
|
|
561
|
+
production. This is validation acceptance only; avatar decoding and loading
|
|
562
|
+
remain owned by the platform image loader or Web avatar worker.
|
|
563
|
+
|
|
640
564
|
A complete snapshot remains atomic after decoration sanitization: invalid
|
|
641
565
|
schema, duplicate/missing identities, required business fields or wallet-group
|
|
642
566
|
parent/children relationships reject the snapshot. No row is silently removed.
|
|
@@ -81,8 +81,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
81
81
|
let title = NativeListResolvedText(
|
|
82
82
|
item.data.string("title"), style: style.dictionary("title"), size: 16,
|
|
83
83
|
weight: .medium, color: .black, lineHeight: 24,
|
|
84
|
-
lines: item.data.
|
|
85
|
-
? item.data.int("titleLines", default: 1) : 1, fonts: fonts)
|
|
84
|
+
lines: item.data.int("titleLines", default: 1), fonts: fonts)
|
|
86
85
|
let subtitle = NativeListResolvedText(
|
|
87
86
|
item.data.string("subtitle"), style: style.dictionary("subtitle"), size: 14,
|
|
88
87
|
color: .black, lineHeight: 20,
|
|
@@ -111,12 +110,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
111
110
|
private let titleRowStack = UIStackView()
|
|
112
111
|
private let titleLabel = NativeListTextLabel()
|
|
113
112
|
private let titleIconView = UIImageView()
|
|
114
|
-
private let titleLeadingIconView = UIImageView()
|
|
115
|
-
private let titleSegmentsLabel = NativeListTitleSegmentsLabel()
|
|
116
|
-
// Runs, when shown, take the title's place in the title row.
|
|
117
|
-
private var visibleTitleView: UIView {
|
|
118
|
-
titleSegmentsLabel.isHidden ? titleLabel : titleSegmentsLabel
|
|
119
|
-
}
|
|
120
113
|
private var titleImageSlot: NativeListImageSlot?
|
|
121
114
|
private var titleImageConstraints: [NSLayoutConstraint] = []
|
|
122
115
|
// Absorbs the title row's slack so the title keeps its intrinsic width and the
|
|
@@ -177,9 +170,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
177
170
|
mainStack.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
178
171
|
titleRowStack.axis = .horizontal
|
|
179
172
|
titleRowStack.alignment = .center
|
|
180
|
-
titleRowStack.addArrangedSubview(titleLeadingIconView)
|
|
181
173
|
titleRowStack.addArrangedSubview(titleLabel)
|
|
182
|
-
titleRowStack.addArrangedSubview(titleSegmentsLabel)
|
|
183
174
|
titleRowStack.addArrangedSubview(titleIconView)
|
|
184
175
|
titleRowStack.addArrangedSubview(badgeLabel)
|
|
185
176
|
titleRowStack.addArrangedSubview(titleRowFiller)
|
|
@@ -194,18 +185,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
194
185
|
])
|
|
195
186
|
titleIconView.contentMode = .scaleAspectFit
|
|
196
187
|
titleIconView.setContentHuggingPriority(.required, for: .horizontal)
|
|
197
|
-
titleLeadingIconView.translatesAutoresizingMaskIntoConstraints = false
|
|
198
|
-
let leadingIconWidth = titleLeadingIconView.widthAnchor.constraint(equalToConstant: 16)
|
|
199
|
-
leadingIconWidth.priority = UILayoutPriority(999)
|
|
200
|
-
NSLayoutConstraint.activate([
|
|
201
|
-
leadingIconWidth,
|
|
202
|
-
titleLeadingIconView.heightAnchor.constraint(equalToConstant: 16),
|
|
203
|
-
])
|
|
204
|
-
titleLeadingIconView.contentMode = .scaleAspectFit
|
|
205
|
-
titleLeadingIconView.setContentHuggingPriority(.required, for: .horizontal)
|
|
206
|
-
titleLeadingIconView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
207
|
-
titleLeadingIconView.isHidden = true
|
|
208
|
-
titleSegmentsLabel.isHidden = true
|
|
209
188
|
[titleRowStack, subtitleLabel, tertiaryLabel].forEach(mainStack.addArrangedSubview)
|
|
210
189
|
visual.translatesAutoresizingMaskIntoConstraints = false
|
|
211
190
|
leadingWidth = visual.widthAnchor.constraint(equalToConstant: 40)
|
|
@@ -267,15 +246,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
267
246
|
titleIconView.isHidden = true
|
|
268
247
|
titleIconView.accessibilityLabel = nil
|
|
269
248
|
titleIconView.isAccessibilityElement = false
|
|
270
|
-
titleLeadingIconView.image = nil
|
|
271
|
-
titleLeadingIconView.isHidden = true
|
|
272
|
-
titleLeadingIconView.accessibilityLabel = nil
|
|
273
|
-
titleLeadingIconView.isAccessibilityElement = false
|
|
274
|
-
titleSegmentsLabel.isHidden = true
|
|
275
|
-
titleSegmentsLabel.reset()
|
|
276
|
-
titleRowFiller.isHidden = false
|
|
277
|
-
titleSegmentsLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
278
|
-
titleSegmentsLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
279
249
|
titleImageSlot?.view.isHidden = true
|
|
280
250
|
titleImageSlot?.view.accessibilityLabel = nil
|
|
281
251
|
titleImageSlot?.view.isAccessibilityElement = false
|
|
@@ -337,7 +307,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
337
307
|
let label = titleIcon.string("accessibilityLabel")
|
|
338
308
|
titleIconView.accessibilityLabel = label.isEmpty ? nil : label
|
|
339
309
|
titleIconView.isAccessibilityElement = !label.isEmpty
|
|
340
|
-
titleRowStack.setCustomSpacing(4, after:
|
|
310
|
+
titleRowStack.setCustomSpacing(4, after: titleLabel)
|
|
341
311
|
// A long title truncates ahead of the 20pt glyph (Android caps the title
|
|
342
312
|
// at row width minus accessories, Web shrinks the title first); with the
|
|
343
313
|
// title at `.required` the icon's 999 width constraint collapsed to 0.
|
|
@@ -369,7 +339,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
369
339
|
]
|
|
370
340
|
titleImageConstraints.forEach { $0.priority = UILayoutPriority(999) }
|
|
371
341
|
NSLayoutConstraint.activate(titleImageConstraints)
|
|
372
|
-
titleRowStack.setCustomSpacing(4, after: titleIconView.isHidden ?
|
|
342
|
+
titleRowStack.setCustomSpacing(4, after: titleIconView.isHidden ? titleLabel : titleIconView)
|
|
373
343
|
titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
374
344
|
slot.bind(image, key: "\(item.key):titleImage", placeholder: "#00000000")
|
|
375
345
|
} else {
|
|
@@ -412,7 +382,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
412
382
|
titleRowStack.setCustomSpacing(
|
|
413
383
|
CGFloat(style.double("titleBadgeGap")),
|
|
414
384
|
after: titleImageSlot?.view.isHidden == false ? titleImageSlot!.view
|
|
415
|
-
: titleIconView.isHidden ?
|
|
385
|
+
: titleIconView.isHidden ? titleLabel : titleIconView)
|
|
416
386
|
}
|
|
417
387
|
}
|
|
418
388
|
if style["trailingGap"] != nil { trailingStack.spacing = CGFloat(style.double("trailingGap")) }
|
|
@@ -426,7 +396,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
426
396
|
labels.forEach { NativeListTextStyles.applyStyledText($0, text, fonts: fonts) }
|
|
427
397
|
}
|
|
428
398
|
}
|
|
429
|
-
if !titleSegmentsLabel.isHidden { bindTitleRuns(item, theme: theme, style: style) }
|
|
430
399
|
contentInsets = UIEdgeInsets(
|
|
431
400
|
top: topInset, left: leftInset, bottom: -bottomInset, right: -rightInset)
|
|
432
401
|
if root.axis == .horizontal {
|
|
@@ -487,8 +456,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
487
456
|
}
|
|
488
457
|
override func recycleContent() {
|
|
489
458
|
visual.recycle()
|
|
490
|
-
titleSegmentsLabel.reset()
|
|
491
|
-
titleLeadingIconView.image = nil
|
|
492
459
|
titleImageSlot?.recycle()
|
|
493
460
|
titleImageSlot?.view.isHidden = true
|
|
494
461
|
titleImageSlot?.view.accessibilityLabel = nil
|
|
@@ -537,30 +504,6 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
537
504
|
visual.bind(
|
|
538
505
|
descriptor, style: image, key: key, theme: currentTheme, isUnread: false, fonts: fonts)
|
|
539
506
|
}
|
|
540
|
-
// Runs take the title's font, size and color unless they set their own tone
|
|
541
|
-
// or weight; the title label holds the final style by now.
|
|
542
|
-
private func bindTitleRuns(_ item: NativeListItem, theme: [String: Any]?, style: [String: Any]) {
|
|
543
|
-
let baseFont = titleLabel.font ?? nativeListFont(ofSize: 16, weight: .medium, fonts: fonts)
|
|
544
|
-
let baseColor = titleLabel.textColor ?? nativeListColor(theme, "primaryText", "#202020")
|
|
545
|
-
let runs = item.data.dictionaries("titleSegments").map { segment in
|
|
546
|
-
let weight = segment.string("fontWeight")
|
|
547
|
-
let tone = segment.string("tone")
|
|
548
|
-
return NativeListTitleSegmentsLabel.Run(
|
|
549
|
-
text: segment.string("text"),
|
|
550
|
-
font: weight.isEmpty
|
|
551
|
-
? baseFont
|
|
552
|
-
: nativeListFont(
|
|
553
|
-
ofSize: baseFont.pointSize, weight: weight == "medium" ? .medium : .regular, fonts: fonts),
|
|
554
|
-
color: tone == "secondary"
|
|
555
|
-
? nativeListColor(theme, "secondaryText", "#646464")
|
|
556
|
-
: tone == "primary" ? nativeListColor(theme, "primaryText", "#202020") : baseColor,
|
|
557
|
-
shrink: segment.bool("shrink"),
|
|
558
|
-
maxWidthRatio: (segment["maxWidthRatio"] as? NSNumber).map { CGFloat(truncating: $0) })
|
|
559
|
-
}
|
|
560
|
-
titleSegmentsLabel.lineHeight = CGFloat(
|
|
561
|
-
style.dictionary("title")?.double("lineHeight", default: 24) ?? 24)
|
|
562
|
-
titleSegmentsLabel.configure(runs, spokenText: item.data.string("title"))
|
|
563
|
-
}
|
|
564
507
|
private func bindIdentity(
|
|
565
508
|
_ item: NativeListItem,
|
|
566
509
|
theme: [String: Any]?,
|
|
@@ -698,33 +641,10 @@ final class NativeListIdentityCell: NativeListRendererCell {
|
|
|
698
641
|
}
|
|
699
642
|
root.addArrangedSubview(mainStack)
|
|
700
643
|
show(titleLabel, item.data.string("title"), lines: item.data.int("titleLines", default: 1))
|
|
701
|
-
// Runs replace the drawn title except where `title` itself is highlighted;
|
|
702
|
-
// their content is bound once the title style is final.
|
|
703
|
-
if !item.data.dictionaries("titleSegments").isEmpty,
|
|
704
|
-
item.data.dictionaries("titleMatch").isEmpty
|
|
705
|
-
{
|
|
706
|
-
titleLabel.isHidden = true
|
|
707
|
-
titleSegmentsLabel.isHidden = false
|
|
708
|
-
// The runs fill what the title row leaves, so a ratio is a share of it.
|
|
709
|
-
titleRowFiller.isHidden = true
|
|
710
|
-
titleSegmentsLabel.setContentHuggingPriority(UILayoutPriority(1), for: .horizontal)
|
|
711
|
-
}
|
|
712
|
-
if let icon = item.data.dictionary("titleLeadingIcon") {
|
|
713
|
-
titleLeadingIconView.image = nativeListIcon(named: icon.string("name"))
|
|
714
|
-
titleLeadingIconView.tintColor = UIColor(
|
|
715
|
-
nativeListHex: icon.string("tintColor",
|
|
716
|
-
default: theme?["iconSubdued"] as? String ?? "#8D8D8D"),
|
|
717
|
-
fallback: .gray)
|
|
718
|
-
titleLeadingIconView.isHidden = false
|
|
719
|
-
let label = icon.string("accessibilityLabel")
|
|
720
|
-
titleLeadingIconView.accessibilityLabel = label.isEmpty ? nil : label
|
|
721
|
-
titleLeadingIconView.isAccessibilityElement = !label.isEmpty
|
|
722
|
-
titleRowStack.setCustomSpacing(6, after: titleLeadingIconView)
|
|
723
|
-
}
|
|
724
644
|
let titleSuffixes = item.data.dictionaries("titleSuffixes")
|
|
725
645
|
if !titleSuffixes.isEmpty {
|
|
726
|
-
|
|
727
|
-
let minimumTitleWidth =
|
|
646
|
+
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
647
|
+
let minimumTitleWidth = titleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 80)
|
|
728
648
|
minimumTitleWidth.priority = UILayoutPriority(751)
|
|
729
649
|
minimumTitleWidth.isActive = true
|
|
730
650
|
selectorConstraints.append(minimumTitleWidth)
|
package/lib/module/models.js
CHANGED
|
@@ -39,8 +39,6 @@ export const ROW_BOX_STYLE_KEYS_BY_TYPE = {
|
|
|
39
39
|
|
|
40
40
|
// OneKey patch: preserve compact very-small-balance digit runs.
|
|
41
41
|
|
|
42
|
-
/** One run of an identity title line; see docs/SPEC.md. */
|
|
43
|
-
|
|
44
42
|
/** Native Market quote row. All values are already localized/formatted strings. */
|
|
45
43
|
|
|
46
44
|
/** Compact KPI card for dashboards and grids. */
|
package/lib/module/validation.js
CHANGED
|
@@ -653,66 +653,6 @@ function assertButtonGroup(row, path) {
|
|
|
653
653
|
}
|
|
654
654
|
});
|
|
655
655
|
}
|
|
656
|
-
const MAX_TITLE_SEGMENTS = 4;
|
|
657
|
-
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}([0-9a-f]{2})?$/i;
|
|
658
|
-
function assertTitleSegments(value, path) {
|
|
659
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_TITLE_SEGMENTS) fail(path, `must hold 1...${MAX_TITLE_SEGMENTS} segments`);
|
|
660
|
-
let shrinking = 0;
|
|
661
|
-
value.forEach((item, index) => {
|
|
662
|
-
const segmentPath = `${path}[${index}]`;
|
|
663
|
-
const segment = protocolObject(item, segmentPath);
|
|
664
|
-
assertUnknownKeys(segment, ['text', 'tone', 'fontWeight', 'shrink', 'maxWidthRatio'], segmentPath, 'title segment key');
|
|
665
|
-
const {
|
|
666
|
-
text,
|
|
667
|
-
tone,
|
|
668
|
-
fontWeight,
|
|
669
|
-
shrink,
|
|
670
|
-
maxWidthRatio
|
|
671
|
-
} = segment;
|
|
672
|
-
if (typeof text !== 'string' || !text.trim()) fail(`${segmentPath}.text`, 'must be non-blank');
|
|
673
|
-
assertText(text, `${segmentPath}.text`);
|
|
674
|
-
if (tone !== undefined && tone !== 'primary' && tone !== 'secondary') fail(`${segmentPath}.tone`, 'must be primary or secondary');
|
|
675
|
-
if (fontWeight !== undefined && fontWeight !== 'regular' && fontWeight !== 'medium') fail(`${segmentPath}.fontWeight`, 'must be regular or medium');
|
|
676
|
-
if (shrink !== undefined && typeof shrink !== 'boolean') fail(`${segmentPath}.shrink`, 'must be a boolean');
|
|
677
|
-
if (shrink) shrinking += 1;
|
|
678
|
-
if (maxWidthRatio !== undefined && (typeof maxWidthRatio !== 'number' || !Number.isFinite(maxWidthRatio) || maxWidthRatio <= 0 || maxWidthRatio > 1)) fail(`${segmentPath}.maxWidthRatio`, 'must be finite within (0, 1]');
|
|
679
|
-
});
|
|
680
|
-
if (shrinking > 1) fail(path, 'allows at most one shrinking segment');
|
|
681
|
-
}
|
|
682
|
-
function assertTitleLeadingIcon(value, path) {
|
|
683
|
-
const icon = protocolObject(value, path);
|
|
684
|
-
assertUnknownKeys(icon, ['name', 'tintColor', 'accessibilityLabel'], path, 'title leading icon key');
|
|
685
|
-
const {
|
|
686
|
-
name,
|
|
687
|
-
tintColor,
|
|
688
|
-
accessibilityLabel
|
|
689
|
-
} = icon;
|
|
690
|
-
if (typeof name !== 'string' || !name.trim()) fail(`${path}.name`, 'must be non-blank');
|
|
691
|
-
if (tintColor !== undefined && (typeof tintColor !== 'string' || !HEX_COLOR_PATTERN.test(tintColor))) fail(`${path}.tintColor`, 'must be #RRGGBB or #RRGGBBAA');
|
|
692
|
-
if (accessibilityLabel !== undefined && typeof accessibilityLabel !== 'string') fail(`${path}.accessibilityLabel`, 'must be a string');
|
|
693
|
-
}
|
|
694
|
-
function assertIdentityTitleRuns(row, path) {
|
|
695
|
-
if (row.titleSegments !== undefined) {
|
|
696
|
-
assertTitleSegments(row.titleSegments, `${path}.titleSegments`);
|
|
697
|
-
// titleMatch ranges index `title`, which the runs replace on screen.
|
|
698
|
-
if (row.titleMatch?.length) fail(`${path}.titleMatch`, 'cannot be combined with titleSegments');
|
|
699
|
-
if (row.presentation === 'walletSidebar') fail(`${path}.titleSegments`, 'is not supported with walletSidebar');
|
|
700
|
-
}
|
|
701
|
-
if (row.titleLeadingIcon !== undefined) {
|
|
702
|
-
assertTitleLeadingIcon(row.titleLeadingIcon, `${path}.titleLeadingIcon`);
|
|
703
|
-
if (row.presentation === 'walletSidebar') fail(`${path}.titleLeadingIcon`, 'is not supported with walletSidebar');
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/** Production keeps decorative title runs only when they pass the rules. */
|
|
708
|
-
function passesTitleRunRules(check) {
|
|
709
|
-
try {
|
|
710
|
-
check();
|
|
711
|
-
return true;
|
|
712
|
-
} catch {
|
|
713
|
-
return false;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
656
|
function assertIdentityExtras(row, path) {
|
|
717
657
|
for (const field of ['titleIcon', 'secondaryTitleIcon']) {
|
|
718
658
|
const titleIcon = row[field];
|
|
@@ -944,7 +884,6 @@ function assertRow(row, index, path = `rows[${index}]`) {
|
|
|
944
884
|
if (suffix.maxWidth !== undefined && (!Number.isFinite(suffix.maxWidth) || suffix.maxWidth < 0 || suffix.maxWidth > 4096)) fail(`${path}.titleSuffixes[${index}].maxWidth`, 'must be finite within 0...4096');
|
|
945
885
|
});
|
|
946
886
|
assertIdentityExtras(row, path);
|
|
947
|
-
assertIdentityTitleRuns(row, path);
|
|
948
887
|
assertText(row.subtitle, `${path}.subtitle`);
|
|
949
888
|
// OneKey patch: selector text segments truncate independently.
|
|
950
889
|
row.subtitleSegments?.forEach((segment, segmentIndex) => {
|
|
@@ -1238,7 +1177,7 @@ function assertSnapshotContract(snapshot) {
|
|
|
1238
1177
|
}
|
|
1239
1178
|
const COMMON_ROW_FIELDS = ['height', 'heightRounding', 'testID', 'opacity', 'backgroundColor', 'backgroundFullWidth', 'pressDisabled', 'key', 'revision', 'sectionKey', 'groupId', 'groupPosition', 'size', 'density', 'disabled', 'selected', 'accessibilityLabel', 'separator', 'type', 'style'];
|
|
1240
1179
|
const ROW_FIELDS = {
|
|
1241
|
-
identity: [...COMMON_ROW_FIELDS, 'contentHeight', 'longPressActionKey', 'titleMatch', 'titleActionKey', 'titleActionOnHover', 'subtitleSegments', 'presentation', 'leading', 'leadingAction', 'title', 'titleSuffixes', 'titleIcon', 'secondaryTitleIcon', 'titleImage', '
|
|
1180
|
+
identity: [...COMMON_ROW_FIELDS, 'contentHeight', 'longPressActionKey', 'titleMatch', 'titleActionKey', 'titleActionOnHover', 'subtitleSegments', 'presentation', 'leading', 'leadingAction', 'title', 'titleSuffixes', 'titleIcon', 'secondaryTitleIcon', 'titleImage', 'subtitle', 'tertiary', 'tertiaryTone', 'titleLines', 'subtitleLines', 'badges', 'trailing', 'draggable'],
|
|
1242
1181
|
walletGroup: [...COMMON_ROW_FIELDS, 'parent', 'children', 'draggable'],
|
|
1243
1182
|
rail: [...COMMON_ROW_FIELDS, 'visual', 'title', 'status', 'badge', 'draggable'],
|
|
1244
1183
|
activity: [...COMMON_ROW_FIELDS, 'leading', 'hideLeading', 'secondaryLeading', 'title', 'description', 'status', 'primaryAmount', 'secondaryAmount', 'amounts', 'badges', 'presentation', 'fee', 'descriptionActionKey', 'footerActions'],
|
|
@@ -1254,7 +1193,7 @@ const ROW_FIELDS = {
|
|
|
1254
1193
|
};
|
|
1255
1194
|
const COMMON_PATCH_FIELDS = ['height', 'heightRounding', 'opacity', 'pressDisabled', 'revision', 'disabled', 'selected', 'accessibilityLabel', 'separator', 'style'];
|
|
1256
1195
|
const PATCH_FIELDS = {
|
|
1257
|
-
identity: [...COMMON_PATCH_FIELDS, 'contentHeight', 'longPressActionKey', 'titleMatch', 'titleActionKey', 'titleActionOnHover', 'subtitleSegments', 'leading', 'leadingAction', 'title', 'titleSuffixes', 'titleIcon', 'secondaryTitleIcon', 'titleImage', '
|
|
1196
|
+
identity: [...COMMON_PATCH_FIELDS, 'contentHeight', 'longPressActionKey', 'titleMatch', 'titleActionKey', 'titleActionOnHover', 'subtitleSegments', 'leading', 'leadingAction', 'title', 'titleSuffixes', 'titleIcon', 'secondaryTitleIcon', 'titleImage', 'subtitle', 'tertiary', 'tertiaryTone', 'subtitleLines', 'badges', 'trailing'],
|
|
1258
1197
|
rail: [...COMMON_PATCH_FIELDS, 'title', 'visual', 'status', 'badge'],
|
|
1259
1198
|
activity: [...COMMON_PATCH_FIELDS, 'presentation', 'leading', 'title', 'badges', 'status', 'hideLeading', 'secondaryLeading', 'description', 'primaryAmount', 'secondaryAmount', 'amounts', 'fee', 'descriptionActionKey', 'footerActions'],
|
|
1260
1199
|
message: [...COMMON_PATCH_FIELDS, 'presentation', 'title', 'unread', 'body', 'bodyLines', 'time', 'thumbnail'],
|
|
@@ -1274,7 +1213,7 @@ function protocolObject(value, path) {
|
|
|
1274
1213
|
function protocolKey(value, path) {
|
|
1275
1214
|
if (typeof value !== 'string' || value.trim().length === 0) fail(path, 'must be a non-blank string');
|
|
1276
1215
|
}
|
|
1277
|
-
const PROTOCOL_ARRAY_FIELDS = new Set(['columns', 'titleSuffixes', '
|
|
1216
|
+
const PROTOCOL_ARRAY_FIELDS = new Set(['columns', 'titleSuffixes', 'cells', 'points', 'images', 'overlays', 'badges', 'trailing', 'footerActions', 'buttons', 'amounts', 'metrics', 'children', 'subtitleSegments', 'titleMatch', 'selectedKeys', 'segments', 'hoverSecondary', 'hoverImages', 'secondaryBadges', 'fallbackUris', 'probeOrder', 'priceSegments', 'textSegments', 'secondaryTextSegments', 'primaryTextSegments']);
|
|
1278
1217
|
const PROTOCOL_NUMBER_FIELDS = new Set(['leadingContentMaxWidthRatio', 'height', 'width', 'fontSize', 'lineHeight', 'opacity', 'offsetY', 'offsetX', 'cornerRadius', 'borderWidth', 'horizontalPadding', 'verticalPadding', 'leadingGap', 'lineGap', 'titleBadgeGap', 'trailingGap', 'start', 'end', 'padding', 'offset', 'overscan', 'minWidth', 'flex', 'paddingStart', 'paddingEnd', 'changeWidth', 'changeHeight', 'changeCornerRadius', 'contentTrailingGap', 'subtitleTrailingPadding', 'partGap', 'contentPadding', 'contentPaddingHorizontal', 'contentPaddingTop', 'contentPaddingBottom', 'itemSpacing', 'gridColumns']);
|
|
1279
1218
|
|
|
1280
1219
|
/** Validate only shapes that renderers dereference, not display budgets. */
|
|
@@ -1345,10 +1284,7 @@ function assertRowProtocol(row, path) {
|
|
|
1345
1284
|
}
|
|
1346
1285
|
if (row.height !== undefined && row.height < 0) fail(`${path}.height`, 'must be non-negative');
|
|
1347
1286
|
if (row.type === 'identity') {
|
|
1348
|
-
if (isDevelopment())
|
|
1349
|
-
assertIdentityExtras(row, path);
|
|
1350
|
-
assertIdentityTitleRuns(row, path);
|
|
1351
|
-
}
|
|
1287
|
+
if (isDevelopment()) assertIdentityExtras(row, path);
|
|
1352
1288
|
if (row.titleSuffixes !== undefined && !Array.isArray(row.titleSuffixes)) fail(`${path}.titleSuffixes`, 'must be an array');
|
|
1353
1289
|
row.titleSuffixes?.forEach((suffix, index) => {
|
|
1354
1290
|
protocolObject(suffix, `${path}.titleSuffixes[${index}]`);
|
|
@@ -1456,7 +1392,7 @@ function isRecord(value) {
|
|
|
1456
1392
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1457
1393
|
}
|
|
1458
1394
|
function isAllowedImageUri(value) {
|
|
1459
|
-
return typeof value === 'string' && value.trim().length > 0 && (!/^[a-z][a-z\d+.-]*:/i.test(value) || /^(https?|file|content|ph|assets-library|asset|res|blob):/i.test(value) || /^data:image\//i.test(value));
|
|
1395
|
+
return typeof value === 'string' && value.trim().length > 0 && (!/^[a-z][a-z\d+.-]*:/i.test(value) || /^(https?|file|content|ph|assets-library|asset|res|blob|onekey-avatar):/i.test(value) || /^data:image\//i.test(value));
|
|
1460
1396
|
}
|
|
1461
1397
|
const IMAGE_FIELDS = ['uri', 'width', 'height', 'headers', 'contentFit', 'cachePolicy', 'autoplay', 'optimizeTos', 'overscan', 'loadingStrategy', 'fallbackUri', 'fallbackUris', 'retryTimes'];
|
|
1462
1398
|
const ICON_FIELDS = ['name', 'tintColor', 'backgroundColor', 'size', 'accessibilityLabel', 'hoverActionKey'];
|
|
@@ -1597,8 +1533,6 @@ const NESTED_DECORATION_FIELDS = {
|
|
|
1597
1533
|
};
|
|
1598
1534
|
function sanitizeDecoration(value, field) {
|
|
1599
1535
|
if (value === undefined || value === null) return undefined;
|
|
1600
|
-
if (field === 'titleSegments') return passesTitleRunRules(() => assertTitleSegments(value, field)) ? value : undefined;
|
|
1601
|
-
if (field === 'titleLeadingIcon') return passesTitleRunRules(() => assertTitleLeadingIcon(value, field)) ? value : undefined;
|
|
1602
1536
|
if (!Array.isArray(value) && ['image', 'networkImage', 'thumbnail', 'source', 'images', 'hoverImages'].includes(field)) return sanitizeImage(value);
|
|
1603
1537
|
if (['titleImage', 'titleIcon', 'secondaryTitleIcon', 'fallbackIcon', 'cornerIcon', 'media'].includes(field) && !isRecord(value)) return undefined;
|
|
1604
1538
|
if (field === 'style' && typeof value !== 'string' && !isRecord(value)) return undefined;
|
|
@@ -466,7 +466,7 @@ export const WEB_LIST_CSS = `
|
|
|
466
466
|
.ok-native-list-trailing-button{appearance:none;box-sizing:border-box;flex:0 0 auto;padding:4px 10px;border:1px solid transparent;border-radius:9999px;background:var(--nl-button-bg,var(--nl-strong));color:var(--nl-button-text,var(--nl-primary));font:500 14px/20px var(--nl-font-medium);white-space:nowrap;cursor:pointer}.ok-native-list-trailing-button:hover:not(:disabled){background:var(--nl-button-hover,var(--nl-strong))}.ok-native-list-trailing-button:active:not(:disabled){background:var(--nl-button-pressed,var(--nl-pressed))}.ok-native-list-trailing-button:focus-visible{outline:2px solid var(--nl-button-focus,var(--nl-accent))}.ok-native-list-trailing-button:disabled{opacity:.4;cursor:default}
|
|
467
467
|
.ok-native-list-icon-button.ok-native-list-tertiary-icon{box-sizing:border-box;flex:0 0 38px;width:38px;height:38px;margin:-7px;padding:6px;border:1px solid transparent;border-radius:9999px;color:var(--nl-icon-subdued);background:transparent}.ok-native-list-icon-button.ok-native-list-tertiary-icon:hover:not(:disabled){background:var(--nl-hover)}.ok-native-list-icon-button.ok-native-list-tertiary-icon:active:not(:disabled){background:var(--nl-pressed)}.ok-native-list-icon-button.ok-native-list-tertiary-icon:disabled{opacity:.4;cursor:default}
|
|
468
468
|
.ok-native-list-icon-button.ok-native-list-tertiary-icon:focus-visible:not(:disabled){outline:2px solid var(--nl-accent)}
|
|
469
|
-
.ok-native-list-identity-title-icon{flex:0 0 20px;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--nl-icon);font-size:20px}.ok-native-list-identity-title-
|
|
469
|
+
.ok-native-list-identity-title-icon{flex:0 0 20px;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--nl-icon);font-size:20px}.ok-native-list-identity-title-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
470
470
|
.ok-native-list-account-row .ok-native-list-visual,.ok-native-list-account-action-row .ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-account-row .ok-native-list-visual>img,.ok-native-list-account-row .ok-native-list-visual-main,.ok-native-list-account-action-row .ok-native-list-visual>img,.ok-native-list-account-action-row .ok-native-list-visual-main{width:32px;height:32px}.ok-native-list-account-row .ok-native-list-visual>.ok-native-list-visual-corner{width:20px;height:20px;padding:2px}.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-secondary);font-size:12px;line-height:16px;font-weight:400;font-family:var(--nl-font-regular)}.ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-primary)}.ok-native-list-account-row .ok-native-list-title{font-size:16px;line-height:20px;font-weight:400;font-family:var(--nl-font-regular)}.ok-native-list-account-row .ok-native-list-secondary{font-size:14px;line-height:20px;font-weight:400;font-family:var(--nl-font-regular)}
|
|
471
471
|
.ok-native-list-flex{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-title{font-size:15px;line-height:20px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--nl-font-semibold)}.ok-native-list-secondary{font-size:13px;line-height:18px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-tertiary{color:var(--nl-secondary)}.ok-native-list-info{color:var(--nl-info)}
|
|
472
472
|
.ok-native-list-identity-row .ok-native-list-title,.ok-native-list-identity-row .ok-native-list-amounts>span:first-child{font-size:16px;line-height:20px}.ok-native-list-identity-row .ok-native-list-secondary{font-size:14px;line-height:20px}
|