@onekeyfe/react-native-native-list 6.7.0-alpha.378 → 6.7.0-alpha.382

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.
Files changed (116) hide show
  1. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +14 -1
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +240 -11
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +7 -4
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +6 -3
  5. package/android/src/main/java/com/onekey/nativelist/NativeListButtonGroupRenderer.kt +102 -0
  6. package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +12 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +87 -7
  8. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +177 -1
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +43 -14
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +25 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +33 -2
  12. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +22 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +2 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +2 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +3 -2
  16. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +69 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +55 -1
  18. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1912 -107
  19. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListPressPolicyTest.kt +1 -0
  20. package/docs/ROW_TEMPLATES.md +60 -9
  21. package/docs/SPEC.md +450 -5
  22. package/docs/STYLE_SPEC.md +6 -0
  23. package/ios/HybridNativeList.swift +21 -2
  24. package/ios/NativeListAccessoryStack.swift +227 -39
  25. package/ios/NativeListActivityDetailsView.swift +18 -8
  26. package/ios/NativeListActivityRenderer.swift +8 -2
  27. package/ios/NativeListButtonGroupRenderer.swift +89 -0
  28. package/ios/NativeListDataRowRenderer.swift +155 -27
  29. package/ios/NativeListDesignAssets.swift +3 -0
  30. package/ios/NativeListIdentityRenderer.swift +166 -30
  31. package/ios/NativeListLeadingVisual.swift +49 -14
  32. package/ios/NativeListMessageRenderer.swift +34 -1
  33. package/ios/NativeListModels.swift +42 -2
  34. package/ios/NativeListRendererRegistry.swift +1 -0
  35. package/ios/NativeListResolvedText.swift +2 -1
  36. package/ios/NativeListRowHost.swift +14 -0
  37. package/ios/NativeListSectionHeaderRenderer.swift +88 -0
  38. package/ios/RNCNativeListView.swift +1013 -55
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +15 -0
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +4 -0
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +15 -0
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +3 -0
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +15 -0
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +3 -0
  45. package/ios/tests/NativeListActivityPresentationTests.swift +56 -0
  46. package/ios/tests/NativeListLifecycleTests.swift +2720 -3
  47. package/lib/module/NativeList.js +175 -28
  48. package/lib/module/NativeList.web.js +129 -30
  49. package/lib/module/index.js +1 -1
  50. package/lib/module/models.js +1 -0
  51. package/lib/module/selection.js +1 -1
  52. package/lib/module/validation.js +421 -8
  53. package/lib/module/web/NativeListWebEngine.js +507 -19
  54. package/lib/module/web/templates/ActivityRowRenderer.js +8 -7
  55. package/lib/module/web/templates/ButtonGroupRowRenderer.js +47 -0
  56. package/lib/module/web/templates/DataRowRenderer.js +40 -5
  57. package/lib/module/web/templates/IdentityRowRenderer.js +90 -4
  58. package/lib/module/web/templates/MessageRowRenderer.js +18 -2
  59. package/lib/module/web/templates/RowContainerStyle.js +1 -0
  60. package/lib/module/web/templates/RowRendererRegistry.js +3 -0
  61. package/lib/module/web/templates/SectionHeaderRowRenderer.js +36 -2
  62. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  63. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  64. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  65. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  66. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  67. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  68. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  69. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  70. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  71. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  72. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  73. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  74. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  75. package/lib/typescript/src/NativeList.d.ts +1 -1
  76. package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
  77. package/lib/typescript/src/NativeList.types.d.ts +18 -0
  78. package/lib/typescript/src/index.d.ts +2 -2
  79. package/lib/typescript/src/models.d.ts +105 -9
  80. package/lib/typescript/src/validation.d.ts +12 -0
  81. package/lib/typescript/src/web/NativeListWebEngine.d.ts +32 -0
  82. package/lib/typescript/src/web/templates/ButtonGroupRowRenderer.d.ts +14 -0
  83. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +1 -1
  84. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +9 -3
  85. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  86. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  87. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  89. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  90. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  91. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  92. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  93. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  94. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  95. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  96. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  97. package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  98. package/package.json +3 -3
  99. package/react-native-native-list.podspec +1 -0
  100. package/src/NativeList.nitro.ts +2 -0
  101. package/src/NativeList.tsx +313 -44
  102. package/src/NativeList.types.ts +22 -0
  103. package/src/NativeList.web.tsx +204 -48
  104. package/src/index.ts +2 -0
  105. package/src/models.ts +117 -3
  106. package/src/selection.ts +1 -0
  107. package/src/validation.ts +852 -6
  108. package/src/web/NativeListWebEngine.ts +803 -20
  109. package/src/web/templates/ActivityRowRenderer.ts +14 -6
  110. package/src/web/templates/ButtonGroupRowRenderer.ts +58 -0
  111. package/src/web/templates/DataRowRenderer.ts +93 -20
  112. package/src/web/templates/IdentityRowRenderer.ts +150 -5
  113. package/src/web/templates/MessageRowRenderer.ts +23 -7
  114. package/src/web/templates/RowContainerStyle.ts +2 -0
  115. package/src/web/templates/RowRendererRegistry.ts +11 -1
  116. package/src/web/templates/SectionHeaderRowRenderer.ts +47 -4
