-
Notifications
You must be signed in to change notification settings - Fork 4
MOBILE-342: Публичное API встроенных блоков #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Vailence
merged 15 commits into
mission/stories
from
feature/MOBILE-342-PublicAPI-EmbeddedBlocks
Sep 7, 2026
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
5d07427
MOBILE-342: Add the embedded block as a Fabric component over the nat…
8239103
MOBILE-342: Follow mission/stories — a live height and a place name t…
f37c934
MOBILE-342: Answer the review — a nameless place collapses, a sizeles…
7919541
MOBILE-342: Say the same about a zero, a negative and a not-a-number …
71f7745
MOBILE-342: Leave the pinned version alone and keep the notes to the …
28c6905
MOBILE-342: Draw the embedded block in the old renderer too
0366ec6
MOBILE-342: Let the pinned toolchain typecheck the block and run its …
765b8f9
MOBILE-342: Tell React Native this pod carries a Fabric component
b3ce2ec
MOBILE-342: Stop warning about space the SDK trims off itself
4426dd2
MOBILE-342: Keep the lint clean, and say why the one style stays inline
e77339c
MOBILE-342: Let the package build where the checkout is not alone
ba96dc2
MOBILE-342: Let a host app point the wrapper at a local native SDK
4ac198d
MOBILE-342: Take the native SDK version knob back out
1e5af76
MOBILE-342: Say which React Native the package needs
9cb91a8
MOBILE-342: Warn from an effect, not from the render body
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
android/src/main/java/com/mindboxsdk/embedded/MindboxEmbeddedBlockEvents.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.mindboxsdk.embedded | ||
|
|
||
| import com.facebook.react.bridge.Arguments | ||
| import com.facebook.react.bridge.WritableMap | ||
| import com.facebook.react.uimanager.events.Event | ||
|
|
||
| internal class AppearanceChangeEvent( | ||
| surfaceId: Int, | ||
| viewTag: Int, | ||
| private val appearance: String, | ||
| ) : Event<AppearanceChangeEvent>(surfaceId, viewTag) { | ||
| override fun getEventName(): String = EVENT_NAME | ||
|
|
||
| override fun getEventData(): WritableMap = Arguments.createMap().apply { | ||
| putString("appearance", appearance) | ||
| } | ||
|
|
||
| companion object { | ||
| const val EVENT_NAME = "topAppearanceChange" | ||
| } | ||
| } | ||
|
|
||
| internal class BlockLoadEvent(surfaceId: Int, viewTag: Int) : Event<BlockLoadEvent>(surfaceId, viewTag) { | ||
| override fun getEventName(): String = EVENT_NAME | ||
|
|
||
| override fun getEventData(): WritableMap = Arguments.createMap() | ||
|
|
||
| companion object { | ||
| const val EVENT_NAME = "topBlockLoad" | ||
| } | ||
| } | ||
|
|
||
| internal class BlockFailEvent(surfaceId: Int, viewTag: Int) : Event<BlockFailEvent>(surfaceId, viewTag) { | ||
| override fun getEventName(): String = EVENT_NAME | ||
|
|
||
| override fun getEventData(): WritableMap = Arguments.createMap() | ||
|
|
||
| companion object { | ||
| const val EVENT_NAME = "topBlockFail" | ||
| } | ||
| } |
217 changes: 217 additions & 0 deletions
217
android/src/main/java/com/mindboxsdk/embedded/MindboxEmbeddedBlockHostView.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| package com.mindboxsdk.embedded | ||
|
|
||
| import android.content.Context | ||
| import android.graphics.Color | ||
| import android.view.View | ||
| import android.widget.FrameLayout | ||
| import androidx.lifecycle.Lifecycle | ||
| import androidx.lifecycle.LifecycleOwner | ||
| import androidx.lifecycle.LifecycleRegistry | ||
| import androidx.lifecycle.setViewTreeLifecycleOwner | ||
| import cloud.mindbox.mobile_sdk.Mindbox | ||
| import cloud.mindbox.mobile_sdk.annotations.InternalMindboxApi | ||
| import cloud.mindbox.mobile_sdk.embedded.MindboxEmbeddedBlockAppearance | ||
| import cloud.mindbox.mobile_sdk.embedded.MindboxEmbeddedBlockListener | ||
| import cloud.mindbox.mobile_sdk.embedded.MindboxEmbeddedBlockView | ||
| import cloud.mindbox.mobile_sdk.logger.Level | ||
|
|
||
| @OptIn(InternalMindboxApi::class) | ||
| internal class MindboxEmbeddedBlockHostView(context: Context) : FrameLayout(context) { | ||
| var onAppearance: ((String) -> Unit)? = null | ||
|
|
||
| var onOutcome: ((String) -> Unit)? = null | ||
|
|
||
| private var blockView: MindboxEmbeddedBlockView? = null | ||
| private var placeSystemName: String? = null | ||
| private var timeoutMs: Long? = null | ||
| private var hostVisible: Boolean = true | ||
| private var hasPlaceholder: Boolean = false | ||
| private var hasErrorView: Boolean = false | ||
|
|
||
| private var placeholderStandIn: View? = null | ||
| private var errorStandIn: View? = null | ||
|
|
||
| private val hostLifecycleOwner = object : LifecycleOwner { | ||
| val registry: LifecycleRegistry = LifecycleRegistry(this) | ||
|
|
||
| override val lifecycle: Lifecycle | ||
| get() = registry | ||
| } | ||
|
|
||
| init { | ||
| hostLifecycleOwner.registry.currentState = Lifecycle.State.RESUMED | ||
| setViewTreeLifecycleOwner(hostLifecycleOwner) | ||
| } | ||
|
|
||
| private val measureAndLayout = Runnable { | ||
| isLayoutScheduled = false | ||
| forceLayout() | ||
| measure( | ||
| MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), | ||
| MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), | ||
| ) | ||
| layout(left, top, right, bottom) | ||
| } | ||
|
|
||
| private var isLayoutScheduled = false | ||
|
|
||
| private var isBlockWanted = false | ||
|
|
||
| override fun requestLayout() { | ||
| super.requestLayout() | ||
|
|
||
| if (isLayoutScheduled || width == 0 || height == 0) { | ||
| return | ||
| } | ||
|
|
||
| isLayoutScheduled = true | ||
| post(measureAndLayout) | ||
| } | ||
|
|
||
| fun setPlaceSystemName(name: String?) { | ||
| if (name == placeSystemName) { | ||
| return | ||
| } | ||
|
|
||
| placeSystemName = name | ||
| dropBlock() | ||
| } | ||
|
|
||
| fun setTimeoutMs(timeoutMs: Double) { | ||
| if (blockView == null) { | ||
| this.timeoutMs = timeoutMs.takeIf { it != 0.0 }?.toLong() | ||
| } | ||
| } | ||
|
|
||
| fun setHostVisible(isHostVisible: Boolean) { | ||
| if (hostVisible == isHostVisible) { | ||
| return | ||
| } | ||
|
|
||
| hostVisible = isHostVisible | ||
| blockView?.setHostVisible(isHostVisible) | ||
| } | ||
|
|
||
| fun setHasPlaceholder(hasPlaceholder: Boolean) { | ||
| if (this.hasPlaceholder == hasPlaceholder) { | ||
| return | ||
| } | ||
|
|
||
| this.hasPlaceholder = hasPlaceholder | ||
| syncStandIns() | ||
| } | ||
|
|
||
| fun setHasErrorView(hasErrorView: Boolean) { | ||
| if (this.hasErrorView == hasErrorView) { | ||
| return | ||
| } | ||
|
|
||
| this.hasErrorView = hasErrorView | ||
| syncStandIns() | ||
| } | ||
|
|
||
| fun commitProps() { | ||
| isBlockWanted = true | ||
| buildBlockIfPossible() | ||
| } | ||
|
|
||
| override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { | ||
| super.onSizeChanged(width, height, oldWidth, oldHeight) | ||
| buildBlockIfPossible() | ||
| } | ||
|
|
||
| private fun buildBlockIfPossible() { | ||
| val place = placeSystemName ?: return | ||
| if (blockView != null || !isBlockWanted || width == 0 || height == 0) { | ||
| return | ||
| } | ||
|
|
||
| if (place.isEmpty()) { | ||
| Mindbox.writeLog( | ||
| message = "[EmbeddedBlock] A React Native block was created without a place system name and has nothing to resolve", | ||
| logLevel = Level.ERROR, | ||
| ) | ||
| } | ||
|
|
||
| val block = MindboxEmbeddedBlockView(context, place, timeoutMs) | ||
| blockView = block | ||
|
|
||
| syncStandIns() | ||
| block.setHostVisible(hostVisible) | ||
| block.setListener( | ||
| object : MindboxEmbeddedBlockListener { | ||
| override fun onLoad(view: MindboxEmbeddedBlockView) { | ||
| onOutcome?.invoke(OUTCOME_LOAD) | ||
| } | ||
|
|
||
| override fun onFail(view: MindboxEmbeddedBlockView) { | ||
| onOutcome?.invoke(OUTCOME_FAIL) | ||
| } | ||
| }, | ||
| ) | ||
| block.setAppearanceObserver { appearance -> onAppearance?.invoke(nameOf(appearance)) } | ||
|
|
||
| addView(block, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) | ||
| } | ||
|
|
||
| fun release() { | ||
| dropBlock() | ||
| onAppearance = null | ||
| onOutcome = null | ||
| hostLifecycleOwner.registry.currentState = Lifecycle.State.DESTROYED | ||
| } | ||
|
|
||
| private fun dropBlock() { | ||
| val block = blockView ?: return | ||
|
|
||
| blockView = null | ||
| placeholderStandIn = null | ||
| errorStandIn = null | ||
| block.setAppearanceObserver(null) | ||
| block.setListener(null) | ||
| block.release() | ||
| removeView(block) | ||
| } | ||
|
|
||
| private fun syncStandIns() { | ||
| val block = blockView ?: return | ||
|
|
||
| if (hasPlaceholder) { | ||
| if (placeholderStandIn == null) { | ||
| placeholderStandIn = makeStandIn() | ||
| block.setPlaceholderView(placeholderStandIn) | ||
| } | ||
| } else if (placeholderStandIn != null) { | ||
| placeholderStandIn = null | ||
| block.setPlaceholderView(null) | ||
| } | ||
|
|
||
| if (hasErrorView) { | ||
| if (errorStandIn == null) { | ||
| errorStandIn = makeStandIn() | ||
| block.setErrorView(errorStandIn) | ||
| } | ||
| } else if (errorStandIn != null) { | ||
| errorStandIn = null | ||
| block.setErrorView(null) | ||
| } | ||
| } | ||
|
|
||
| private fun makeStandIn(): View = View(context).apply { | ||
| setBackgroundColor(Color.TRANSPARENT) | ||
| isClickable = false | ||
| isFocusable = false | ||
| } | ||
|
|
||
| private companion object { | ||
| const val OUTCOME_LOAD = "load" | ||
| const val OUTCOME_FAIL = "fail" | ||
|
|
||
| fun nameOf(appearance: MindboxEmbeddedBlockAppearance): String = when (appearance) { | ||
| MindboxEmbeddedBlockAppearance.PLACEHOLDER -> "placeholder" | ||
| MindboxEmbeddedBlockAppearance.CONTENT -> "content" | ||
| MindboxEmbeddedBlockAppearance.ERROR -> "error" | ||
| MindboxEmbeddedBlockAppearance.COLLAPSED -> "collapsed" | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.