diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3c0a0a62fd..aff3d3ff12 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,13 +1,16 @@ [//]: # (This file will be used for the release notes on GitHub when publishing.) [//]: # (Types of changes: `Breaking changes` `New` `Added` `Improved` `Changed` `Deprecated` `Removed` `Fixed`) [//]: # (Example:) -[//]: # (## Added) +[//]: # (## New) [//]: # ( - New payment method) [//]: # (## Changed) [//]: # ( - DropIn service's package changed from `com.adyen.dropin` to `com.adyen.dropin.services`) [//]: # (## Deprecated) [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) +## New +- For external redirects launched in a Custom Tab, you can now [customize the toolbar and navigation bar colors](docs/UI_CUSTOMIZATION.md#styling-custom-tabs) of the Custom Tab. + ## Fixed - Fixed various memory leaks. - Drop-in no longer overrides the log level in case of debug builds. diff --git a/docs/UI_CUSTOMIZATION.md b/docs/UI_CUSTOMIZATION.md index 18e620d8d9..3b35896062 100644 --- a/docs/UI_CUSTOMIZATION.md +++ b/docs/UI_CUSTOMIZATION.md @@ -10,6 +10,7 @@ The base theme can be extended and overridden. You can also use a custom base th - [Customizing a specific view](#customizing-a-specific-view) - [Adding dark mode support](#adding-dark-mode-support) - [Overriding string resources](#overriding-string-resources) +- [Styling Custom Tabs](#styling-custom-tabs) ## Customizing the base theme @@ -125,4 +126,22 @@ CheckoutConfiguration(shopperLocale, environment, clientKey) { } ``` -If you cannot find a certain string in the code base, then check whether it is coming from the Checkout API. Make sure you localize these strings by sending the correct [shopperLocale](https://docs.adyen.com/api-explorer/Checkout/latest/post/sessions#request-shopperLocale). +If you cannot find a certain string in the code base, then check whether it is coming from the Checkout API. Make sure you localize these strings by sending the correct [shopperLocale](https://docs.adyen.com/api-explorer/Checkout/latest/post/sessions#request-shopperLocale). + +## Styling Custom Tabs + +We use Custom Tabs to launch any external redirects that cannot be handled inside the SDK or by another installed native app. +By default we set the toolbar color to match the `colorPrimary` attribute defined in your theme. +To change this color to a different value than your `colorPrimary` attribute, you can override the `AdyenCheckout.CustomTabs` style in your `styles.xml`: + +```xml + +``` diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/DefaultRedirectHandler.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/DefaultRedirectHandler.kt index 8adeaafddb..ed18556010 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/DefaultRedirectHandler.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/DefaultRedirectHandler.kt @@ -14,13 +14,11 @@ import android.net.Uri import android.os.Build import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo -import androidx.browser.customtabs.CustomTabColorSchemeParams -import androidx.browser.customtabs.CustomTabsIntent import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.core.internal.util.adyenLog -import com.adyen.checkout.ui.core.internal.util.ThemeUtil +import com.adyen.checkout.ui.core.internal.util.CustomTabsLauncher import org.json.JSONException import org.json.JSONObject import java.lang.ref.WeakReference @@ -153,25 +151,15 @@ class DefaultRedirectHandler : RedirectHandler { private fun launchWithCustomTabs(context: Context, uri: Uri): Boolean { // open in custom tabs if there's no native app for the target uri - val defaultColors = CustomTabColorSchemeParams.Builder() - .setToolbarColor(ThemeUtil.getPrimaryThemeColor(context)) - .build() - - @Suppress("SwallowedException") - return try { - CustomTabsIntent.Builder() - .setShowTitle(true) - .setDefaultColorSchemeParams(defaultColors) - .build() - .launchUrl(context, uri) + val isLaunched = CustomTabsLauncher.launchCustomTab(context, uri) + if (isLaunched) { adyenLog(AdyenLogLevel.DEBUG) { "launchWithCustomTabs - redirect successful with custom tabs" } - true - } catch (e: ActivityNotFoundException) { + } else { adyenLog(AdyenLogLevel.DEBUG) { "launchWithCustomTabs - device doesn't support custom tabs or chrome is disabled" } - false } + return isLaunched } /** diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/CustomTabsLauncher.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/CustomTabsLauncher.kt new file mode 100644 index 0000000000..6ebe7ed2b5 --- /dev/null +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/CustomTabsLauncher.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by josephj on 6/5/2024. + */ + +package com.adyen.checkout.ui.core.internal.util + +import android.content.ActivityNotFoundException +import android.content.Context +import android.net.Uri +import androidx.annotation.AttrRes +import androidx.annotation.RestrictTo +import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsIntent +import com.adyen.checkout.ui.core.R + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +object CustomTabsLauncher { + fun launchCustomTab(context: Context, uri: Uri): Boolean { + @Suppress("SwallowedException") + return try { + CustomTabsIntent.Builder() + .setShowTitle(true) + .setDefaultColorSchemeParams(getDefaultColorSchemeParams(context)) + .build() + .launchUrl(context, uri) + true + } catch (e: ActivityNotFoundException) { + false + } + } + + private fun getDefaultColorSchemeParams(context: Context): CustomTabColorSchemeParams { + val toolbarColor = context.getColorOrNull(R.attr.adyenCustomTabsToolbarColor) + val secondaryToolbarColor = context.getColorOrNull(R.attr.adyenCustomTabsSecondaryToolbarColor) + val navigationBarColor = context.getColorOrNull(R.attr.adyenCustomTabsNavigationBarColor) + val navigationBarDividerColor = context.getColorOrNull(R.attr.adyenCustomTabsNavigationBarDividerColor) + + return CustomTabColorSchemeParams.Builder().apply { + toolbarColor?.let { setToolbarColor(it) } + secondaryToolbarColor?.let { setSecondaryToolbarColor(it) } + navigationBarColor?.let { setNavigationBarColor(it) } + navigationBarDividerColor?.let { setNavigationBarDividerColor(it) } + }.build() + } + + private fun Context.getColorOrNull(@AttrRes attribute: Int): Int? { + val typedArray = obtainStyledAttributes(R.style.AdyenCheckout_CustomTabs, intArrayOf(attribute)) + val color = typedArray.getColor(0, -1).takeIf { it != -1 } + typedArray.recycle() + return color + } +} diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt index 9d7705fa28..091531e8d9 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt @@ -14,8 +14,6 @@ import android.content.Intent import android.net.Uri import android.os.Build import androidx.annotation.RestrictTo -import androidx.browser.customtabs.CustomTabColorSchemeParams -import androidx.browser.customtabs.CustomTabsIntent import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog @@ -63,23 +61,13 @@ class PdfOpener { private fun openInCustomTab(context: Context, uri: Uri): Boolean { // open in custom tabs if there's no native app for the target uri - val defaultColors = CustomTabColorSchemeParams.Builder() - .setToolbarColor(ThemeUtil.getPrimaryThemeColor(context)) - .build() - - return try { - CustomTabsIntent.Builder() - .setShowTitle(true) - .setDefaultColorSchemeParams(defaultColors) - .build() - .launchUrl(context, uri) - + val isLaunched = CustomTabsLauncher.launchCustomTab(context, uri) + if (isLaunched) { adyenLog(AdyenLogLevel.DEBUG) { "Successfully opened pdf in custom tab" } - true - } catch (e: ActivityNotFoundException) { - adyenLog(AdyenLogLevel.DEBUG, e) { "Couldn't open pdf in custom tab" } - false + } else { + adyenLog(AdyenLogLevel.DEBUG) { "Couldn't open pdf in custom tab" } } + return isLaunched } private fun openInBrowser(context: Context, uri: Uri): Boolean { diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ThemeUtil.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ThemeUtil.kt deleted file mode 100644 index 37cb8de9bc..0000000000 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ThemeUtil.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by caiof on 2/4/2019. - */ -package com.adyen.checkout.ui.core.internal.util - -import android.content.Context -import android.util.TypedValue -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -object ThemeUtil { - - @ColorInt - fun getPrimaryThemeColor(context: Context): Int { - return getAttributeColor(context, androidx.appcompat.R.attr.colorPrimary) - } - - @ColorInt - private fun getAttributeColor(context: Context, @AttrRes attributeColor: Int): Int { - val typedValue = TypedValue() - val typedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(attributeColor)) - val color = typedArray.getColor(0, 0) - typedArray.recycle() - return color - } -} diff --git a/ui-core/src/main/res/values/attrs.xml b/ui-core/src/main/res/values/attrs.xml index 09d378992e..aa2c582726 100644 --- a/ui-core/src/main/res/values/attrs.xml +++ b/ui-core/src/main/res/values/attrs.xml @@ -1,5 +1,4 @@ - - + diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/FullVoucherView.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/FullVoucherView.kt index a0789baa25..5999f0a762 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/FullVoucherView.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/FullVoucherView.kt @@ -8,15 +8,12 @@ package com.adyen.checkout.voucher.internal.ui.view -import android.content.ActivityNotFoundException import android.content.Context import android.net.Uri import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import androidx.annotation.StringRes -import androidx.browser.customtabs.CustomTabColorSchemeParams -import androidx.browser.customtabs.CustomTabsIntent import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible @@ -31,7 +28,7 @@ import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.LogoSize import com.adyen.checkout.ui.core.internal.ui.loadLogo -import com.adyen.checkout.ui.core.internal.util.ThemeUtil +import com.adyen.checkout.ui.core.internal.util.CustomTabsLauncher import com.adyen.checkout.ui.core.internal.util.formatFullStringWithHyperLink import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle import com.adyen.checkout.voucher.R @@ -206,20 +203,11 @@ internal class FullVoucherView @JvmOverloads constructor( } private fun onReadInstructionsClicked(url: String) { - val defaultColors = CustomTabColorSchemeParams.Builder() - .setToolbarColor(ThemeUtil.getPrimaryThemeColor(context)) - .build() - - try { - CustomTabsIntent.Builder() - .setShowTitle(true) - .setDefaultColorSchemeParams(defaultColors) - .build() - .launchUrl(context, Uri.parse(url)) - + val isLaunched = CustomTabsLauncher.launchCustomTab(context, Uri.parse(url)) + if (isLaunched) { adyenLog(AdyenLogLevel.DEBUG) { "Successfully opened instructions in custom tab" } - } catch (e: ActivityNotFoundException) { - adyenLog(AdyenLogLevel.DEBUG, e) { "Couldn't open instructions in custom tab" } + } else { + adyenLog(AdyenLogLevel.ERROR) { "Couldn't open instructions in custom tab" } } }