@onekeyfe/react-native-native-list 3.0.114 → 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/README.md +57 -3
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +47 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +795 -30
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +445 -91
- package/ios/NativeListCell.swift +655 -20
- package/ios/NativeListDesignAssets.swift +8 -0
- package/ios/RNCNativeListView.swift +275 -42
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/icon.svg +1 -0
- package/lib/module/validation.js +145 -1
- package/lib/module/web/NativeListWebEngine.js +461 -51
- package/lib/typescript/src/models.d.ts +108 -2
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +37 -3
- package/package.json +4 -2
- package/src/models.ts +144 -3
- package/src/validation.ts +250 -0
- package/src/web/NativeListWebEngine.ts +766 -91
package/README.md
CHANGED
|
@@ -112,9 +112,10 @@ second delta.
|
|
|
112
112
|
| `activity` | Transaction/activity with amounts and up to three actions |
|
|
113
113
|
| `message` | Notification/message with bounded body lines and thumbnail |
|
|
114
114
|
| `dataRow` | Two to four fixed table columns, optional index/checkbox |
|
|
115
|
+
| `market` | Recycled token, stock, or perpetual quote row |
|
|
115
116
|
| `mediaTile` | Gallery or browser-preview tile |
|
|
116
|
-
| `metricCard` | KPI or fixed Activity/Performance portfolio card
|
|
117
|
-
| `sectionHeader` | Sticky/group, tri-state, or fixed list-summary header
|
|
117
|
+
| `metricCard` | KPI or fixed Activity/Performance portfolio card |
|
|
118
|
+
| `sectionHeader` | Sticky/group, tri-state, or fixed list-summary header |
|
|
118
119
|
| `action` | Fixed action row or fixed footer |
|
|
119
120
|
| `system` | Loading, retry, end marker, or bounded spacer |
|
|
120
121
|
|
|
@@ -162,6 +163,59 @@ Bold faces and use them before the platform-font fallback. Their semantic
|
|
|
162
163
|
icons and checkbox marks are the same vector paths as the OneKey component
|
|
163
164
|
library, rendered by Core Graphics/UIKit on iOS and Canvas on Android.
|
|
164
165
|
|
|
166
|
+
### Market rows
|
|
167
|
+
|
|
168
|
+
`market` is a fixed native/DOM template, not a React `renderItem`. Its default
|
|
169
|
+
token/perpetual geometry uses a 32-point leading image with a 16-point network
|
|
170
|
+
corner; stock uses a 40-point image. The trailing change block is 80×32 with an
|
|
171
|
+
8-point radius. All displayed values are already formatted strings; compact
|
|
172
|
+
zero runs can use `priceSegments`, `subtitleSegments`, or
|
|
173
|
+
`change.textSegments` with `{ style: 'subscript' }`.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const btc = {
|
|
177
|
+
type: 'market',
|
|
178
|
+
key: 'btc',
|
|
179
|
+
variant: 'token',
|
|
180
|
+
leading: { kind: 'token', image: btcImage, networkImage: bitcoinImage },
|
|
181
|
+
title: 'BTC',
|
|
182
|
+
subtitle: '$1.23B',
|
|
183
|
+
price: '$64,230.00',
|
|
184
|
+
change: { text: '+2.40%', tone: 'positive' },
|
|
185
|
+
badges: [
|
|
186
|
+
{
|
|
187
|
+
key: 'community',
|
|
188
|
+
iconName: 'verified',
|
|
189
|
+
tone: 'success',
|
|
190
|
+
actionKey: 'market.communityInfo',
|
|
191
|
+
accessibilityLabel: 'Community recognized',
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
pressInActionKey: 'market.prewarm',
|
|
195
|
+
pressActionKey: 'market.open',
|
|
196
|
+
longPressActionKey: 'market.menu',
|
|
197
|
+
diagnostics: { imageBindActionKey: 'market.imageBind' },
|
|
198
|
+
} satisfies MarketRow;
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Badges accept localized text, a bounded remote image, or the single built-in
|
|
202
|
+
`iconName: 'verified'` glyph. A badge `actionKey` emits independently with an
|
|
203
|
+
anchor whose source is `marketBadge`. Market long press fires after 800 ms and
|
|
204
|
+
is cancelled when the pointer moves more than 10 logical pixels or the list
|
|
205
|
+
scrolls. The optional diagnostic action fires when a row binds an image; quote-
|
|
206
|
+
only patches (`price`, `priceSegments`, `change`, `accessibilityLabel`, and
|
|
207
|
+
`revision`) update visible labels without rebinding that image.
|
|
208
|
+
|
|
209
|
+
`style` is deliberately finite: row padding/gaps, image width/height/shape/
|
|
210
|
+
corner radius/content fit, title/subtitle/price/change font size, weight,
|
|
211
|
+
color, line height, one-or-two-line truncation and alignment, plus change-block
|
|
212
|
+
width/height/radius. Omitting it preserves the defaults. Loading, empty, retry,
|
|
213
|
+
and pagination states use the existing `system` rows with
|
|
214
|
+
`presentation: 'market'`. Loading rows accept `loadingStyle: 'skeleton'` for
|
|
215
|
+
the 56-point token skeleton or `'spinner'` for the centered 52-point pagination
|
|
216
|
+
indicator. Use `accessibilityLabel` for loading announcements. Omitting
|
|
217
|
+
`loadingStyle` preserves each platform's existing loading rendering.
|
|
218
|
+
|
|
165
219
|
For native reorder, set `capabilities.reorderable` and mark eligible rail rows
|
|
166
220
|
with `draggable: true`, or add a `drag` trailing accessory to an identity row.
|
|
167
221
|
Reordering never crosses a `sectionKey` boundary.
|
|
@@ -307,7 +361,7 @@ by the iOS wallet list's 120 Hz tail behavior.
|
|
|
307
361
|
| Activity rows | `components/TxHistoryListView/TxHistoryListItem.tsx` |
|
|
308
362
|
| Message rows | `Notifications/components/NotificationListView.tsx` |
|
|
309
363
|
| Data rows | `Perp/components/TokenSelector/PerpTokenSelectorRow.tsx` |
|
|
310
|
-
| Media tile rows | `Home/components/NFTListView/NFTListItem.tsx`
|
|
364
|
+
| Media tile rows | `Home/components/NFTListView/NFTListItem.tsx` |
|
|
311
365
|
| Metric card rows | `Perp/components/Portfolio/PerpPortfolioContent.tsx` |
|
|
312
366
|
| Section header rows | `AllNetworksManager/NetworksSectionList.tsx`, `TxHistoryListView/TxHistorySectionHeader.tsx` |
|
|
313
367
|
| Action rows | `TokenManager/TokenManagerList.tsx`, `Discovery/pages/BookmarkListModal/index.tsx` |
|
package/android/build.gradle
CHANGED
|
@@ -101,5 +101,6 @@ dependencies {
|
|
|
101
101
|
implementation "androidx.recyclerview:recyclerview:1.4.0"
|
|
102
102
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
|
103
103
|
implementation project(":react-native-nitro-modules")
|
|
104
|
+
implementation project(":onekeyfe_react-native-native-logger")
|
|
104
105
|
implementation project(":onekeyfe_react-native-image")
|
|
105
106
|
}
|
|
@@ -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
|
)
|
|
@@ -92,6 +96,10 @@ internal class NativeListAdapter(
|
|
|
92
96
|
position: Int,
|
|
93
97
|
payloads: MutableList<Any>,
|
|
94
98
|
) {
|
|
99
|
+
if (payloads.isNotEmpty() && payloads.all { it == MARKET_QUOTE_PAYLOAD }) {
|
|
100
|
+
itemAt(position)?.let { holder.rowView.bindMarketQuote(it, theme) }
|
|
101
|
+
return
|
|
102
|
+
}
|
|
95
103
|
// OneKey patch: an unknown payload must retain full binding; validated echoes keep images alive.
|
|
96
104
|
// if (payloads.contains(SELECTION_PAYLOAD)) {
|
|
97
105
|
if (payloads.isNotEmpty() && payloads.all { it == SELECTION_PAYLOAD || it == SELECTION_ECHO_PAYLOAD }) {
|
|
@@ -118,6 +126,30 @@ internal class NativeListAdapter(
|
|
|
118
126
|
fun itemAt(position: Int): NativeListItem? =
|
|
119
127
|
reorderItems?.getOrNull(position) ?: differ.currentList.getOrNull(position)
|
|
120
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
|
+
|
|
121
153
|
fun positionOfKey(key: String): Int =
|
|
122
154
|
(reorderItems ?: differ.currentList).indexOfFirst { it.key == key }
|
|
123
155
|
|
|
@@ -165,6 +197,8 @@ internal class NativeListAdapter(
|
|
|
165
197
|
}
|
|
166
198
|
|
|
167
199
|
fun dispose() {
|
|
200
|
+
marketSourceItems = null
|
|
201
|
+
marketSourceEdges = DoubleArray(0)
|
|
168
202
|
reorderItems = null
|
|
169
203
|
suppressDifferUpdates = false
|
|
170
204
|
createdRows.forEach(NativeListRowView::dispose)
|
|
@@ -183,7 +217,18 @@ internal class NativeListAdapter(
|
|
|
183
217
|
override fun getChangePayload(oldItem: NativeListItem, newItem: NativeListItem): Any? =
|
|
184
218
|
if (oldItem.key == newItem.key && oldItem.type == newItem.type &&
|
|
185
219
|
newItem.selectionUpdateFromContent == oldItem.content
|
|
186
|
-
) SELECTION_ECHO_PAYLOAD else null
|
|
220
|
+
) SELECTION_ECHO_PAYLOAD else if (isMarketQuoteUpdate(oldItem, newItem)) MARKET_QUOTE_PAYLOAD else null
|
|
221
|
+
|
|
222
|
+
private fun isMarketQuoteUpdate(oldItem: NativeListItem, newItem: NativeListItem): Boolean {
|
|
223
|
+
if (oldItem.type != "market" || newItem.type != "market") return false
|
|
224
|
+
fun stableContent(item: NativeListItem): String {
|
|
225
|
+
val value = JSONObject(item.json.toString())
|
|
226
|
+
listOf("revision", "price", "priceSegments", "change", "accessibilityLabel")
|
|
227
|
+
.forEach(value::remove)
|
|
228
|
+
return value.toString()
|
|
229
|
+
}
|
|
230
|
+
return stableContent(oldItem) == stableContent(newItem)
|
|
231
|
+
}
|
|
187
232
|
}
|
|
188
233
|
}
|
|
189
234
|
}
|
|
@@ -191,3 +236,4 @@ internal class NativeListAdapter(
|
|
|
191
236
|
internal const val SELECTION_PAYLOAD = "selection"
|
|
192
237
|
// OneKey patch: internal payload, never exposed through the serialized row contract.
|
|
193
238
|
internal const val SELECTION_ECHO_PAYLOAD = "selectionEcho"
|
|
239
|
+
internal const val MARKET_QUOTE_PAYLOAD = "marketQuote"
|