@onekeyfe/react-native-native-list 6.7.0-alpha.406 → 6.7.0-alpha.407

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.
@@ -18,6 +18,9 @@ internal class NativeListActivityDetailsView(private val reactContext: ThemedRea
18
18
  private val main = LinearLayout(context).apply { orientation = HORIZONTAL; gravity = Gravity.CENTER_VERTICAL }
19
19
  private val identityColumn = LinearLayout(context).apply { orientation = HORIZONTAL; gravity = Gravity.CENTER_VERTICAL }
20
20
  private val visual = NativeListLeadingVisual(reactContext, fonts)
21
+ /** Whether the bound leading visual lays a badge past its frame; see [setClipping]. */
22
+ var leadingOverhangs = false
23
+ private set
21
24
  private val identity = LinearLayout(context).apply { orientation = VERTICAL }
22
25
  private val titleLine = LinearLayout(context).apply { orientation = HORIZONTAL; gravity = Gravity.CENTER_VERTICAL }
23
26
  private val title = NativeListTextView(context)
@@ -49,6 +52,18 @@ internal class NativeListActivityDetailsView(private val reactContext: ThemedRea
49
52
  addView(main, LayoutParams(-1, -2))
50
53
  addView(actions, LayoutParams(-1, -2))
51
54
  }
55
+ /**
56
+ * Android clips a child to its own bounds while its parent clips children, so
57
+ * every view between the leading visual and the row host stops clipping when
58
+ * the visual's network badge overhangs its box (SPEC "Leading badge overhang",
59
+ * OK-66275); the host row applies the same policy to itself.
60
+ */
61
+ private fun setClipping(clips: Boolean) {
62
+ for (group in listOf(identityColumn, main, this)) {
63
+ group.clipChildren = clips
64
+ group.clipToPadding = clips
65
+ }
66
+ }
52
67
  private fun dp(value: Int) = if (sourceScale) (value * resources.displayMetrics.density).roundToInt() else NativeListScale.dp(resources, value)
53
68
  private fun px(value: Double) = (value * resources.displayMetrics.density).roundToInt()
54
69
  private fun color(tone: String, theme: JSONObject?): Int {
@@ -79,6 +94,8 @@ internal class NativeListActivityDetailsView(private val reactContext: ThemedRea
79
94
  visual.visibility = if (hideLeading) GONE else VISIBLE
80
95
  if (hideLeading) visual.recycle()
81
96
  else visual.bind(data.optJSONObject("leading") ?: JSONObject(), image, item.key, theme, false, sourceScale, data.optJSONObject("secondaryLeading"))
97
+ leadingOverhangs = !hideLeading && visual.requestsUnclippedHost
98
+ setClipping(nativeListActivityHostClips(leadingOverhangs))
82
99
  amounts.layoutParams = if (table) LayoutParams(0, -2, 1f).apply { marginStart = gap } else LayoutParams(-2, -2).apply { marginStart = gap }
83
100
  text(title, data.optString("title"), style.optJSONObject("title"), 16f, color("primary", theme), 24, "medium")
84
101
  text(description, data.optString("description"), style.optJSONObject("description"), 14f, color("secondary", theme), 20, lines = 2)
@@ -153,7 +170,11 @@ internal class NativeListActivityDetailsView(private val reactContext: ThemedRea
153
170
  }
154
171
  label.text = styled
155
172
  }
156
- fun recycle() { visual.recycle(); visual.visibility = VISIBLE; amountViews.forEach { it.recycle() }; description.setOnClickListener(null); actions.removeAllViews() }
173
+ fun recycle() {
174
+ visual.recycle(); visual.visibility = VISIBLE; amountViews.forEach { it.recycle() }; description.setOnClickListener(null); actions.removeAllViews()
175
+ leadingOverhangs = false
176
+ setClipping(true)
177
+ }
157
178
  fun dispose() { recycle(); visual.dispose(); amountViews.forEach { it.dispose() } }
158
179
 
159
180
  private inner class AmountView : LinearLayout(context) {
@@ -83,10 +83,11 @@ internal class NativeListActivityRowView(context: ThemedReactContext, fonts: Nat
83
83
  setPadding(hp, vp, hp, vp)
84
84
  visual.recycle()
85
85
  details.bind(item, theme, sourceScale)
86
- // This view may be reused from a legacy row whose overhanging badge
87
- // switched clipping off; a rich row has no badge on the host, so restore
88
- // the defaults before returning.
89
- setHostClipping(nativeListActivityHostClips(rich = true, leadingOverhangs = false))
86
+ // The rich row's leading visual lives on the details view, where its
87
+ // network badge overhangs the box (OK-66275): this host must stop clipping
88
+ // as well, and a view reused from a badge row clips again when the bound
89
+ // row has no badge.
90
+ setHostClipping(nativeListActivityHostClips(details.leadingOverhangs))
90
91
  return
91
92
  }
92
93
  details.recycle()
@@ -174,12 +175,7 @@ internal class NativeListActivityRowView(context: ThemedReactContext, fonts: Nat
174
175
  // Stop clipping the content row and this row's padding exactly when the
175
176
  // bound visual reports such a badge (SPEC "Leading badge overhang"), as the
176
177
  // Identity renderer does.
177
- setHostClipping(
178
- nativeListActivityHostClips(
179
- rich = false,
180
- leadingOverhangs = !hideLeading && visual.requestsUnclippedHost,
181
- )
182
- )
178
+ setHostClipping(nativeListActivityHostClips(!hideLeading && visual.requestsUnclippedHost))
183
179
  bind(
184
180
  title,
185
181
  data.optString("title"),
@@ -50,16 +50,17 @@ internal fun nativeListLeadingVisualOverhangsFrame(
50
50
  ): Boolean = kind == "token" && sourceCount > 1 || hasCornerIcon || overlayCount > 0
51
51
 
52
52
  /**
53
- * Whether an Activity row host clips its children and padding for this bind.
54
- * One row view serves both the legacy row and the rich (`amounts`) row and is
55
- * reused across binds, so clipping is derived from the bound item alone and
56
- * applied on every bind: only a legacy row whose leading visual lays a badge
57
- * past its frame ([nativeListLeadingVisualOverhangsFrame], OK-66071) opts out;
58
- * a rich row keeps Android's default clipping even when the view it reuses
59
- * last showed such a badge.
53
+ * Whether an Activity row host (and, for a rich `amounts` row, the details view
54
+ * with its nested columns) clips its children and padding for this bind. One
55
+ * row view serves both the legacy row and the rich row and is reused across
56
+ * binds, so clipping is derived from the bound item alone and applied on every
57
+ * bind: a row whose bound leading visual lays a badge past its frame
58
+ * ([nativeListLeadingVisualOverhangsFrame]) opts out, whether that visual sits
59
+ * on the host (legacy row, OK-66071) or on the details view (rich row,
60
+ * OK-66275); a row without such a badge clips again even when the view it
61
+ * reuses last showed one.
60
62
  */
61
- internal fun nativeListActivityHostClips(rich: Boolean, leadingOverhangs: Boolean): Boolean =
62
- rich || !leadingOverhangs
63
+ internal fun nativeListActivityHostClips(leadingOverhangs: Boolean): Boolean = !leadingOverhangs
63
64
 
64
65
  internal data class NativeListSlotFrame(val x: Int, val y: Int, val width: Int, val height: Int)
65
66
 
@@ -5,10 +5,12 @@ import org.junit.Test
5
5
 
6
6
  class NativeListActivityHostClipPolicyTest {
7
7
  // One Activity row view serves legacy and rich rows and is reused across
8
- // binds. A legacy row with an overhanging network badge switches the host's
9
- // clipping off (OK-66071); the next bind must derive clipping afresh, so a
10
- // rich row reusing that view clips again instead of drawing its details over
11
- // a neighbouring row (PR #185 review).
8
+ // binds. A row with an overhanging network badge switches the host's
9
+ // clipping off, whether the badge sits on the host's visual (legacy row,
10
+ // OK-66071) or on the details view's visual (rich row, OK-66275); the next
11
+ // bind must derive clipping afresh, so a row without a badge reusing that
12
+ // view clips again instead of drawing over a neighbouring row (PR #185
13
+ // review).
12
14
  @Test
13
15
  fun rebindSequenceDerivesClippingFromEachBoundItem() {
14
16
  data class Bind(val name: String, val rich: Boolean, val leadingOverhangs: Boolean, val clips: Boolean)
@@ -17,13 +19,17 @@ class NativeListActivityHostClipPolicyTest {
17
19
  Bind("legacy row, plain token", rich = false, leadingOverhangs = false, clips = true),
18
20
  Bind("legacy row, network badge", rich = false, leadingOverhangs = true, clips = false),
19
21
  Bind("rich row reusing the badge row's view", rich = true, leadingOverhangs = false, clips = true),
22
+ // The app's history rows carry `amounts`, so their badge lives on the
23
+ // details view's leading visual: a rich row with such a badge must stop
24
+ // clipping too (OK-66275).
25
+ Bind("rich row, network badge in details", rich = true, leadingOverhangs = true, clips = false),
20
26
  Bind("legacy row, badge again", rich = false, leadingOverhangs = true, clips = false),
21
27
  Bind("legacy row, hidden leading", rich = false, leadingOverhangs = false, clips = true),
22
28
  )
23
29
 
24
30
  assertEquals(
25
31
  binds.map(Bind::clips),
26
- binds.map { nativeListActivityHostClips(rich = it.rich, leadingOverhangs = it.leadingOverhangs) },
32
+ binds.map { nativeListActivityHostClips(leadingOverhangs = it.leadingOverhangs) },
27
33
  )
28
34
  }
29
35
  }
package/docs/SPEC.md CHANGED
@@ -778,7 +778,14 @@ Host clipping is per-bind state. A row view that serves several row shapes
778
778
  (the Android Activity view serves the legacy row and the rich `amounts` row) is
779
779
  reused across binds, so each bind MUST derive clipping from the item it binds
780
780
  (`nativeListActivityHostClips`): a view that last showed an overhanging badge
781
- clips again when it is rebound to a row without one.
781
+ clips again when it is rebound to a row without one. The rule follows the
782
+ visual, not the row shape: the rich row renders its leading visual on the
783
+ details view, so that view and every column between the visual and the host
784
+ stop clipping too, and the host derives its own clipping from the details
785
+ view's visual (`leadingOverhangs`, OK-66275). Before this rule the rich branch
786
+ forced the host's clipping back on and left the details view at Android's
787
+ defaults, so the app's history rows (which always carry `amounts`) still showed
788
+ the badge cut at the bottom-right after the legacy branch had been fixed.
782
789
 
783
790
  The row's own shape still clips on every platform: iOS masks the cell to its
784
791
  corner radius (`masksToBounds`), Web keeps the row `overflow: hidden`, and
@@ -815,9 +822,10 @@ over the badge. The per-source geometry is one pure function on Android
815
822
  `NativeListLeadingVisual.layoutSubviews` on iOS; both restore the stacking
816
823
  order by role on every bind because slots are reused across roles.
817
824
 
818
- The Android Activity renderer also applies the badge-overhang rule above
819
- (`requestsUnclippedHost` stops the content row and the row padding clipping),
820
- so the history rows' network badges render whole instead of cut flat.
825
+ The Android Activity renderer also applies the badge-overhang rule above on
826
+ both of its branches (`requestsUnclippedHost` stops the legacy content row, the
827
+ rich details view with its columns, and the row padding clipping), so the
828
+ history rows' network badges render whole instead of cut flat.
821
829
 
822
830
  ## Failures, fallback and limits
823
831
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-list",
3
- "version": "6.7.0-alpha.406",
3
+ "version": "6.7.0-alpha.407",
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",
@@ -86,8 +86,8 @@
86
86
  "typescript": "^5.9.2"
87
87
  },
88
88
  "peerDependencies": {
89
- "@onekeyfe/react-native-image": "6.7.0-alpha.406",
90
- "@onekeyfe/react-native-native-logger": "6.7.0-alpha.406",
89
+ "@onekeyfe/react-native-image": "6.7.0-alpha.407",
90
+ "@onekeyfe/react-native-native-logger": "6.7.0-alpha.407",
91
91
  "react": "*",
92
92
  "react-native": "*",
93
93
  "react-native-nitro-modules": "0.37.0"