@@ -1,7 +1,9 @@
1
1
  package com.margelo.nitro.nativelist
2
2
 
3
3
  import android.graphics.Color
4
+ import android.graphics.Canvas
4
5
  import android.graphics.Outline
6
+ import android.graphics.Paint
5
7
  import android.graphics.drawable.GradientDrawable
6
8
  import android.view.Gravity
7
9
  import android.view.View
@@ -211,6 +213,7 @@ internal class NativeListMessageRowView(context: ThemedReactContext, fonts: Nati
211
213
  NativeListRendererRowView(context, fonts) {
212
214
  override val assetFields = listOf("leading", "thumbnail")
213
215
  private val views = NativeListMessageRenderer.Views(context, fonts)
216
+ private val warningBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
214
217
 
215
218
  override fun bindContent(
216
219
  item: NativeListItem,
@@ -229,5 +232,27 @@ internal class NativeListMessageRowView(context: ThemedReactContext, fonts: Nati
229
232
 
230
233
  override fun recycleContent() = views.recycle()
231
234
 
235
+ override fun appearance() {
236
+ super.appearance()
237
+ if (boundItem?.json?.optString("presentation") == "inlineWarning") {
238
+ foreground = null
239
+ clipToOutline = false
240
+ (background as? GradientDrawable)?.cornerRadius = 0f
241
+ }
242
+ }
243
+
244
+ override fun dispatchDraw(canvas: Canvas) {
245
+ super.dispatchDraw(canvas)
246
+ val item = boundItem ?: return
247
+ if (item.json.optString("presentation") != "inlineWarning") return
248
+ val container = item.json.optJSONObject("style")?.optJSONObject("container") ?: return
249
+ val borderWidth = container.optDouble("borderWidth", 0.0).toFloat()
250
+ if (borderWidth <= 0) return
251
+ val pixels = (borderWidth * resources.displayMetrics.density).coerceAtLeast(1f)
252
+ warningBorderPaint.color = parseNativeListColor(container.optString("borderColor", "#00000000"))
253
+ canvas.drawRect(0f, 0f, width.toFloat(), pixels, warningBorderPaint)
254
+ canvas.drawRect(0f, height - pixels, width.toFloat(), height.toFloat(), warningBorderPaint)
255
+ }
256
+
232
257
  override fun disposeContent() = views.dispose()
233
258
  }
@@ -13,7 +13,7 @@ internal fun isNativeListRowPressEnabled(
13
13
  variant: String,
14
14
  disabled: Boolean,
15
15
  pressDisabled: Boolean,
16
- ): Boolean = !disabled && !pressDisabled && (type != "system" || variant == "retry")
16
+ ): Boolean = !disabled && !pressDisabled && type != "buttonGroup" && (type != "system" || variant == "retry")
17
17
 
18
18
  internal fun isNativeListWholeRowInteractive(
19
19
  type: String,
@@ -78,7 +78,7 @@ private fun updateLengthPrefixed(digest: MessageDigest, value: String) {
78
78
  }
79
79
 
80
80
  // Template type alone partitions reuse; data keys and styles never select a renderer.
81
- internal enum class NativeListRendererKey { MESSAGE, RAIL, MEDIA_TILE, ACTION, SYSTEM, ACTIVITY, DATA_ROW, METRIC_CARD, MARKET, IDENTITY, SECTION_HEADER, WALLET_GROUP }
81
+ internal enum class NativeListRendererKey { MESSAGE, RAIL, MEDIA_TILE, ACTION, SYSTEM, ACTIVITY, DATA_ROW, METRIC_CARD, MARKET, IDENTITY, SECTION_HEADER, WALLET_GROUP, BUTTON_GROUP }
82
82
 
83
83
  internal data class NativeListItem(
84
84
  val key: String,
@@ -163,6 +163,8 @@ internal data class NativeListItem(
163
163
 
164
164
  internal data class NativeListConfig(
165
165
  val generation: Int,
166
+ val structuralTransition: StructuralTransition?,
167
+ val disclosure: Disclosure?,
166
168
  val layout: String,
167
169
  val orientation: String,
168
170
  val gridColumns: Int,
@@ -189,9 +191,36 @@ internal data class NativeListConfig(
189
191
  val fixedFooter: NativeListItem?,
190
192
  val items: List<NativeListItem>,
191
193
  ) {
194
+ internal data class StructuralTransition(
195
+ val id: String,
196
+ val durationMs: Long,
197
+ val anchorKey: String?,
198
+ )
199
+ internal data class Disclosure(
200
+ val expanded: Boolean,
201
+ val expandedTrailingInset: Double,
202
+ )
192
203
  companion object {
193
204
  fun parse(snapshotJson: String): NativeListConfig {
194
205
  val root = JSONObject(snapshotJson)
206
+ val transition = root.optJSONObject("structuralTransition")
207
+ val structuralTransition = transition?.optString("id")?.takeIf(String::isNotEmpty)?.let { id ->
208
+ StructuralTransition(
209
+ id = id,
210
+ durationMs = transition.optDouble("durationMs", 220.0).coerceIn(0.0, 2000.0).toLong(),
211
+ anchorKey = transition.optString("anchorKey").takeIf(String::isNotEmpty),
212
+ )
213
+ }
214
+ val disclosureJson = root.optJSONObject("disclosure")
215
+ require(!root.has("disclosure") || disclosureJson != null) { "Invalid disclosure" }
216
+ val disclosure = disclosureJson?.let { value ->
217
+ require(value.opt("expanded") is Boolean) { "Invalid disclosure.expanded" }
218
+ require(!value.has("expandedTrailingInset") ||
219
+ value.opt("expandedTrailingInset") is Number) { "Invalid disclosure.expandedTrailingInset" }
220
+ val inset = value.optDouble("expandedTrailingInset", 0.0)
221
+ require(inset.isFinite() && inset >= 0.0) { "Invalid disclosure.expandedTrailingInset" }
222
+ Disclosure(value.getBoolean("expanded"), inset)
223
+ }
195
224
  require(root.getInt("schemaVersion") == 1) { "Unsupported schemaVersion" }
196
225
  val layout = root.getJSONObject("layout")
197
226
  val selection = root.optJSONObject("selection")
@@ -214,6 +243,8 @@ internal data class NativeListConfig(
214
243
  val fixedFooter = root.optJSONObject("fixedFooter")?.let(NativeListItem::parse)
215
244
  return NativeListConfig(
216
245
  generation = root.getInt("generation"),
246
+ structuralTransition = structuralTransition,
247
+ disclosure = disclosure,
217
248
  layout = layout.getString("kind"),
218
249
  orientation = layout.optString("orientation", "vertical"),
219
250
  gridColumns = layout.optInt("gridColumns", 1).coerceIn(1, 7),
@@ -240,6 +240,9 @@ internal class OneKeyIconView(context: android.content.Context) : View(context)
240
240
  private val selectorIconColors: Map<String, List<Int?>> =
241
241
  mapOf(
242
242
  "GlobusOutline" to listOf(null),
243
+ "GasSolid" to listOf(null),
244
+ "RefreshCcwOutline" to listOf(null),
245
+ "CryptoCoinOutline" to listOf(null, null),
243
246
  "LockSolid" to listOf(null),
244
247
  "GoogleIllus" to
245
248
  listOf(
@@ -267,6 +270,9 @@ internal class OneKeyIconView(context: android.content.Context) : View(context)
267
270
  private val selectorIconViewBoxes =
268
271
  mapOf(
269
272
  "GlobusOutline" to 24f,
273
+ "GasSolid" to 24f,
274
+ "RefreshCcwOutline" to 24f,
275
+ "CryptoCoinOutline" to 24f,
270
276
  "LockSolid" to 24f,
271
277
  "GoogleIllus" to 24f,
272
278
  "AppleBrand" to 16f,
@@ -282,6 +288,9 @@ internal class OneKeyIconView(context: android.content.Context) : View(context)
282
288
  private val actionIconFillTypes =
283
289
  mapOf(
284
290
  "GlobusOutline" to listOf(Path.FillType.EVEN_ODD),
291
+ "GasSolid" to listOf(Path.FillType.EVEN_ODD),
292
+ "RefreshCcwOutline" to listOf(Path.FillType.WINDING),
293
+ "CryptoCoinOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
285
294
  "LockSolid" to listOf(Path.FillType.EVEN_ODD),
286
295
  "GoogleIllus" to
287
296
  listOf(
@@ -324,6 +333,19 @@ internal class OneKeyIconView(context: android.content.Context) : View(context)
324
333
  // Exact 24x24 paths from app-monorepo packages/components Icon sources.
325
334
  private val iconPaths =
326
335
  mapOf(
336
+ "RefreshCcwOutline" to
337
+ listOf(
338
+ "M5 12a7 7 0 0 0 7 7 7.15 7.15 0 0 0 4.955-2h-2.943v-2h6v6h-2v-2.257A9.148 9.148 0 0 1 12 21a9 9 0 0 1-9-9v-1h2v1Zm1-6.754A9.148 9.148 0 0 1 12 3a9 9 0 0 1 9 9v1h-2v-1a7 7 0 0 0-7-7 7.15 7.15 0 0 0-4.955 2H10v2H4V3h2v2.246Z"
339
+ ),
340
+ "GasSolid" to
341
+ listOf(
342
+ "M15 9h4v7h1V8.414L17.586 6 19 4.586l3 3V18h-5v-7h-2v8h1v2H2v-2h1V3h12zM6 9v2h6V9z"
343
+ ),
344
+ "CryptoCoinOutline" to
345
+ listOf(
346
+ "M11 16.387a4.5 4.5 0 0 1 0-8.775V6h2v1.613a4.5 4.5 0 0 1 2.856 2.067l-1.712 1.032a2.5 2.5 0 1 0 0 2.576l1.712 1.032A4.5 4.5 0 0 1 13 16.386V18h-2z",
347
+ "M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0",
348
+ ),
327
349
  // OneKey patch: official selector SVG path geometry.
328
350
  "GlobusOutline" to
329
351
  listOf(
@@ -17,6 +17,7 @@ internal object NativeListRendererRegistry {
17
17
  NativeListRendererKey.ACTIVITY to ::NativeListActivityRowView,
18
18
  NativeListRendererKey.SYSTEM to ::NativeListSystemRowView,
19
19
  NativeListRendererKey.ACTION to ::NativeListActionRowView,
20
+ NativeListRendererKey.BUTTON_GROUP to ::NativeListButtonGroupRowView,
20
21
  )
21
22
 
22
23
  private val keys =
@@ -33,6 +34,7 @@ internal object NativeListRendererRegistry {
33
34
  "activity" to NativeListRendererKey.ACTIVITY,
34
35
  "system" to NativeListRendererKey.SYSTEM,
35
36
  "action" to NativeListRendererKey.ACTION,
37
+ "buttonGroup" to NativeListRendererKey.BUTTON_GROUP,
36
38
  )
37
39
 
38
40
  fun key(type: String) = keys.getValue(type)
@@ -106,6 +106,8 @@ internal abstract class NativeListRendererRowView(protected val reactContext: Th
106
106
  protected open val pressedColorName = "rowPressedBackground"
107
107
  protected open val pressedColorFallback = "#00000017"
108
108
  private var current: NativeListItem? = null
109
+ protected val boundItem: NativeListItem?
110
+ get() = current
109
111
  private var theme: JSONObject? = null
110
112
  private var listLayout = "linear"
111
113
  private var itemIndex: Int? = null
@@ -52,7 +52,7 @@ internal data class NativeListResolvedText(
52
52
  view.letterSpacing = 0f
53
53
  view.gravity = gravity
54
54
  view.opticalOffsetY = offset
55
- view.maxLines = lines
55
+ view.maxLines = if (lines == 0) Int.MAX_VALUE else lines
56
56
  view.setHorizontallyScrolling(lines == 1 && explicitTruncation)
57
57
  view.ellipsize = if (explicitTruncation && !clip) TextUtils.TruncateAt.END else null
58
58
  view.setLineSpacing(0f, 1f)
@@ -87,7 +87,8 @@ internal data class NativeListResolvedText(
87
87
  sourceScale: Boolean,
88
88
  ): NativeListResolvedText {
89
89
  val density = context.resources.displayMetrics.density
90
- val maxLines = (style?.optInt("lines", lines) ?: lines).coerceIn(1, 3)
90
+ val requestedLines = style?.optInt("lines", lines) ?: lines
91
+ val maxLines = if (requestedLines == 0) 0 else requestedLines.coerceIn(1, 3)
91
92
  val truncation = style?.let { it.has("lines") || it.has("truncate") } == true
92
93
  val fontSize =
93
94
  if (style?.has("fontSize") == true) (style.optDouble("fontSize") * density).toFloat()
@@ -26,6 +26,7 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
26
26
  private val mainColumn = LinearLayout(context)
27
27
  private val titleLine = PackedTitleLineLayout(context)
28
28
  private val title = DottedUnderlineTextView(context)
29
+ private val leadingBadge = NativeListTextView(context)
29
30
  private val titleSpinner = ProgressBar(context, null, android.R.attr.progressBarStyleSmall).apply {
30
31
  isIndeterminate = true
31
32
  importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -165,6 +166,12 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
165
166
  currentTheme = theme
166
167
  currentLayout = layout
167
168
  removeAllViews()
169
+ if (trailingViews[0].parent === titleLine) {
170
+ titleLine.removeView(trailingViews[0])
171
+ trailingColumn.addView(trailingViews[0], 0)
172
+ }
173
+ titleLine.removeView(leadingBadge)
174
+ leadingBadge.text = ""
168
175
  titleLine.removeView(headerTitleIcon)
169
176
  trailingColumn.removeView(headerValueIcon)
170
177
  paintDefaults.forEach { (view, flags) -> view.paintFlags = flags }
@@ -188,6 +195,10 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
188
195
  view.ellipsize = null
189
196
  view.setHorizontallyScrolling(false)
190
197
  view.opticalOffsetY = 0f
198
+ view.helpBindingGeneration += 1
199
+ view.guardsHelpTouches = false
200
+ view.primaryHelpOnly = false
201
+ view.showsHelpUnderline = false
191
202
  view.gravity = Gravity.START
192
203
  view.letterSpacing = 0f
193
204
  view.fontFeatureSettings = "tnum"
@@ -291,6 +302,7 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
291
302
  }
292
303
  }
293
304
  applyTextStyle(item)
305
+ bindPositionHeaderFields(item, theme)
294
306
  titleSpinner.indeterminateTintList = ColorStateList.valueOf(title.currentTextColor)
295
307
  style
296
308
  .optJSONObject("container")
@@ -307,6 +319,57 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
307
319
  applySelectorTypography(item)
308
320
  }
309
321
 
322
+ private fun bindPositionHeaderFields(item: NativeListItem, theme: JSONObject?) {
323
+ val style = item.json.optJSONObject("style") ?: JSONObject()
324
+ item.json.optJSONObject("leadingBadge")?.let { badge ->
325
+ leadingBadge.text = badge.optString("text")
326
+ leadingBadge.textSize = sp(12f)
327
+ leadingBadge.typeface = fonts.medium(context)
328
+ leadingBadge.includeFontPadding = false
329
+ val tones = mapOf("info" to ("info" to "#0F69F6"), "success" to ("positive" to "#008F5A"), "warning" to ("caution" to "#A35E00"), "danger" to ("negative" to "#CE2C31"))
330
+ val tone = tones[badge.optString("tone")]
331
+ if (tone != null) {
332
+ val tint = color(theme, tone.first, tone.second)
333
+ leadingBadge.setTextColor(tint)
334
+ val fallback = (tint and 0x00FFFFFF) or (31 shl 24)
335
+ val fill = if (badge.optString("tone") == "success" && theme?.has("successBackground") == true)
336
+ safeColor(theme.optString("successBackground"), fallback) else fallback
337
+ leadingBadge.background = roundedFill(fill, 4f)
338
+ } else {
339
+ leadingBadge.setTextColor(color(theme, "secondaryText", "#0000009B"))
340
+ leadingBadge.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 4f)
341
+ }
342
+ leadingBadge.setPadding(dp(8), dp(2), dp(8), dp(2))
343
+ TextViewCompat.setLineHeight(leadingBadge, dp(16))
344
+ titleLine.addView(leadingBadge, 0, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)).apply { marginEnd = dp(8) })
345
+ }
346
+ if (style.has("titleDecoration")) {
347
+ title.showsDottedUnderline = false
348
+ title.showsHelpUnderline = style.optString("titleDecoration") == "dashed"
349
+ title.setPadding(0, 0, 0, if (title.showsHelpUnderline) dp(3) else 0)
350
+ }
351
+ val value = trailingViews[0]
352
+ if (style.optString("valuePlacement") == "afterTitle") {
353
+ trailingColumn.removeView(value)
354
+ title.layoutParams = wrap()
355
+ titleLine.packsChildrenAtStart = false
356
+ titleLine.addView(value, wrap().apply { marginStart = dp(8) })
357
+ }
358
+ item.json.optJSONObject("valueHelp")?.let { help ->
359
+ value.guardsHelpTouches = true
360
+ value.setOnClickListener(null)
361
+ value.isClickable = false
362
+ value.showsHelpUnderline = help.optString("underline") == "dashed"
363
+ if (help.optString("presentation") == "popover") {
364
+ value.setOnClickListener {
365
+ currentItem?.takeUnless { it.json.optBoolean("disabled") }?.let {
366
+ emitAction(it, help.optString("actionKey"), null, value, "trailingAccessory", 0)
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+
310
373
  private fun applyTextStyle(item: NativeListItem) {
311
374
  val style = item.json.optJSONObject("style") ?: return
312
375
  for ((slot, view) in
@@ -411,6 +474,12 @@ internal class NativeListSectionHeaderRowView(context: ThemedReactContext, fonts
411
474
  override fun recycleContent() {
412
475
  checkboxControl.reset()
413
476
  currentItem = null
477
+ title.setOnClickListener(null)
478
+ title.isClickable = false
479
+ trailingViews[0].helpBindingGeneration += 1
480
+ trailingViews[0].setOnClickListener(null)
481
+ trailingViews[0].isClickable = false
482
+ trailingViews[0].showsHelpUnderline = false
414
483
  titleSpinner.visibility = GONE
415
484
  }
416
485
 
@@ -1,9 +1,62 @@
1
1
  package com.margelo.nitro.nativelist
2
2
 
3
3
  import android.graphics.Canvas
4
+ import android.graphics.DashPathEffect
5
+ import android.graphics.Paint
6
+ import android.graphics.RectF
7
+ import android.view.MotionEvent
4
8
  import android.widget.TextView
5
9
 
6
10
  internal open class NativeListTextView(context: android.content.Context) : TextView(context) {
11
+ var helpBindingGeneration = 0
12
+ var guardsHelpTouches = false
13
+ private var helpDownGeneration = -1
14
+ var primaryHelpOnly = false
15
+ var showsHelpUnderline = false
16
+ set(value) { field = value; invalidate() }
17
+ private val helpPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
18
+ style = Paint.Style.STROKE
19
+ strokeWidth = resources.displayMetrics.density
20
+ pathEffect = DashPathEffect(floatArrayOf(3 * strokeWidth, 2 * strokeWidth), 0f)
21
+ }
22
+
23
+ val helpAnchorBounds: RectF?
24
+ get() {
25
+ if (!primaryHelpOnly) return null
26
+ val textLayout = layout ?: return null
27
+ if (textLayout.lineCount == 0) return null
28
+ return RectF(
29
+ totalPaddingLeft + textLayout.getLineLeft(0),
30
+ (totalPaddingTop + textLayout.getLineTop(0)).toFloat() + opticalOffsetY,
31
+ totalPaddingLeft + textLayout.getLineRight(0),
32
+ (totalPaddingTop + textLayout.getLineBottom(0)).toFloat() + opticalOffsetY,
33
+ )
34
+ }
35
+
36
+ override fun onTouchEvent(event: MotionEvent): Boolean {
37
+ if (primaryHelpOnly && event.actionMasked == MotionEvent.ACTION_DOWN &&
38
+ helpAnchorBounds?.contains(event.x, event.y) != true) return false
39
+ if (guardsHelpTouches) {
40
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) helpDownGeneration = helpBindingGeneration
41
+ if (event.actionMasked == MotionEvent.ACTION_UP && helpDownGeneration != helpBindingGeneration) {
42
+ isPressed = false
43
+ return true
44
+ }
45
+ }
46
+ return super.onTouchEvent(event)
47
+ }
48
+
49
+ private fun drawHelpUnderline(canvas: Canvas) {
50
+ if (!showsHelpUnderline) return
51
+ val textLayout = layout ?: return
52
+ if (textLayout.lineCount == 0) return
53
+ helpPaint.color = currentTextColor
54
+ val left = totalPaddingLeft + textLayout.getLineLeft(0)
55
+ val right = totalPaddingLeft + textLayout.getLineRight(0)
56
+ val bottom = totalPaddingTop + textLayout.getLineBottom(0) - resources.displayMetrics.density
57
+ canvas.drawLine(left, bottom, right, bottom, helpPaint)
58
+ }
59
+
7
60
  var rowHorizontallyScrolling = false
8
61
  private set
9
62
 
@@ -16,10 +69,11 @@ internal open class NativeListTextView(context: android.content.Context) : TextV
16
69
  set(value) { field = value; invalidate() }
17
70
 
18
71
  override fun onDraw(canvas: Canvas) {
19
- if (opticalOffsetY == 0f) { super.onDraw(canvas); return }
72
+ if (opticalOffsetY == 0f) { super.onDraw(canvas); drawHelpUnderline(canvas); return }
20
73
  val checkpoint = canvas.save()
21
74
  canvas.translate(0f, opticalOffsetY)
22
75
  super.onDraw(canvas)
76
+ drawHelpUnderline(canvas)
23
77
  canvas.restoreToCount(checkpoint)
24
78
  }
25
79
  }