Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cloud.mindbox.mobile_sdk.inapp.presentation

import android.app.Activity
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.annotation.VisibleForTesting
import cloud.mindbox.mobile_sdk.addUnique
import cloud.mindbox.mobile_sdk.di.mindboxInject
Expand Down Expand Up @@ -34,6 +36,8 @@ internal interface MindboxView {
val container: ViewGroup

fun requestPermission()

fun registerBack(onBack: OnBackPressedCallback)
}

internal class InAppMessageViewDisplayerImpl(
Expand Down Expand Up @@ -232,16 +236,7 @@ internal class InAppMessageViewDisplayerImpl(
}

currentActivity?.root?.let { root ->
currentHolder?.show(object : MindboxView {
override val container: ViewGroup
get() = root

override fun requestPermission() {
currentActivity?.let { activity ->
mindboxNotificationManager.requestPermission(activity = activity)
}
}
})
currentHolder?.show(createMindboxView(root))
} ?: run {
mindboxLogE("failed to show inApp: currentRoot is null")
}
Expand Down Expand Up @@ -270,6 +265,11 @@ internal class InAppMessageViewDisplayerImpl(
mindboxNotificationManager.requestPermission(activity = activity)
}
}

override fun registerBack(onBack: OnBackPressedCallback) {
val backOwner = currentActivity as? OnBackPressedDispatcherOwner
backOwner?.onBackPressedDispatcher?.addCallback(onBack)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public enum class WebViewAction {
@SerializedName("hide")
HIDE,

@SerializedName("back")
BACK,

@SerializedName("log")
LOG,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloud.mindbox.mobile_sdk.inapp.presentation.view
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import cloud.mindbox.mobile_sdk.BuildConfig
import cloud.mindbox.mobile_sdk.Mindbox
Expand All @@ -22,17 +23,13 @@ import cloud.mindbox.mobile_sdk.logger.mindboxLogE
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.managers.DbManager
import cloud.mindbox.mobile_sdk.managers.GatewayManager
import cloud.mindbox.mobile_sdk.models.Configuration
import cloud.mindbox.mobile_sdk.models.getShortUserAgent
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.safeAs
import cloud.mindbox.mobile_sdk.utils.Constants
import cloud.mindbox.mobile_sdk.utils.MindboxUtils.Stopwatch
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.VolleyError
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import com.google.gson.Gson
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
Expand Down Expand Up @@ -61,11 +58,13 @@ internal class WebViewInAppViewHolder(

private var closeInappTimer: Timer? = null
private var webViewController: WebViewController? = null
private var backPressedCallback: OnBackPressedCallback? = null
private val pendingResponsesById: MutableMap<String, CompletableDeferred<BridgeMessage.Response>> =
ConcurrentHashMap()

private val gson: Gson by mindboxInject { this.gson }
private val messageValidator: BridgeMessageValidator by lazy { BridgeMessageValidator() }
private val gatewayManager: GatewayManager by mindboxInject { gatewayManager }

override val isActive: Boolean
get() = isInAppMessageActive
Expand Down Expand Up @@ -143,11 +142,10 @@ internal class WebViewInAppViewHolder(
}

private fun handleInitAction(controller: WebViewController): String {
mindboxLogI("WebView initialization completed " + Stopwatch.stop(TIMER))
closeInappTimer?.cancel()
closeInappTimer = null
stopTimer()
wrapper.inAppActionCallbacks.onInAppShown.onShown()
controller.setVisibility(true)
backPressedCallback?.isEnabled = true
return BridgeMessage.EMPTY_PAYLOAD
}

Expand Down Expand Up @@ -235,6 +233,23 @@ internal class WebViewInAppViewHolder(
return controller
}

private fun clearBackPressedCallback() {
backPressedCallback?.remove()
backPressedCallback = null
}

private fun sendBackAction(controller: WebViewController) {
val message: BridgeMessage.Request = BridgeMessage.createAction(
WebViewAction.BACK,
BridgeMessage.EMPTY_PAYLOAD
)
sendActionInternal(controller, message) { error ->
mindboxLogW("Failed to send back action to WebView: $error")
inAppCallback.onInAppDismissed(wrapper.inAppType.inAppId)
hide()
}
}

internal fun checkEvaluateJavaScript(response: String?): Boolean {
return when (response) {
JS_RETURN -> true
Expand Down Expand Up @@ -329,35 +344,38 @@ internal class WebViewInAppViewHolder(
return@setJsBridge
}

when (message) {
is BridgeMessage.Request -> handleRequest(message, controller, handlers)
is BridgeMessage.Response -> handleResponse(message)
is BridgeMessage.Error -> handleError(message)
else -> mindboxLogW("Unknown message type: $message")
controller.executeOnViewThread {
when (message) {
is BridgeMessage.Request -> handleRequest(message, controller, handlers)
is BridgeMessage.Response -> handleResponse(message)
is BridgeMessage.Error -> handleError(message)
else -> mindboxLogW("Unknown message type: $message")
}
}
})

controller.setUserAgentSuffix(configuration.getShortUserAgent())

val requestQueue: RequestQueue = Volley.newRequestQueue(currentDialog.context)
val stringRequest = StringRequest(
Request.Method.GET,
layer.contentUrl,
{ response: String ->
onContentLoaded(
layer.contentUrl?.let { contentUrl ->
runCatching {
gatewayManager.fetchWebViewContent(contentUrl)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeysozinov обрати внимание, тут переписал. Вынес запрос в gatewayManager

}.onSuccess { response: String ->
onContentPageLoaded(
controller = controller,
content = WebViewHtmlContent(
baseUrl = layer.baseUrl ?: "",
html = response
)
)
},
{ error: VolleyError ->
mindboxLogE("Failed to fetch HTML content for In-App: $error. Destroying.")
}.onFailure { e ->
mindboxLogE("Failed to fetch HTML content for In-App: $e")
hide()
release()
}
)
requestQueue.add(stringRequest)
} ?: run {
mindboxLogE("WebView content URL is null")
hide()
}
}
}

Expand All @@ -370,27 +388,33 @@ internal class WebViewInAppViewHolder(
} ?: release()
}

private fun onContentLoaded(controller: WebViewController, content: WebViewHtmlContent) {
private fun onContentPageLoaded(controller: WebViewController, content: WebViewHtmlContent) {
controller.executeOnViewThread {
controller.loadContent(content)
startTimer(controller)
}
startTimer {
controller.executeOnViewThread {
mindboxLogE("WebView initialization timed out after ${Stopwatch.stop(TIMER)}.")
hide()
release()
}
}
}

private fun startTimer(controller: WebViewController) {
private fun stopTimer() {
closeInappTimer?.let { timer ->
mindboxLogI("WebView initialization completed " + Stopwatch.stop(TIMER))
timer.cancel()
}
closeInappTimer = null
}

private fun startTimer(onTimeOut: () -> Unit) {
Stopwatch.start(TIMER)
closeInappTimer = timer(
initialDelay = INIT_TIMEOUT_MS,
period = INIT_TIMEOUT_MS,
action = {
controller.executeOnViewThread {
if (closeInappTimer != null) {
mindboxLogE("WebView initialization timed out after ${Stopwatch.stop(TIMER)}.")
hide()
release()
}
}
}
action = { onTimeOut() }
)
}

Expand All @@ -399,17 +423,27 @@ internal class WebViewInAppViewHolder(
mindboxLogI("Try to show in-app with id ${wrapper.inAppType.inAppId}")
wrapper.inAppType.layers.forEach { layer ->
when (layer) {
is Layer.WebViewLayer -> {
renderLayer(layer)
}

else -> {
mindboxLogD("Layer is not supported")
}
is Layer.WebViewLayer -> renderLayer(layer)
else -> mindboxLogW("Layer is not supported")
}
}
mindboxLogI("Show In-App ${wrapper.inAppType.inAppId} in holder ${this.hashCode()}")
inAppLayout.requestFocus()
webViewController?.let { controller ->
currentRoot.registerBack(registerBackPressedCallback(controller))
}
}

private fun registerBackPressedCallback(controller: WebViewController): OnBackPressedCallback {
val isBackCallbackEnabled = backPressedCallback?.isEnabled ?: false
clearBackPressedCallback()
val callback = object : OnBackPressedCallback(isBackCallbackEnabled) {
override fun handleOnBackPressed() {
sendBackAction(controller)
}
}
backPressedCallback = callback
return callback
}

override fun reattach(currentRoot: MindboxView) {
Expand All @@ -421,15 +455,18 @@ internal class WebViewInAppViewHolder(
}
}
inAppLayout.requestFocus()
webViewController?.let { controller ->
currentRoot.registerBack(registerBackPressedCallback(controller))
}
}

override fun canReuseOnRestore(inAppId: String): Boolean = wrapper.inAppType.inAppId == inAppId

override fun hide() {
// Clean up timeout when hiding
closeInappTimer?.cancel()
closeInappTimer = null
stopTimer()
cancelPendingResponses("WebView In-App is hidden")
clearBackPressedCallback()
webViewController?.let { controller ->
val view: WebViewPlatformView = controller.view
inAppLayout.removeView(view)
Expand All @@ -440,9 +477,9 @@ internal class WebViewInAppViewHolder(
override fun release() {
super.release()
// Clean up WebView resources
closeInappTimer?.cancel()
closeInappTimer = null
stopTimer()
cancelPendingResponses("WebView In-App is released")
clearBackPressedCallback()
webViewController?.destroy()
webViewController = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cloud.mindbox.mobile_sdk.fromJsonTyped
import cloud.mindbox.mobile_sdk.inapp.data.dto.GeoTargetingDto
import cloud.mindbox.mobile_sdk.inapp.domain.models.*
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogE
import cloud.mindbox.mobile_sdk.models.*
import cloud.mindbox.mobile_sdk.models.operation.OperationResponseBaseInternal
import cloud.mindbox.mobile_sdk.models.operation.request.LogResponseDto
Expand All @@ -20,6 +21,7 @@ import com.android.volley.DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
import com.android.volley.ParseError
import com.android.volley.Request
import com.android.volley.VolleyError
import com.android.volley.toolbox.StringRequest
import com.google.gson.Gson
import kotlinx.coroutines.*
import org.json.JSONException
Expand Down Expand Up @@ -445,6 +447,25 @@ internal class GatewayManager(private val mindboxServiceGenerator: MindboxServic
}
}

suspend fun fetchWebViewContent(contentUrl: String): String {
return suspendCoroutine { continuation ->
try {
val request: StringRequest = StringRequest(
Request.Method.GET,
contentUrl,
{ response -> continuation.resume(response) },
{ error -> continuation.resumeWithException(error) }
).apply {
setShouldCache(false)
}
mindboxServiceGenerator.addToRequestQueue(request)
} catch (e: Exception) {
mindboxLogE("Failed to fetch WebView content", e)
continuation.resumeWithException(e)
}
}
}

private inline fun <reified T> Continuation<T>.resumeFromJson(json: String) {
loggingRunCatching(null) {
gson.fromJsonTyped<T>(json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cloud.mindbox.mobile_sdk.di.MindboxDI
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.models.MindboxRequest
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.VolleyLog
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -35,6 +36,16 @@ internal class MindboxServiceGenerator(private val requestQueue: RequestQueue) {
}
}

internal fun addToRequestQueue(request: Request<*>) = LoggingExceptionHandler.runCatching {
requestQueue.add(request)
mindboxLogD(
"""
---> Method: ${request.method} ${request.url}
---> End of request
""".trimIndent()
)
}

private fun logMindboxRequest(request: MindboxRequest) {
LoggingExceptionHandler.runCatching {
val builder = StringBuilder()
Expand Down