@@ -15,25 +15,10 @@ import cloud.mindbox.mobile_sdk.embedded.MindboxEmbeddedBlockListener
1515import cloud.mindbox.mobile_sdk.embedded.MindboxEmbeddedBlockView
1616import cloud.mindbox.mobile_sdk.logger.Level
1717
18- /* *
19- * The React Native side of one embedded block: a frame that holds the SDK's own container and turns
20- * its two signals into RN events.
21- *
22- * The block itself is the SDK's `MindboxEmbeddedBlockView`, whole and unchanged — the content
23- * factory, the waiting budget, the page and its bridge stay on the native side, and RN gets a view to
24- * place plus the signals to react to.
25- *
26- * Why a frame around it rather than the block itself: the SDK block takes its place system name in
27- * the constructor, and RN creates a view before it has any props. So the block is built once the
28- * props of the first transaction are all in (see [commitProps]) and lives inside this frame.
29- */
3018@OptIn(InternalMindboxApi ::class )
3119internal class MindboxEmbeddedBlockHostView (context : Context ) : FrameLayout(context) {
32-
33- /* * Native → RN: where the block stands now, as one of the wire words. */
3420 var onAppearance: ((String ) -> Unit )? = null
3521
36- /* * Native → RN: how the load ended — `load` or `fail`. */
3722 var onOutcome: ((String ) -> Unit )? = null
3823
3924 private var blockView: MindboxEmbeddedBlockView ? = null
@@ -43,22 +28,9 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
4328 private var hasPlaceholder: Boolean = false
4429 private var hasErrorView: Boolean = false
4530
46- /* *
47- * The stand-ins currently handed to the container, kept to tell "the host still draws its own
48- * screen" from "it has just started to".
49- */
5031 private var placeholderStandIn: View ? = null
5132 private var errorStandIn: View ? = null
5233
53- /* *
54- * The lifecycle the block reads as its host screen's.
55- *
56- * The container gives up for good when the lifecycle owner above it is destroyed — the right rule
57- * for a native screen, and the wrong one here. React Native keeps this view across screens while
58- * `react-native-screens` destroys the fragment of a screen that gets covered: the block would hear
59- * its host die, free its page, and come back to a screen it can no longer load anything for. So the
60- * block is told about the lifetime that actually matters — this view's own.
61- */
6234 private val hostLifecycleOwner = object : LifecycleOwner {
6335 val registry: LifecycleRegistry = LifecycleRegistry (this )
6436
@@ -71,21 +43,8 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
7143 setViewTreeLifecycleOwner(hostLifecycleOwner)
7244 }
7345
74- /* *
75- * Lays the block out again after React Native has stopped listening.
76- *
77- * Yoga owns layout in RN, so the view groups on the way up answer `requestLayout()` with nothing.
78- * The container swaps its own children as the block resolves — the shimmer for the page, the page
79- * for the failure — and every child added after the last layout pass would stay at zero size: the
80- * page loads, reports its content, and nobody ever sees it. So the frame measures and lays itself
81- * out on the next turn of the looper, with the bounds RN gave it.
82- */
8346 private val measureAndLayout = Runnable {
8447 isLayoutScheduled = false
85- // `forceLayout` and not just `measure`: the container swaps a child while RN is already inside
86- // its own layout pass, and that pass clears the flag `requestLayout` had set — a `measure` with
87- // unchanged specs would then return without measuring anything, and the new child would keep
88- // its zero size for good.
8948 forceLayout()
9049 measure(
9150 MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
@@ -96,7 +55,6 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
9655
9756 private var isLayoutScheduled = false
9857
99- /* * Whether RN has asked for a block at all — the props of the first transaction have landed. */
10058 private var isBlockWanted = false
10159
10260 override fun requestLayout () {
@@ -111,29 +69,14 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
11169 }
11270
11371 fun setPlaceSystemName (name : String? ) {
114- // Taken exactly as given: the SDK compares names without trimming on every platform, so a
115- // padded name is a name like any other — one that simply never resolves. Filtering it here
116- // would turn that into a block that never settles, which is worse than one that collapses.
11772 if (name == placeSystemName) {
11873 return
11974 }
12075
121- // A different place is a different block, and the old one has nothing to hand over. The
122- // wrapper keys the whole component by the place, so this is a safety net and not the usual
123- // path — but a place changed under a live block must not leave the old one running.
12476 placeSystemName = name
12577 dropBlock()
12678 }
12779
128- /* *
129- * The waiting budget the block is built with, in milliseconds as they came over the wire.
130- *
131- * Zero is the wire word for "the host said nothing" and turns back into the null the SDK
132- * constructor reads as its own default; anything else — a negative included — is handed over as it
133- * is, for the container to sanitize and log. A value that arrives after the block is built goes
134- * nowhere: a running wait cannot be re-budgeted, the JS wrapper both freezes the value and warns,
135- * and this is only the native end of that same rule.
136- */
13780 fun setTimeoutMs (timeoutMs : Double ) {
13881 if (blockView == null ) {
13982 this .timeoutMs = timeoutMs.takeIf { it != 0.0 }?.toLong()
@@ -167,46 +110,23 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
167110 syncStandIns()
168111 }
169112
170- /* *
171- * The props of a transaction are all in — the block can be built as soon as there is a frame to
172- * build it in.
173- *
174- * Called from here and nowhere else: `createViewInstance` has no props yet, and a single prop
175- * setter would build a block on the place system name while the stand-in flags were still the
176- * defaults.
177- */
178113 fun commitProps () {
179114 isBlockWanted = true
180115 buildBlockIfPossible()
181116 }
182117
183- /* *
184- * The frame has bounds now, which is what the block was waiting for.
185- *
186- * A block built before them starts its page in a view of zero size: the page lays itself out
187- * against a zero-width viewport, reports content that occupies nothing, and a later resize does
188- * not make it lay out again — the block reports `onLoad` for a feed nobody can see. RN gives a view
189- * its bounds after the props, so the block waits for them.
190- */
191118 override fun onSizeChanged (width : Int , height : Int , oldWidth : Int , oldHeight : Int ) {
192119 super .onSizeChanged(width, height, oldWidth, oldHeight)
193120 buildBlockIfPossible()
194121 }
195122
196- /* *
197- * The container hands out its appearance the moment the observer subscribes — and a place with
198- * nothing behind it settles right there — so subscribing happens before the block ever reaches the
199- * window.
200- */
201123 private fun buildBlockIfPossible () {
202124 val place = placeSystemName ? : return
203125 if (blockView != null || ! isBlockWanted || width == 0 || height == 0 ) {
204126 return
205127 }
206128
207129 if (place.isEmpty()) {
208- // Built all the same: the SDK settles a nameless place as empty, so the host hears
209- // `collapsed` and `onFail` instead of watching a placeholder that never resolves.
210130 Mindbox .writeLog(
211131 message = " [EmbeddedBlock] A React Native block was created without a place system name and has nothing to resolve" ,
212132 logLevel = Level .ERROR ,
@@ -231,21 +151,13 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
231151 )
232152 block.setAppearanceObserver { appearance -> onAppearance?.invoke(nameOf(appearance)) }
233153
234- // Last: attaching to the window is what starts the content, and by now everything that has an
235- // opinion about it has been said.
236154 addView(block, LayoutParams (LayoutParams .MATCH_PARENT , LayoutParams .MATCH_PARENT ))
237155 }
238156
239- /* *
240- * The RN view is gone, so the block's screen is gone with it. Waiting for the host Activity to be
241- * destroyed instead would keep a page loading for a screen nobody can see.
242- */
243157 fun release () {
244158 dropBlock()
245159 onAppearance = null
246160 onOutcome = null
247- // Now the host screen really is gone, and a block that outlived this call — one the platform
248- // still holds a reference to — has to hear it.
249161 hostLifecycleOwner.registry.currentState = Lifecycle .State .DESTROYED
250162 }
251163
@@ -261,16 +173,6 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
261173 removeView(block)
262174 }
263175
264- /* *
265- * Puts an empty view where the host draws its own screen — the same arrangement the Compose and
266- * Flutter wrappers use for a slot they cannot hand over directly.
267- *
268- * RN children are real Android views, but the ones above this block belong to Fabric: it mounts
269- * them, and Yoga lays them out. Handing them to the container would take them out of both. So the
270- * container is not given the screen: it is given the fact that the place is taken. That is all it
271- * needs — its own placeholder is held back, and a failed block keeps its height instead of
272- * collapsing. What is actually drawn there is an RN overlay above this frame.
273- */
274176 private fun syncStandIns () {
275177 val block = blockView ? : return
276178
@@ -297,21 +199,14 @@ internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(cont
297199
298200 private fun makeStandIn (): View = View (context).apply {
299201 setBackgroundColor(Color .TRANSPARENT )
300- // The stand-in is a placeholder for space, not for touches: what the host drew over it is an
301- // RN view, and it is RN that has to hear the taps on it.
302202 isClickable = false
303203 isFocusable = false
304204 }
305205
306206 private companion object {
307-
308207 const val OUTCOME_LOAD = " load"
309208 const val OUTCOME_FAIL = " fail"
310209
311- /* *
312- * Spelled out rather than taken from the enum name: the wire word is a contract with the JS
313- * side, and renaming a case in the SDK must not quietly change it.
314- */
315210 fun nameOf (appearance : MindboxEmbeddedBlockAppearance ): String = when (appearance) {
316211 MindboxEmbeddedBlockAppearance .PLACEHOLDER -> " placeholder"
317212 MindboxEmbeddedBlockAppearance .CONTENT -> " content"
0 commit comments