From 91f81d2c5d1956e46a2c309f1ca1af23b31b30d8 Mon Sep 17 00:00:00 2001 From: Daniel Kift Date: Thu, 25 Jun 2026 11:42:18 +0100 Subject: [PATCH] ensure soft keyboard doesnt obscure pay button --- .../com/shopify/checkoutkit/CheckoutDialog.kt | 40 +++++++++++++---- .../src/main/res/layout/dialog_checkout.xml | 12 ++--- .../shopify/checkoutkit/CheckoutDialogTest.kt | 44 ++++++++++++++++++- 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt index 173445bca..36d21b1d5 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt @@ -7,9 +7,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.graphics.Color import android.os.Build import android.view.MenuItem +import android.view.View import android.view.View.INVISIBLE import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowManager import android.widget.ProgressBar import android.widget.RelativeLayout @@ -21,6 +21,8 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.toDrawable +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.children import com.shopify.checkoutkit.ShopifyCheckoutKit.log @@ -32,6 +34,7 @@ internal class CheckoutDialog( ) : ComponentDialog(context) { private var presentedCheckoutWebView: CheckoutWebView? = null + private var rootPaddingBottom: Int = 0 private val backNavigationCallback = object : OnBackPressedCallback(enabled = true) { override fun handleOnBackPressed() { @@ -47,14 +50,9 @@ internal class CheckoutDialog( fun start(context: ComponentActivity) { log.d(LOG_TAG, "Dialog start called.") setContentView(R.layout.dialog_checkout) - window?.setLayout(MATCH_PARENT, WRAP_CONTENT) window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) - // Although this flag is deprecated in newest targets, it's properly - // addressing the keyboard focus on the WebView within the dialog. - // The non-deprecated alternative (insets listener) does notify about - // keyboard insets when visible, but it is not adjusting the pan - // properly into the fields. To be investigated further. - window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + setupKeyboardInsetHandling() log.d(LOG_TAG, "Finding or creating WebView.") val checkoutWebView = CheckoutWebView.checkoutViewFor(checkoutUrl, context) @@ -69,6 +67,7 @@ internal class CheckoutDialog( val colorScheme = ShopifyCheckoutKit.configuration.colorScheme log.d(LOG_TAG, "Configured colorScheme $colorScheme") + findViewById(R.id.checkoutKitRoot).setBackgroundColor(colorScheme.webViewBackgroundColor()) findViewById(R.id.checkoutKitHeader).apply { log.d(LOG_TAG, "Applying configured header colors and inflating menu.") setBackgroundColor(colorScheme.headerBackgroundColor()) @@ -100,6 +99,29 @@ internal class CheckoutDialog( log.d(LOG_TAG, "Showing dialog.") show() + // Dialog window size is only applied reliably after show(). + window?.setLayout(MATCH_PARENT, MATCH_PARENT) + ViewCompat.requestApplyInsets(findViewById(R.id.checkoutKitRoot)) + } + + private fun setupKeyboardInsetHandling() { + val root = findViewById(R.id.checkoutKitRoot) + rootPaddingBottom = root.paddingBottom + ViewCompat.setOnApplyWindowInsetsListener(root) { _, insets -> + val imeBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom + applyKeyboardInset(imeBottom) + insets + } + } + + internal fun applyKeyboardInset(imeBottom: Int) { + val root = findViewById(R.id.checkoutKitRoot) + root.setPadding( + root.paddingLeft, + root.paddingTop, + root.paddingRight, + rootPaddingBottom + imeBottom.coerceAtLeast(0), + ) } private fun MenuItem.setupCloseButton(colorScheme: ColorScheme) { @@ -140,7 +162,7 @@ internal class CheckoutDialog( findViewById(R.id.checkoutKitContainer).apply { log.d(LOG_TAG, "Found parent view, setting its colors and layout params.") setBackgroundColor(colorScheme.webViewBackgroundColor()) - val layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT) + val layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) layoutParams.addRule(RelativeLayout.BELOW, R.id.progressBar) checkoutWebView.removeFromParent() log.d(LOG_TAG, "Adding WebView to parent view.") diff --git a/platforms/android/lib/src/main/res/layout/dialog_checkout.xml b/platforms/android/lib/src/main/res/layout/dialog_checkout.xml index 8fca29d13..64376b7df 100644 --- a/platforms/android/lib/src/main/res/layout/dialog_checkout.xml +++ b/platforms/android/lib/src/main/res/layout/dialog_checkout.xml @@ -1,8 +1,10 @@ - + android:background="@android:color/transparent" + android:orientation="vertical"> - + diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt index 4eabd7acd..65abcb76a 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/CheckoutDialogTest.kt @@ -4,7 +4,10 @@ import android.app.Dialog import android.graphics.drawable.ColorDrawable import android.os.Looper import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.WindowManager import android.webkit.WebView +import android.widget.LinearLayout import android.widget.RelativeLayout import androidx.activity.ComponentActivity import androidx.appcompat.widget.Toolbar @@ -63,6 +66,24 @@ class CheckoutDialogTest { assertThat(dialog.isShowing).isTrue } + @Test + fun `dialog fills height and handles keyboard with insets`() { + ShopifyCheckoutKit.present("https://shopify.com", activity, processor) + + val dialog = ShadowDialog.getLatestDialog() + val attributes = dialog.window?.attributes + val root = dialog.findViewById(R.id.checkoutKitRoot) + val container = dialog.findViewById(R.id.checkoutKitContainer) + val containerLayoutParams = container.layoutParams as LinearLayout.LayoutParams + + assertThat(root).isInstanceOf(LinearLayout::class.java) + assertThat(attributes?.height).isEqualTo(MATCH_PARENT) + assertThat(attributes?.softInputMode?.and(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)) + .isEqualTo(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + assertThat(containerLayoutParams.height).isZero() + assertThat(containerLayoutParams.weight).isEqualTo(1f) + } + @Test fun `checkoutView is added to the container when dialog is presented`() { ShopifyCheckoutKit.present("https://shopify.com", activity, processor) @@ -83,8 +104,26 @@ class CheckoutDialogTest { val webView: WebView = ShadowDialog.getLatestDialog() .findViewById(R.id.checkoutKitContainer) .children.firstOrNull { it is WebView } as WebView? ?: fail("No WebVew found in dialog") + val layoutParams = webView.layoutParams as RelativeLayout.LayoutParams assertThat(shadowOf(webView).wasOnResumeCalled()).isTrue() + assertThat(layoutParams.width).isEqualTo(MATCH_PARENT) + assertThat(layoutParams.height).isEqualTo(MATCH_PARENT) + } + + @Test + fun `ime inset changes pad checkout content without resizing window`() { + ShopifyCheckoutKit.present("https://shopify.com", activity, processor) + val dialog = ShadowDialog.getLatestDialog() as CheckoutDialog + val root = dialog.findViewById(R.id.checkoutKitRoot) + dialog.window?.setLayout(MATCH_PARENT, 400) + + assertThat(root.paddingBottom).isZero() + + dialog.applyKeyboardInset(100) + + assertThat(dialog.window?.attributes?.height).isEqualTo(400) + assertThat(root.paddingBottom).isEqualTo(100) } @Test @@ -238,17 +277,20 @@ class CheckoutDialogTest { } @Test - fun `sets WebView container background color based on current configuration`() { + fun `sets checkout content background color based on current configuration`() { val customColors = customColors() ShopifyCheckoutKit.configuration.colorScheme = ColorScheme.Web(customColors) ShopifyCheckoutKit.present("https://shopify.com", activity, processor) val dialog = ShadowDialog.getLatestDialog() + val root = dialog.findViewById(R.id.checkoutKitRoot) val webViewContainer = dialog.findViewById(R.id.checkoutKitContainer) + val rootBackgroundColor = backgroundColor(root) val webViewContainerBackgroundColor = backgroundColor(webViewContainer) val configuredColor = customColors.webViewBackground.getValue(activity) + assertThat(rootBackgroundColor).isEqualTo(configuredColor) assertThat(webViewContainerBackgroundColor).isEqualTo(configuredColor) }