react-native-list 1.0.0 → 2.0.0-alpha.1

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 (200) hide show
  1. package/README.md +190 -32
  2. package/ReactNativeList.podspec +39 -0
  3. package/android/CMakeLists.txt +48 -0
  4. package/android/build.gradle +151 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/JHybridUiListModule.cpp +192 -0
  9. package/android/src/main/cpp/JHybridUiListModule.h +50 -0
  10. package/android/src/main/cpp/cpp-adapter.cpp +12 -0
  11. package/android/src/main/java/com/hannojg/reactnativelist/ReactNativeListPackage.kt +27 -0
  12. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListDataSource.kt +146 -0
  13. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListLayout.kt +86 -0
  14. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListModule.kt +116 -0
  15. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListView.kt +410 -0
  16. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridViewHolder.kt +9 -0
  17. package/android/src/main/java/com/margelo/nitro/reactnativelist/NativeListAdapter.kt +217 -0
  18. package/ios/DataSource/HybridNativeListDataSource.swift +213 -0
  19. package/ios/HybridObjects/HybridUiListModule.swift +49 -0
  20. package/ios/HybridObjects/HybridViewHolder.swift +16 -0
  21. package/ios/Layout/HybridNativeListLayout.swift +128 -0
  22. package/ios/Utils/ErrorUtils.h +26 -0
  23. package/ios/Utils/HybridIOSWorkletsModuleProxyHolder.swift +10 -0
  24. package/ios/Utils/SurfaceHelper.h +20 -0
  25. package/ios/Utils/SurfaceHelper.mm +144 -0
  26. package/ios/Utils/SurfacePresenterRegistry.h +17 -0
  27. package/ios/Utils/SurfacePresenterRegistry.m +31 -0
  28. package/ios/Utils/TurboModuleInstaller.h +18 -0
  29. package/ios/Utils/TurboModuleInstaller.mm +267 -0
  30. package/ios/Views/HostCell.swift +216 -0
  31. package/ios/Views/HybridUiListView.swift +695 -0
  32. package/metro/RendererProxyThreadSwitch.js +66 -0
  33. package/metro-config.d.ts +1 -0
  34. package/metro-config.js +52 -0
  35. package/nitro.json +47 -0
  36. package/nitrogen/generated/.gitattributes +1 -0
  37. package/nitrogen/generated/android/ReactNativeList+autolinking.cmake +99 -0
  38. package/nitrogen/generated/android/ReactNativeList+autolinking.gradle +27 -0
  39. package/nitrogen/generated/android/ReactNativeListOnLoad.cpp +156 -0
  40. package/nitrogen/generated/android/ReactNativeListOnLoad.hpp +34 -0
  41. package/nitrogen/generated/android/c++/JFunc_bool_NativeListItem_NativeListItem.hpp +83 -0
  42. package/nitrogen/generated/android/c++/JFunc_bool_double_NativeListItem_double.hpp +83 -0
  43. package/nitrogen/generated/android/c++/JFunc_double_std__string.hpp +78 -0
  44. package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.cpp +49 -0
  45. package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.hpp +63 -0
  46. package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.cpp +63 -0
  47. package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.hpp +65 -0
  48. package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.cpp +101 -0
  49. package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.hpp +70 -0
  50. package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.cpp +49 -0
  51. package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.hpp +63 -0
  52. package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.cpp +65 -0
  53. package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.hpp +64 -0
  54. package/nitrogen/generated/android/c++/JHybridUiListViewSpec.cpp +92 -0
  55. package/nitrogen/generated/android/c++/JHybridUiListViewSpec.hpp +67 -0
  56. package/nitrogen/generated/android/c++/JHybridViewHolderSpec.cpp +49 -0
  57. package/nitrogen/generated/android/c++/JHybridViewHolderSpec.hpp +63 -0
  58. package/nitrogen/generated/android/c++/JNativeItemSizeEstimate.hpp +61 -0
  59. package/nitrogen/generated/android/c++/JNativeLinearListLayoutConfig.hpp +81 -0
  60. package/nitrogen/generated/android/c++/JNativeLinearListLayoutIOSConfig.hpp +59 -0
  61. package/nitrogen/generated/android/c++/JNativeListItem.hpp +76 -0
  62. package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.cpp +26 -0
  63. package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.hpp +72 -0
  64. package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.cpp +53 -0
  65. package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.hpp +49 -0
  66. package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.cpp +53 -0
  67. package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.hpp +49 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_NativeListItem_NativeListItem.kt +80 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_double_NativeListItem_double.kt +80 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_double_std__string.kt +80 -0
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridIOSWorkletsModuleProxyHolderSpec.kt +52 -0
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeLinearListLayoutSpec.kt +54 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListDataSourceSpec.kt +87 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListLayoutSpec.kt +52 -0
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListModuleSpec.kt +59 -0
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListViewSpec.kt +76 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridViewHolderSpec.kt +53 -0
  78. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeItemSizeEstimate.kt +56 -0
  79. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutConfig.kt +76 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutIOSConfig.kt +51 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeListItem.kt +71 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/ReactNativeListOnLoad.kt +35 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Variant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.kt +62 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewManager.kt +80 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewStateUpdater.kt +23 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderManager.kt +80 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderStateUpdater.kt +23 -0
  88. package/nitrogen/generated/ios/ReactNativeList+autolinking.rb +62 -0
  89. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.cpp +162 -0
  90. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.hpp +368 -0
  91. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Umbrella.hpp +92 -0
  92. package/nitrogen/generated/ios/ReactNativeListAutolinking.mm +83 -0
  93. package/nitrogen/generated/ios/ReactNativeListAutolinking.swift +86 -0
  94. package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.cpp +11 -0
  95. package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.hpp +75 -0
  96. package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.cpp +11 -0
  97. package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.hpp +92 -0
  98. package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.cpp +11 -0
  99. package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.hpp +132 -0
  100. package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.cpp +11 -0
  101. package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.hpp +75 -0
  102. package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.cpp +11 -0
  103. package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.hpp +93 -0
  104. package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.cpp +11 -0
  105. package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.hpp +121 -0
  106. package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.cpp +11 -0
  107. package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.hpp +75 -0
  108. package/nitrogen/generated/ios/c++/views/HybridUiListViewComponent.mm +118 -0
  109. package/nitrogen/generated/ios/c++/views/HybridViewHolderComponent.mm +118 -0
  110. package/nitrogen/generated/ios/swift/Func_bool_NativeListItem_NativeListItem.swift +47 -0
  111. package/nitrogen/generated/ios/swift/Func_bool_double_NativeListItem_double.swift +47 -0
  112. package/nitrogen/generated/ios/swift/Func_double_std__string.swift +47 -0
  113. package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec.swift +55 -0
  114. package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec_cxx.swift +128 -0
  115. package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec.swift +55 -0
  116. package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec_cxx.swift +140 -0
  117. package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec.swift +62 -0
  118. package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec_cxx.swift +222 -0
  119. package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec.swift +55 -0
  120. package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec_cxx.swift +128 -0
  121. package/nitrogen/generated/ios/swift/HybridUiListModuleSpec.swift +56 -0
  122. package/nitrogen/generated/ios/swift/HybridUiListModuleSpec_cxx.swift +175 -0
  123. package/nitrogen/generated/ios/swift/HybridUiListViewSpec.swift +59 -0
  124. package/nitrogen/generated/ios/swift/HybridUiListViewSpec_cxx.swift +227 -0
  125. package/nitrogen/generated/ios/swift/HybridViewHolderSpec.swift +55 -0
  126. package/nitrogen/generated/ios/swift/HybridViewHolderSpec_cxx.swift +147 -0
  127. package/nitrogen/generated/ios/swift/NativeItemSizeEstimate.swift +60 -0
  128. package/nitrogen/generated/ios/swift/NativeLinearListLayoutConfig.swift +60 -0
  129. package/nitrogen/generated/ios/swift/NativeLinearListLayoutIOSConfig.swift +35 -0
  130. package/nitrogen/generated/ios/swift/NativeListItem.swift +75 -0
  131. package/nitrogen/generated/ios/swift/Variant_NullType__any_HybridIOSWorkletsModuleProxyHolderSpec_.swift +30 -0
  132. package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.cpp +21 -0
  133. package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.hpp +62 -0
  134. package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.cpp +22 -0
  135. package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.hpp +67 -0
  136. package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.cpp +28 -0
  137. package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.hpp +72 -0
  138. package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.cpp +21 -0
  139. package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.hpp +62 -0
  140. package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.cpp +22 -0
  141. package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.hpp +68 -0
  142. package/nitrogen/generated/shared/c++/HybridUiListViewSpec.cpp +25 -0
  143. package/nitrogen/generated/shared/c++/HybridUiListViewSpec.hpp +79 -0
  144. package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.cpp +23 -0
  145. package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.hpp +65 -0
  146. package/nitrogen/generated/shared/c++/HybridViewHolderSpec.cpp +21 -0
  147. package/nitrogen/generated/shared/c++/HybridViewHolderSpec.hpp +62 -0
  148. package/nitrogen/generated/shared/c++/NativeItemSizeEstimate.hpp +87 -0
  149. package/nitrogen/generated/shared/c++/NativeLinearListLayoutConfig.hpp +105 -0
  150. package/nitrogen/generated/shared/c++/NativeLinearListLayoutIOSConfig.hpp +85 -0
  151. package/nitrogen/generated/shared/c++/NativeListItem.hpp +101 -0
  152. package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.cpp +72 -0
  153. package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.hpp +109 -0
  154. package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.cpp +72 -0
  155. package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.hpp +109 -0
  156. package/nitrogen/generated/shared/json/UiListViewConfig.json +9 -0
  157. package/nitrogen/generated/shared/json/ViewHolderConfig.json +9 -0
  158. package/package.json +152 -5
  159. package/react-native.config.js +16 -0
  160. package/src/ListDataSource.ts +232 -0
  161. package/src/ListLayout.ts +95 -0
  162. package/src/UiListModule.ts +5 -0
  163. package/src/hooks/useChangeEffect.ts +50 -0
  164. package/src/index.tsx +49 -0
  165. package/src/privateGlobals.ts +20 -0
  166. package/src/renderer/fabric/RenderHelper.ts +29 -0
  167. package/src/renderer/fabric/UiManagerHelper.ts +5 -0
  168. package/src/renderer/react/ReactFabricMirror.bundle.js +1984 -0
  169. package/src/renderer/react/ReactFabricMirror.ts +766 -0
  170. package/src/renderer/react/ReactFabricRenderer.ts +11 -0
  171. package/src/specs/IOSWorkletsModuleProxyHolder.nitro.ts +6 -0
  172. package/src/specs/NativeLinearListLayout.nitro.ts +23 -0
  173. package/src/specs/NativeListDataSource.nitro.ts +28 -0
  174. package/src/specs/NativeListLayout.nitro.ts +6 -0
  175. package/src/specs/UIListModule.nitro.ts +13 -0
  176. package/src/specs/UIManagerHelper.nitro.ts +34 -0
  177. package/src/specs/UiListView.nitro.ts +31 -0
  178. package/src/specs/ViewHolder.nitro.ts +11 -0
  179. package/src/views/List.tsx +525 -0
  180. package/src/views/UiListHostComponent.ts +8 -0
  181. package/FillRateHelper.js +0 -179
  182. package/FlatList.js +0 -494
  183. package/LICENSE.md +0 -31
  184. package/ListView/__mocks__/ListViewMock.js +0 -55
  185. package/MetroListView.js +0 -166
  186. package/SectionList.js +0 -291
  187. package/ViewabilityHelper.js +0 -260
  188. package/VirtualizeUtils.js +0 -163
  189. package/VirtualizedList.js +0 -861
  190. package/VirtualizedSectionList.js +0 -397
  191. package/__flowtests__/FlatList-flowtest.js +0 -90
  192. package/__flowtests__/SectionList-flowtest.js +0 -89
  193. package/__tests__/FillRateHelper-test.js +0 -106
  194. package/__tests__/FlatList-test.js +0 -64
  195. package/__tests__/SectionList-test.js +0 -67
  196. package/__tests__/ViewabilityHelper-test.js +0 -359
  197. package/__tests__/VirtualizeUtils-test.js +0 -74
  198. package/__tests__/__snapshots__/FlatList-test.js.snap +0 -251
  199. package/__tests__/__snapshots__/SectionList-test.js.snap +0 -283
  200. package/index.js +0 -5
