В моем приложении для Android, созданном с использованием Jetpack Compose и Kotlin, я использую Android Webview для загрузки URL-адреса проверки Mollie для платежей. URL-адрес (https://www.mollie.com/checkout/select- ... CTOPAYMENT) показывает такой контент
[img]https://i.sstatic. net/pBT07Cuf.jpg[/img]
Изначально все работало, и Android Webview правильно загружал URL-адреса Молли, но теперь пользователи живого приложения сталкиваются с проблемой отображения пустой страницы, поэтому Webvie загружает URL-адрес, но содержимое не отображается.
Ниже приведен экран BookingPaymentScreen, который загружает URL-адрес.
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun BookingPaymentScreen(
navController: NavController,
sharedViewModel: SharedViewModel,
addBookingViewModel: AddBookingViewModel,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val result = navController.previousBackStackEntry?.savedStateHandle?.get("isFromRetry")
val resultUrl = navController.previousBackStackEntry?.savedStateHandle?.get("url")
val errorToastState = remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = { activityResult ->
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
url = addBookingViewModel.bookingFinalUiState.paymentUrl
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
})
BackHandler {
goBackBookingPaymentScreen(addBookingViewModel, navController)
}
// Ensure that retry logic is only executed once and after the Composable is composed
LaunchedEffect(result) {
if (result == true && !addBookingViewModel.retryCallMade) {
addBookingViewModel.onEvent(AddBookingUiEvent.onRetryPayment)
addBookingViewModel.retryCallMade = true
sharedViewModel.logAnalyticsEvent(AnalyticsEvents.mlBookingsRetryPaymentBtnClicked)
}
}
Surface(
modifier = Modifier
.background(
color = PallaAppTheme.colors.primary
)
.fillMaxHeight()
.statusBarsPadding()
) {
if (errorToastState.value) {
Toast.makeText(context, "App not installed or setup", Toast.LENGTH_LONG).show()
errorToastState.value = false
}
if (addBookingViewModel.checkoutLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(AppVariables.AppPading)
.background(PallaAppTheme.AppBackground), contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = PallaAppTheme.colors.secondary,
strokeWidth = 2.dp
)
}
} else {
AndroidView(modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.navigationBarsPadding(),
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = object : WebViewClient() {
override fun onPageCommitVisible(view: WebView?, url: String?) {
super.onPageCommitVisible(view, url)
}
override fun onPageStarted(
view: WebView?, url: String?, favicon: Bitmap?
) {
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
}
override fun shouldOverrideUrlLoading(
view: WebView?, request: WebResourceRequest?
): Boolean {
Log.d("shouldOverrideUrlLoading", request?.url.toString())
return handleOverrideUrlLoading(webView = view,
url = request?.url.toString(),
addBookingViewModel,
navController,
context,
launcher,
onBankingAppNotFound = {
errorToastState.value = true
})
}
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(
view: WebView?, url: String?
): Boolean {
Log.d("shouldOverrideUrlLoading", "$url")
return handleOverrideUrlLoading(webView = view,
url = url,
addBookingViewModel,
navController,
context,
launcher,
onBankingAppNotFound = {
errorToastState.value = true
})
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
Log.d("onReceivedError", error.toString())
val error1 = error
val abc = "fddg"
super.onReceivedError(view, request, error)
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
Log.d("onReceivedSslError", error.toString())
// ignore ssl error
if (handler != null) {
handler.proceed()
} else {
super.onReceivedSslError(view, null, error)
}
}
}
// WebChromeClient for JavaScript console logs
webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d("WebView Console", "${consoleMessage.message()} -- from line " +
"${consoleMessage.lineNumber()} of ${consoleMessage.sourceId()}")
return super.onConsoleMessage(consoleMessage)
}
}
var url = addBookingViewModel.bookingFinalUiState.paymentUrl
if (url.isEmpty()) {
url = resultUrl ?: ""
}
loadUrl(url)
}
},
update = { webView ->
// Update the WebView if needed
//CookieManager.getInstance()?.setAcceptThirdPartyCookies(webView, true)
})
}
}
}
fun goBackBookingPaymentScreen(
addBookingViewModel: AddBookingViewModel, navController: NavController
) {
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
}
fun isDeepLink(url: String?): Boolean {
if ((url?.startsWith("nl") == true && url.contains("payloadUri"))) {
return true
}
if (url?.startsWith("http") == false && !url.startsWith("https") && url.contains("://")) {
return true
}
return false
}
//OLD FUNCTION WITH DEEP LINK
private fun handleOverrideUrlLoading(
webView: WebView?,
url: String?,
addBookingViewModel: AddBookingViewModel,
navController: NavController,
context: Context,
launcher: ManagedActivityResultLauncher,
onBankingAppNotFound: () -> Unit
): Boolean {
if (url != null && url.startsWith(AppDeepLinks.COURT_BOOKING)) {
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
url = addBookingViewModel.bookingFinalUiState.paymentUrl
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
return true
}
if (isIntentLink(url)) {
handleBankingAppIntent(url ?: "", context, launcher, onBankingAppNotFound = {
onBankingAppNotFound()
})
return true
}
if (isDeepLink(url)) {
try {
val intent = Intent(ACTION_VIEW, Uri.parse(url))
//context.startActivity(intent)
launcher.launch(intent)
} catch (e: ActivityNotFoundException) {
// Only browser apps are available, or a browser is the default.
// So you can open the URL directly in your app, for example in a
e.printStackTrace()
onBankingAppNotFound()
}
return true
}
url?.let {
webView?.loadUrl(it)
return true
}
return true
}
fun isIntentLink(url: String?): Boolean {
if ((url?.startsWith("intent://") == true)) {
return true
}
return false
}
/*// In some cases Mollie creates wrong Android Chrome Intent (lower cased)
// https://developer.chrome.com/multidevice/android/intents
private fun String.fixedIntentName(): String = replace("#intent;", "#Intent;")*/
private fun handleBankingAppIntent(
url: String, context: Context,
launcher: ManagedActivityResultLauncher,
onBankingAppNotFound: () -> Unit
) {
try {
val intent = Intent.parseUri(
url, Intent.URI_INTENT_SCHEME
)
try {
// context.startActivity(intent)
launcher.launch(intent)
} catch (e: ActivityNotFoundException) {
// Only browser apps are available, or a browser is the default.
// So you can open the URL directly in your app, for example in a
e.printStackTrace()
onBankingAppNotFound()
}
} catch (e: URISyntaxException) {
e.printStackTrace()
onBankingAppNotFound()
}
}
Я пробовал настройки веб-просмотра, такие как начальный масштаб, масштабирование, прокрутка, статическая высота/ширина и различные строки пользовательского агента, но, похоже, ничего не работает. Те же платежные URL-адреса Mollie работают и в приложении для iOS.
В моем приложении для Android, созданном с использованием Jetpack Compose и Kotlin, я использую Android Webview для загрузки URL-адреса проверки Mollie для платежей. URL-адрес (https://www.mollie.com/checkout/select-method/SOMECODESPECIFICTOPAYMENT) показывает такой контент [img]https://i.sstatic. net/pBT07Cuf.jpg[/img]
Изначально все работало, и Android Webview правильно загружал URL-адреса Молли, но теперь пользователи живого приложения сталкиваются с проблемой отображения пустой страницы, поэтому Webvie загружает URL-адрес, но содержимое не отображается. Ниже приведен экран BookingPaymentScreen, который загружает URL-адрес. [code]@RequiresApi(Build.VERSION_CODES.O) @SuppressLint("SetJavaScriptEnabled") @Composable fun BookingPaymentScreen( navController: NavController, sharedViewModel: SharedViewModel, addBookingViewModel: AddBookingViewModel, ) { val context = LocalContext.current val scope = rememberCoroutineScope() val result = navController.previousBackStackEntry?.savedStateHandle?.get("isFromRetry") val resultUrl = navController.previousBackStackEntry?.savedStateHandle?.get("url") val errorToastState = remember { mutableStateOf(false) } val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult(), onResult = { activityResult -> addBookingViewModel.retryCallMade = false val bookingDetail = BookingDetail( bookingId = addBookingViewModel.bookingFinalUiState.bookingId, ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken, url = addBookingViewModel.bookingFinalUiState.paymentUrl ) navController.currentBackStackEntry?.savedStateHandle?.set( key = "bookingDetail", value = bookingDetail ) navController.navigate(BOOKING_DETAIL_ROUTE) })
BackHandler { goBackBookingPaymentScreen(addBookingViewModel, navController) } // Ensure that retry logic is only executed once and after the Composable is composed LaunchedEffect(result) { if (result == true && !addBookingViewModel.retryCallMade) { addBookingViewModel.onEvent(AddBookingUiEvent.onRetryPayment) addBookingViewModel.retryCallMade = true sharedViewModel.logAnalyticsEvent(AnalyticsEvents.mlBookingsRetryPaymentBtnClicked) } } Surface( modifier = Modifier .background( color = PallaAppTheme.colors.primary ) .fillMaxHeight() .statusBarsPadding() ) { if (errorToastState.value) { Toast.makeText(context, "App not installed or setup", Toast.LENGTH_LONG).show() errorToastState.value = false }
fun isDeepLink(url: String?): Boolean { if ((url?.startsWith("nl") == true && url.contains("payloadUri"))) { return true } if (url?.startsWith("http") == false && !url.startsWith("https") && url.contains("://")) { return true } return false }
//OLD FUNCTION WITH DEEP LINK
private fun handleOverrideUrlLoading( webView: WebView?, url: String?, addBookingViewModel: AddBookingViewModel, navController: NavController, context: Context, launcher: ManagedActivityResultLauncher, onBankingAppNotFound: () -> Unit ): Boolean { if (url != null && url.startsWith(AppDeepLinks.COURT_BOOKING)) { addBookingViewModel.retryCallMade = false val bookingDetail = BookingDetail( bookingId = addBookingViewModel.bookingFinalUiState.bookingId, ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken, url = addBookingViewModel.bookingFinalUiState.paymentUrl ) navController.currentBackStackEntry?.savedStateHandle?.set( key = "bookingDetail", value = bookingDetail ) navController.navigate(BOOKING_DETAIL_ROUTE) return true } if (isIntentLink(url)) { handleBankingAppIntent(url ?: "", context, launcher, onBankingAppNotFound = { onBankingAppNotFound() }) return true } if (isDeepLink(url)) { try { val intent = Intent(ACTION_VIEW, Uri.parse(url)) //context.startActivity(intent) launcher.launch(intent) } catch (e: ActivityNotFoundException) { // Only browser apps are available, or a browser is the default. // So you can open the URL directly in your app, for example in a e.printStackTrace() onBankingAppNotFound() } return true } url?.let { webView?.loadUrl(it) return true } return true }
fun isIntentLink(url: String?): Boolean { if ((url?.startsWith("intent://") == true)) { return true } return false }
/*// In some cases Mollie creates wrong Android Chrome Intent (lower cased) // https://developer.chrome.com/multidevice/android/intents private fun String.fixedIntentName(): String = replace("#intent;", "#Intent;")*/ private fun handleBankingAppIntent( url: String, context: Context, launcher: ManagedActivityResultLauncher, onBankingAppNotFound: () -> Unit ) { try { val intent = Intent.parseUri( url, Intent.URI_INTENT_SCHEME ) try { // context.startActivity(intent) launcher.launch(intent) } catch (e: ActivityNotFoundException) { // Only browser apps are available, or a browser is the default. // So you can open the URL directly in your app, for example in a e.printStackTrace() onBankingAppNotFound() } } catch (e: URISyntaxException) { e.printStackTrace() onBankingAppNotFound() } } [/code] Я пробовал настройки веб-просмотра, такие как начальный масштаб, масштабирование, прокрутка, статическая высота/ширина и различные строки пользовательского агента, но, похоже, ничего не работает. Те же платежные URL-адреса Mollie работают и в приложении для iOS. [code]settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW CookieManager.getInstance().setAcceptThirdPartyCookies(this, true) settings.userAgentString="Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36" [/code] Любая помощь в этом будет оценена по достоинству.