@@ -0,0 +1,410 @@
1
+ package com.margelo.nitro.reactnativelist
2
+
3
+ import android.graphics.Color
4
+ import android.graphics.Canvas
5
+ import android.os.Looper
6
+ import android.view.View
7
+ import android.view.ViewGroup
8
+ import androidx.recyclerview.widget.DiffUtil
9
+ import androidx.recyclerview.widget.LinearLayoutManager
10
+ import androidx.recyclerview.widget.RecyclerView
11
+ import com.facebook.react.ReactActivity
12
+ import com.facebook.react.bridge.ReactApplicationContext
13
+ import com.facebook.react.interfaces.fabric.ReactSurface
14
+ import com.facebook.react.runtime.ReactSurfaceView
15
+ import com.facebook.react.uimanager.ThemedReactContext
16
+ import com.facebook.react.uimanager.UIManagerHelper
17
+ import com.facebook.react.uimanager.common.UIManagerType
18
+ import com.margelo.nitro.NitroModules
19
+ import java.util.concurrent.CountDownLatch
20
+ import java.util.concurrent.atomic.AtomicReference
21
+
22
+ typealias CreateViewCallbackType = (
23
+ type: String
24
+ ) -> Double
25
+ typealias UpdateViewCallbackType = (
26
+ reactTag: Double,
27
+ item: NativeListItem,
28
+ index: Double
29
+ ) -> Boolean
30
+
31
+ class HybridUiListView(val reactContext: ThemedReactContext) :
32
+ HybridUiListViewSpec(),
33
+ NativeListDataSourceObserver {
34
+ private var createViewCallback: CreateViewCallbackType? = null
35
+ private var updateViewCallback: UpdateViewCallbackType? = null
36
+ private var adapter: NativeListAdapter? = null
37
+ private var dataSource: HybridNativeListDataSource? = null
38
+ private var isRecyclerViewLayoutScheduled = false
39
+ private var itemContentInsets = ItemContentInsets(horizontal = 0, vertical = 0)
40
+ private var rendererSurface: ReactSurface? = null
41
+ private var rendererSurfaceId: Int? = null
42
+ private var isRendererSurfaceStarted = false
43
+ private var isDisposed = false
44
+
45
+ override val view: RecyclerView by lazy {
46
+ ClippedRecyclerView(reactContext).apply {
47
+ layoutParams = ViewGroup.LayoutParams(
48
+ ViewGroup.LayoutParams.MATCH_PARENT,
49
+ ViewGroup.LayoutParams.MATCH_PARENT
50
+ )
51
+ layoutManager = LinearLayoutManager(reactContext)
52
+ setBackgroundColor(Color.TRANSPARENT)
53
+ }
54
+ }
55
+
56
+ override fun setListCallbacks(
57
+ uiListModule: HybridUiListModuleSpec,
58
+ createView: CreateViewCallbackType,
59
+ updateView: UpdateViewCallbackType
60
+ ) {
61
+ isDisposed = false
62
+ createViewCallback = createView
63
+ updateViewCallback = updateView
64
+ runOnMain {
65
+ ensureAdapter()
66
+ }
67
+ }
68
+
69
+ override fun getSurfaceId(): Double {
70
+ val surfaceId = runOnMainSync {
71
+ ensureRendererSurface()
72
+ }
73
+ return surfaceId.toDouble()
74
+ }
75
+
76
+ override fun disposeRendererSurface() {
77
+ runOnMainSync {
78
+ isDisposed = true
79
+ createViewCallback = null
80
+ updateViewCallback = null
81
+ dataSource?.observer = null
82
+ dataSource = null
83
+ adapter?.dataSource = null
84
+ adapter = null
85
+ // setAdapter(null) asks RecyclerView to recycle attached holders immediately.
86
+ // During list teardown the whole native view is going away, so drop the adapter
87
+ // without entering RecyclerView's recycling path.
88
+ view.swapAdapter(null, false)
89
+ view.layoutManager?.removeAllViews()
90
+ isRecyclerViewLayoutScheduled = false
91
+
92
+ val surface = rendererSurface
93
+ rendererSurface = null
94
+ rendererSurfaceId = null
95
+ isRendererSurfaceStarted = false
96
+
97
+ if (surface == null) {
98
+ return@runOnMainSync
99
+ }
100
+
101
+ surface.stop()
102
+ surface.clear()
103
+ surface.detach()
104
+ }
105
+ }
106
+
107
+ override fun onDropView() {
108
+ disposeRendererSurface()
109
+ super.onDropView()
110
+ }
111
+
112
+ override fun setDataSource(dataSource: HybridNativeListDataSourceSpec) {
113
+ val nativeDataSource = dataSource as? HybridNativeListDataSource
114
+ ?: throw IllegalStateException("NativeListDataSource must be created by react-native-list.")
115
+
116
+ runOnMain {
117
+ val existingDataSource = this.dataSource
118
+ if (existingDataSource === nativeDataSource) {
119
+ return@runOnMain
120
+ }
121
+
122
+ this.dataSource?.observer = null
123
+ this.dataSource = nativeDataSource
124
+ nativeDataSource.observer = this
125
+ val nativeAdapter = ensureAdapter()
126
+ nativeAdapter.dataSource = nativeDataSource
127
+ nativeAdapter.retainMeasuredContent(nativeDataSource)
128
+ nativeAdapter.notifyDataSetChanged()
129
+ scheduleRecyclerViewLayout()
130
+ }
131
+ }
132
+
133
+ override fun setLayout(layout: HybridNativeListLayoutSpec) {
134
+ val layoutProvider = layout as? NativeListLayoutProvider
135
+ ?: throw IllegalStateException("NativeListLayout must provide a platform layout.")
136
+
137
+ runOnMain {
138
+ itemContentInsets = layoutProvider.itemContentInsets(reactContext)
139
+ val nativeAdapter = adapter
140
+ if (nativeAdapter != null) {
141
+ nativeAdapter.itemContentInsets = itemContentInsets
142
+ nativeAdapter.notifyDataSetChanged()
143
+ }
144
+ layoutProvider.applyTo(view, reactContext)
145
+ scheduleRecyclerViewLayout()
146
+ }
147
+ }
148
+
149
+ override fun dataSourceDidReload(diffResult: DiffUtil.DiffResult?, animated: Boolean) {
150
+ runOnMain {
151
+ val nativeAdapter = ensureAdapter()
152
+ val nativeDataSource = dataSource
153
+ if (nativeDataSource != null) {
154
+ nativeAdapter.retainMeasuredContent(nativeDataSource)
155
+ }
156
+
157
+ if (!animated || diffResult == null) {
158
+ nativeAdapter.notifyDataSetChanged()
159
+ scheduleRecyclerViewLayout()
160
+ return@runOnMain
161
+ }
162
+
163
+ diffResult.dispatchUpdatesTo(nativeAdapter)
164
+ scheduleRecyclerViewLayout()
165
+ }
166
+ }
167
+
168
+ override fun dataSourceDidInsert(index: Int) {
169
+ runOnMain {
170
+ val nativeAdapter = ensureAdapter()
171
+ nativeAdapter.notifyItemInserted(index)
172
+ scheduleRecyclerViewLayout()
173
+ }
174
+ }
175
+
176
+ override fun dataSourceDidUpdate(index: Int, previousItem: NativeListItem) {
177
+ runOnMain {
178
+ val nativeAdapter = ensureAdapter()
179
+ nativeAdapter.notifyItemChanged(index)
180
+ scheduleRecyclerViewLayout()
181
+ }
182
+ }
183
+
184
+ override fun dataSourceDidRemove(index: Int, removedItem: NativeListItem) {
185
+ runOnMain {
186
+ val nativeAdapter = ensureAdapter()
187
+ nativeAdapter.notifyItemRemoved(index)
188
+ scheduleRecyclerViewLayout()
189
+ }
190
+ }
191
+
192
+ override fun dataSourceDidMove(fromIndex: Int, toIndex: Int) {
193
+ runOnMain {
194
+ val nativeAdapter = ensureAdapter()
195
+ nativeAdapter.notifyItemMoved(fromIndex, toIndex)
196
+ scheduleRecyclerViewLayout()
197
+ }
198
+ }
199
+
200
+ private fun ensureAdapter(): NativeListAdapter {
201
+ if (isDisposed) {
202
+ throw IllegalStateException("Cannot create adapter after list was disposed.")
203
+ }
204
+
205
+ val existingAdapter = adapter
206
+ if (existingAdapter != null) {
207
+ return existingAdapter
208
+ }
209
+
210
+ val nativeAdapter = NativeListAdapter(
211
+ reactContext = reactContext,
212
+ createView = { type ->
213
+ createNativeView(type)
214
+ },
215
+ updateView = { reactTag, item, index ->
216
+ val capturedCallback = updateViewCallback
217
+ ?: throw IllegalStateException("UpdateView callback is not set.")
218
+ capturedCallback(reactTag, item, index)
219
+ }
220
+ )
221
+ nativeAdapter.itemContentInsets = itemContentInsets
222
+ nativeAdapter.dataSource = dataSource
223
+ adapter = nativeAdapter
224
+ attachAdapterIfRendererReady()
225
+ return nativeAdapter
226
+ }
227
+
228
+ private fun attachAdapterIfRendererReady() {
229
+ val nativeAdapter = adapter
230
+ ?: return
231
+
232
+ val surfaceId = rendererSurfaceId
233
+ if (surfaceId != null && !isRendererSurfaceStarted) {
234
+ return
235
+ }
236
+
237
+ val currentAdapter = view.adapter
238
+ if (currentAdapter === nativeAdapter) {
239
+ return
240
+ }
241
+
242
+ view.adapter = nativeAdapter
243
+ }
244
+
245
+ private fun ensureRendererSurface(): Int {
246
+ val existingSurfaceId = rendererSurfaceId
247
+ if (existingSurfaceId != null) {
248
+ return existingSurfaceId
249
+ }
250
+
251
+ val context: ReactApplicationContext = NitroModules.applicationContext
252
+ ?: throw IllegalStateException("ReactApplicationContext is null! Is Nitro installed?")
253
+ val reactActivity = context.currentActivity as? ReactActivity
254
+ ?: throw IllegalStateException("Current activity is not a ReactActivity!")
255
+ val reactHost = reactActivity.reactActivityDelegate.reactHost
256
+ ?: throw IllegalStateException("ReactNativeHost is null!")
257
+
258
+ val surface = reactHost.createSurface(reactContext, "", null)
259
+ val surfaceView = surface.view as? ReactSurfaceView
260
+ ?: throw IllegalStateException("Surface view is not a ReactSurfaceView!")
261
+ val surfaceId = surfaceView.getRootViewTag()
262
+
263
+ rendererSurface = surface
264
+ rendererSurfaceId = surfaceId
265
+ isRendererSurfaceStarted = false
266
+ isDisposed = false
267
+
268
+ val startTask = surface.start()
269
+ val waitThread = Thread {
270
+ try {
271
+ startTask.waitForCompletion()
272
+ view.post {
273
+ val startError = startTask.getError()
274
+ if (startError != null) {
275
+ throw startError
276
+ }
277
+
278
+ if (isDisposed) {
279
+ return@post
280
+ }
281
+
282
+ val currentSurfaceId = rendererSurfaceId
283
+ if (currentSurfaceId != surfaceId) {
284
+ return@post
285
+ }
286
+
287
+ isRendererSurfaceStarted = true
288
+ attachAdapterIfRendererReady()
289
+ scheduleRecyclerViewLayout()
290
+ }
291
+ } catch (throwable: Throwable) {
292
+ view.post {
293
+ throw throwable
294
+ }
295
+ }
296
+ }
297
+ waitThread.name = "UiListSurfaceStart-$surfaceId"
298
+ waitThread.start()
299
+
300
+ return surfaceId
301
+ }
302
+
303
+ private fun createNativeView(type: String): View {
304
+ if (isDisposed) {
305
+ throw IllegalStateException("Cannot create view after list was disposed.")
306
+ }
307
+
308
+ val capturedCallback = createViewCallback
309
+ ?: throw IllegalStateException("CreateView callback is not set.")
310
+
311
+ val viewTag = capturedCallback(type).toInt()
312
+ val fabricUiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC)
313
+ ?: throw IllegalStateException("Fabric UIManager is null. Is Fabric enabled?")
314
+
315
+ val resolvedView = fabricUiManager.resolveView(viewTag)
316
+ ?: throw IllegalStateException("Could not resolve view with tag $viewTag.")
317
+
318
+ val parent = resolvedView.parent as? ViewGroup
319
+ ?: throw IllegalStateException("View with tag $viewTag has no parent.")
320
+ val childIndex = parent.indexOfChild(resolvedView)
321
+ parent.removeViewAt(childIndex)
322
+
323
+ if (resolvedView.parent != null) {
324
+ throw IllegalStateException("View with tag $viewTag still has a parent after removing.")
325
+ }
326
+
327
+ parent.addView(View(reactContext), childIndex)
328
+
329
+ return resolvedView
330
+ }
331
+
332
+ private fun scheduleRecyclerViewLayout() {
333
+ val surfaceId = rendererSurfaceId
334
+ if (surfaceId != null && !isRendererSurfaceStarted) {
335
+ return
336
+ }
337
+
338
+ if (isRecyclerViewLayoutScheduled) {
339
+ return
340
+ }
341
+
342
+ isRecyclerViewLayoutScheduled = true
343
+ view.post {
344
+ isRecyclerViewLayoutScheduled = false
345
+ performRecyclerViewLayoutIfReady()
346
+ }
347
+ }
348
+
349
+ private fun performRecyclerViewLayoutIfReady() {
350
+ val viewWidth = view.width
351
+ val viewHeight = view.height
352
+ if (!view.isAttachedToWindow || viewWidth <= 0 || viewHeight <= 0) {
353
+ return
354
+ }
355
+
356
+ // React Native has already assigned bounds, but RecyclerView requestLayout()
357
+ // is not always traversed from this Fabric-hosted view. Drive RecyclerView's
358
+ // pending adapter updates with the current RN layout.
359
+ val widthSpec = View.MeasureSpec.makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY)
360
+ val heightSpec = View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY)
361
+ view.measure(widthSpec, heightSpec)
362
+ view.layout(view.left, view.top, view.right, view.bottom)
363
+ }
364
+
365
+ private fun runOnMain(block: () -> Unit) {
366
+ val isMainThread = Looper.myLooper() == Looper.getMainLooper()
367
+ if (isMainThread) {
368
+ block()
369
+ } else {
370
+ view.post(block)
371
+ }
372
+ }
373
+
374
+ private fun <T> runOnMainSync(block: () -> T): T {
375
+ val isMainThread = Looper.myLooper() == Looper.getMainLooper()
376
+ if (isMainThread) {
377
+ return block()
378
+ }
379
+
380
+ val result = AtomicReference<Result<T>>()
381
+ val latch = CountDownLatch(1)
382
+ view.post {
383
+ try {
384
+ val value = block()
385
+ result.set(Result.success(value))
386
+ } catch (throwable: Throwable) {
387
+ result.set(Result.failure(throwable))
388
+ } finally {
389
+ latch.countDown()
390
+ }
391
+ }
392
+ latch.await()
393
+ val capturedResult = result.get()
394
+ ?: throw IllegalStateException("UI thread did not return a result.")
395
+ return capturedResult.getOrThrow()
396
+ }
397
+
398
+ private class ClippedRecyclerView(context: ThemedReactContext) : RecyclerView(context) {
399
+ override fun dispatchDraw(canvas: Canvas) {
400
+ // RN/Fabric parents may allow child drawing outside their bounds.
401
+ // Keep clipToPadding=false for list insets, but clip rows to the list viewport.
402
+ // Without this item could overflow the list bounds.
403
+ val saveCount = canvas.save()
404
+ canvas.clipRect(0, 0, width, height)
405
+ super.dispatchDraw(canvas)
406
+ canvas.restoreToCount(saveCount)
407
+ }
408
+ }
409
+
410
+ }
@@ -0,0 +1,9 @@
1
+ package com.margelo.nitro.reactnativelist
2
+
3
+ import android.view.View
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+
6
+ class HybridViewHolder(context: ThemedReactContext) : HybridViewHolderSpec() {
7
+ override val view: View
8
+ get() = TODO("Hm, would this here be the user's view?")
9
+ }
@@ -0,0 +1,217 @@
1
+ package com.margelo.nitro.reactnativelist
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+ import android.widget.FrameLayout
6
+ import androidx.recyclerview.widget.RecyclerView
7
+ import com.facebook.react.uimanager.ThemedReactContext
8
+ import kotlin.math.roundToInt
9
+
10
+ internal class NativeListAdapter(
11
+ private val reactContext: ThemedReactContext,
12
+ private val createView: (type: String) -> View,
13
+ private val updateView: (reactTag: Double, item: NativeListItem, index: Double) -> Boolean
14
+ ) : RecyclerView.Adapter<NativeListAdapter.ViewHolder>() {
15
+
16
+ var dataSource: HybridNativeListDataSource? = null
17
+ private val viewTypeByItemType = mutableMapOf<String, Int>()
18
+ private val itemTypeByViewType = mutableMapOf<Int, String>()
19
+ private val measuredContentSizeByItemKey = mutableMapOf<String, PixelSize>()
20
+ private var nextViewType = 1
21
+ var itemContentInsets = ItemContentInsets(horizontal = 0, vertical = 0)
22
+
23
+ class ViewHolder(val container: FrameLayout) : RecyclerView.ViewHolder(container) {
24
+ var hostedView: View? = null
25
+ var itemType: String? = null
26
+ var reactTag: Int? = null
27
+ }
28
+
29
+ private data class PixelSize(
30
+ val width: Int?,
31
+ val height: Int?
32
+ )
33
+
34
+ private data class ResolvedPixelSize(
35
+ val width: Int,
36
+ val height: Int
37
+ )
38
+
39
+ override fun getItemViewType(position: Int): Int {
40
+ val item = requireDataSource().getItemAt(position)
41
+ val existingViewType = viewTypeByItemType[item.type]
42
+ if (existingViewType != null) {
43
+ return existingViewType
44
+ }
45
+
46
+ val viewType = nextViewType
47
+ nextViewType += 1
48
+ viewTypeByItemType[item.type] = viewType
49
+ itemTypeByViewType[viewType] = item.type
50
+ return viewType
51
+ }
52
+
53
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
54
+ val container = FrameLayout(parent.context)
55
+ container.layoutParams = RecyclerView.LayoutParams(
56
+ RecyclerView.LayoutParams.WRAP_CONTENT,
57
+ RecyclerView.LayoutParams.WRAP_CONTENT
58
+ )
59
+ val itemType = itemTypeByViewType[viewType]
60
+ ?: throw IllegalStateException("Missing item type for viewType $viewType.")
61
+ val child = createView(itemType)
62
+ val existingParent = child.parent as? ViewGroup
63
+ existingParent?.removeView(child)
64
+
65
+ container.addView(child)
66
+ val holder = ViewHolder(container)
67
+ holder.hostedView = child
68
+ holder.itemType = itemType
69
+ holder.reactTag = child.id
70
+ return holder
71
+ }
72
+
73
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
74
+ val item = requireDataSource().getItemAt(position)
75
+ val child = requireHostedView(holder, item)
76
+ prepareChildLayoutForMeasurement(child, item)
77
+ val reactTag = holder.reactTag
78
+ if (reactTag != null) {
79
+ val reactTagDouble = reactTag.toDouble()
80
+ val positionDouble = position.toDouble()
81
+ updateView(reactTagDouble, item, positionDouble)
82
+ }
83
+
84
+ captureMeasuredContentSize(item.key, child)
85
+ val contentSize = resolvedContentSize(item)
86
+ bindContainerLayout(holder.container, contentSize)
87
+ bindChildLayout(child, contentSize)
88
+ }
89
+
90
+ override fun getItemCount(): Int {
91
+ return dataSource?.getCountAsInt() ?: 0
92
+ }
93
+
94
+ fun retainMeasuredContent(dataSource: HybridNativeListDataSource) {
95
+ val activeKeys = mutableSetOf<String>()
96
+ val itemCount = dataSource.getCountAsInt()
97
+ for (index in 0 until itemCount) {
98
+ val item = dataSource.getItemAt(index)
99
+ activeKeys.add(item.key)
100
+ }
101
+
102
+ val measuredSizeIterator = measuredContentSizeByItemKey.keys.iterator()
103
+ while (measuredSizeIterator.hasNext()) {
104
+ val itemKey = measuredSizeIterator.next()
105
+ if (activeKeys.contains(itemKey)) {
106
+ continue
107
+ }
108
+ measuredSizeIterator.remove()
109
+ }
110
+ }
111
+
112
+ private fun requireDataSource(): HybridNativeListDataSource {
113
+ return dataSource ?: throw IllegalStateException("NativeListDataSource is not set.")
114
+ }
115
+
116
+ private fun requireHostedView(holder: ViewHolder, item: NativeListItem): View {
117
+ if (holder.itemType != item.type) {
118
+ throw IllegalStateException(
119
+ "RecyclerView supplied holder for type '${holder.itemType}' " +
120
+ "to item type '${item.type}'."
121
+ )
122
+ }
123
+ return holder.hostedView ?: throw IllegalStateException("ViewHolder has no hosted view.")
124
+ }
125
+
126
+ private fun captureMeasuredContentSize(itemKey: String, view: View) {
127
+ val existingSize = measuredContentSizeByItemKey[itemKey]
128
+ val measuredWidth = positiveDimension(view.measuredWidth)
129
+ val measuredHeight = positiveDimension(view.measuredHeight)
130
+ val viewWidth = positiveDimension(view.width)
131
+ val viewHeight = positiveDimension(view.height)
132
+ val layoutWidth = positiveDimension(view.layoutParams?.width)
133
+ val layoutHeight = positiveDimension(view.layoutParams?.height)
134
+ val width = measuredWidth ?: viewWidth ?: layoutWidth
135
+ val height = measuredHeight ?: viewHeight ?: layoutHeight
136
+
137
+ val nextWidth = existingSize?.width ?: width
138
+ val nextHeight = existingSize?.height ?: height
139
+ if (nextWidth == null && nextHeight == null) {
140
+ return
141
+ }
142
+
143
+ measuredContentSizeByItemKey[itemKey] = PixelSize(
144
+ width = nextWidth,
145
+ height = nextHeight
146
+ )
147
+ }
148
+
149
+ private fun resolvedContentSize(item: NativeListItem): ResolvedPixelSize {
150
+ val measuredSize = measuredContentSizeByItemKey[item.key]
151
+ val width = item.width?.let { toPixels(it) } ?: measuredSize?.width
152
+ val height = item.height?.let { toPixels(it) } ?: measuredSize?.height
153
+
154
+ if (width == null) {
155
+ throw IllegalStateException(
156
+ "Missing width for item type '${item.type}'. " +
157
+ "Provide width from getItemSize or render a measurable shell."
158
+ )
159
+ }
160
+ if (height == null) {
161
+ throw IllegalStateException(
162
+ "Missing height for item type '${item.type}'. " +
163
+ "Provide height from getItemSize or render a measurable shell."
164
+ )
165
+ }
166
+
167
+ return ResolvedPixelSize(width, height)
168
+ }
169
+
170
+ private fun bindContainerLayout(container: FrameLayout, contentSize: ResolvedPixelSize) {
171
+ val horizontalInset = itemContentInsets.horizontal
172
+ val verticalInset = itemContentInsets.vertical
173
+ val containerWidth = contentSize.width + horizontalInset * 2
174
+ val containerHeight = contentSize.height + verticalInset * 2
175
+ val layoutParams = container.layoutParams as? RecyclerView.LayoutParams
176
+ ?: RecyclerView.LayoutParams(containerWidth, containerHeight)
177
+ layoutParams.width = containerWidth
178
+ layoutParams.height = containerHeight
179
+ container.layoutParams = layoutParams
180
+ container.setPadding(
181
+ horizontalInset,
182
+ verticalInset,
183
+ horizontalInset,
184
+ verticalInset
185
+ )
186
+ }
187
+
188
+ private fun bindChildLayout(child: View, contentSize: ResolvedPixelSize) {
189
+ val layoutParams = FrameLayout.LayoutParams(contentSize.width, contentSize.height)
190
+ child.layoutParams = layoutParams
191
+ }
192
+
193
+ private fun prepareChildLayoutForMeasurement(child: View, item: NativeListItem) {
194
+ if (item.width != null && item.height != null) {
195
+ return
196
+ }
197
+
198
+ val width = item.width?.let { toPixels(it) } ?: ViewGroup.LayoutParams.WRAP_CONTENT
199
+ val height = item.height?.let { toPixels(it) } ?: ViewGroup.LayoutParams.WRAP_CONTENT
200
+ val layoutParams = FrameLayout.LayoutParams(width, height)
201
+ child.layoutParams = layoutParams
202
+ }
203
+
204
+ private fun positiveDimension(value: Int?): Int? {
205
+ if (value == null || value <= 0) {
206
+ return null
207
+ }
208
+ return value
209
+ }
210
+
211
+ private fun toPixels(value: Double): Int {
212
+ val density = reactContext.resources.displayMetrics.density
213
+ val pixels = value * density
214
+ val rounded = pixels.roundToInt()
215
+ return rounded.coerceAtLeast(1)
216
+ }
217
+ }