Skip to content
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

Add compose compatibility #1237

Merged
merged 11 commits into from Jun 30, 2023
52 changes: 52 additions & 0 deletions components-compose/build.gradle
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by oscars on 1/6/2023.
*/

plugins {
id 'com.android.library'
id 'kotlin-android'
}

// Maven artifact
ext.mavenArtifactId = "components-compose"
ext.mavenArtifactName = "Adyen checkout components compose"
ext.mavenArtifactDescription = "Compose compat Adyen checkout components."

apply from: "${rootDir}/config/gradle/sharedTasks.gradle"

android {
namespace 'com.adyen.checkout.components.compose'
compileSdkVersion compile_sdk_version

defaultConfig {
minSdkVersion min_sdk_version
targetSdkVersion target_sdk_version
versionCode version_code
versionName version_name

testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
consumerProguardFiles "consumer-rules.pro"
}

buildFeatures {
compose true
}

composeOptions {
kotlinCompilerExtensionVersion = compose_compiler_version
}
}

dependencies {
// Checkout
api project(':components-core')
api project(':sessions-core')
api project(':ui-core')

implementation platform(libraries.compose.bom)
implementation libraries.compose.viewmodel
}
Empty file.
10 changes: 10 additions & 0 deletions components-compose/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2023 Adyen N.V.
~
~ This file is open source and available under the MIT license. See the LICENSE file for more info.
~
~ Created by oscars on 1/6/2023.
-->

<manifest />
@@ -0,0 +1,222 @@
/*
* Copyright (c) 2023 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 17/5/2023.
*/

package com.adyen.checkout.components.compose

import android.app.Application
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import com.adyen.checkout.components.core.ComponentCallback
import com.adyen.checkout.components.core.Order
import com.adyen.checkout.components.core.PaymentComponentState
import com.adyen.checkout.components.core.PaymentMethod
import com.adyen.checkout.components.core.StoredPaymentMethod
import com.adyen.checkout.components.core.internal.Component
import com.adyen.checkout.components.core.internal.Configuration
import com.adyen.checkout.components.core.internal.PaymentComponent
import com.adyen.checkout.components.core.internal.provider.PaymentComponentProvider
import com.adyen.checkout.components.core.internal.provider.StoredPaymentComponentProvider
import com.adyen.checkout.core.exception.ComponentException
import com.adyen.checkout.sessions.core.CheckoutSession
import com.adyen.checkout.sessions.core.SessionComponentCallback
import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider
import com.adyen.checkout.sessions.core.internal.provider.SessionStoredPaymentComponentProvider
import com.adyen.checkout.ui.core.AdyenComponentView
import com.adyen.checkout.ui.core.internal.ui.ViewableComponent

/**
* Get a [PaymentComponent] from a [Composable].
*
* @param paymentMethod The corresponding [PaymentMethod] object.
* @param configuration The Configuration of the component.
* @param componentCallback The callback to handle events from the [PaymentComponent].
* @param order An [Order] in case of an ongoing partial payment flow.
* @param key The key to use to identify the [PaymentComponent].
*
* NOTE: By default only one [PaymentComponent] will be created per lifecycle. Use [key] in case you need to
* instantiate multiple [PaymentComponent]s in the same lifecycle.
*
* @return The Component
*/
@Composable
fun <
ComponentT : PaymentComponent,
ConfigurationT : Configuration,
ComponentStateT : PaymentComponentState<*>,
ComponentCallbackT : ComponentCallback<ComponentStateT>
> PaymentComponentProvider<ComponentT, ConfigurationT, ComponentStateT, ComponentCallbackT>.get(
paymentMethod: PaymentMethod,
configuration: ConfigurationT,
componentCallback: ComponentCallbackT,
key: String?,
order: Order? = null,
): ComponentT {
return get(
savedStateRegistryOwner = LocalSavedStateRegistryOwner.current,
viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw ComponentException("Cannot find current LocalViewModelStoreOwner"),
lifecycleOwner = LocalLifecycleOwner.current,
paymentMethod = paymentMethod,
configuration = configuration,
application = LocalContext.current.applicationContext as Application,
componentCallback = componentCallback,
order = order,
key = key,
)
}

/**
* Get a [PaymentComponent] with a stored payment method from a [Composable].
*
* @param storedPaymentMethod The corresponding [StoredPaymentMethod] object.
* @param configuration The Configuration of the component.
* @param componentCallback The callback to handle events from the [PaymentComponent].
* @param order An [Order] in case of an ongoing partial payment flow.
* @param key The key to use to identify the [PaymentComponent].
*
* NOTE: By default only one [PaymentComponent] will be created per lifecycle. Use [key] in case you need to
* instantiate multiple [PaymentComponent]s in the same lifecycle.
*
* @return The Component
*/
@Composable
fun <
ComponentT : PaymentComponent,
ConfigurationT : Configuration,
ComponentStateT : PaymentComponentState<*>,
ComponentCallbackT : ComponentCallback<ComponentStateT>
> StoredPaymentComponentProvider<ComponentT, ConfigurationT, ComponentStateT, ComponentCallbackT>.get(
storedPaymentMethod: StoredPaymentMethod,
configuration: ConfigurationT,
componentCallback: ComponentCallbackT,
key: String?,
order: Order? = null,
): ComponentT {
return get(
savedStateRegistryOwner = LocalSavedStateRegistryOwner.current,
viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw ComponentException("Cannot find current LocalViewModelStoreOwner"),
lifecycleOwner = LocalLifecycleOwner.current,
storedPaymentMethod = storedPaymentMethod,
configuration = configuration,
application = LocalContext.current.applicationContext as Application,
componentCallback = componentCallback,
order = order,
key = key,
)
}

/**
* Get a [PaymentComponent] with a checkout session from a [Composable]. You only need to integrate with the /sessions
* endpoint to create a session and the component will automatically handle the rest of the payment flow.
*
* @param checkoutSession The [CheckoutSession] object to launch this component.
* @param paymentMethod The corresponding [PaymentMethod] object.
* @param configuration The Configuration of the component.
* @param componentCallback The callback to handle events from the [PaymentComponent].
* @param key The key to use to identify the [PaymentComponent].
*
* NOTE: By default only one [PaymentComponent] will be created per lifecycle. Use [key] in case you need to
* instantiate multiple [PaymentComponent]s in the same lifecycle.
*
* @return The Component
*/
@Composable
fun <
ComponentT : PaymentComponent,
ConfigurationT : Configuration,
ComponentStateT : PaymentComponentState<*>,
ComponentCallbackT : SessionComponentCallback<ComponentStateT>
> SessionPaymentComponentProvider<ComponentT, ConfigurationT, ComponentStateT, ComponentCallbackT>.get(
checkoutSession: CheckoutSession,
paymentMethod: PaymentMethod,
configuration: ConfigurationT,
componentCallback: ComponentCallbackT,
key: String,
): ComponentT {
return get(
savedStateRegistryOwner = LocalSavedStateRegistryOwner.current,
viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw ComponentException("Cannot find current LocalViewModelStoreOwner"),
lifecycleOwner = LocalLifecycleOwner.current,
checkoutSession = checkoutSession,
paymentMethod = paymentMethod,
configuration = configuration,
application = LocalContext.current.applicationContext as Application,
componentCallback = componentCallback,
key = key,
)
}

/**
* Get a [PaymentComponent] with a stored payment method and a checkout session from a [Composable]. You only need to
* integrate with the /sessions endpoint to create a session and the component will automatically handle the rest of
* the payment flow.
*
* @param checkoutSession The [CheckoutSession] object to launch this component.
* @param storedPaymentMethod The corresponding [StoredPaymentMethod] object.
* @param configuration The Configuration of the component.
* @param componentCallback The callback to handle events from the [PaymentComponent].
* @param key The key to use to identify the [PaymentComponent].
*
* NOTE: By default only one [PaymentComponent] will be created per lifecycle. Use [key] in case you need to
* instantiate multiple [PaymentComponent]s in the same lifecycle.
*
* @return The Component
*/
@Composable
fun <
ComponentT : PaymentComponent,
ConfigurationT : Configuration,
ComponentStateT : PaymentComponentState<*>,
ComponentCallbackT : SessionComponentCallback<ComponentStateT>
> SessionStoredPaymentComponentProvider<ComponentT, ConfigurationT, ComponentStateT, ComponentCallbackT>.get(
checkoutSession: CheckoutSession,
storedPaymentMethod: StoredPaymentMethod,
configuration: ConfigurationT,
componentCallback: ComponentCallbackT,
key: String?,
): ComponentT {
return get(
savedStateRegistryOwner = LocalSavedStateRegistryOwner.current,
viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw ComponentException("Cannot find current LocalViewModelStoreOwner"),
lifecycleOwner = LocalLifecycleOwner.current,
checkoutSession = checkoutSession,
storedPaymentMethod = storedPaymentMethod,
configuration = configuration,
application = LocalContext.current.applicationContext as Application,
componentCallback = componentCallback,
key = key,
)
}

/**
* A [Composable] that can display input and fill in details for a [Component].
*/
@Suppress("unused")
@Composable
fun <T> AdyenComponent(
component: T,
modifier: Modifier = Modifier,
) where T : ViewableComponent, T : Component {
val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
factory = {
AdyenComponentView(it).apply {
attach(component, lifecycleOwner)
}
},
modifier = modifier,
)
}
7 changes: 7 additions & 0 deletions config/detekt/detekt.yml
Expand Up @@ -7,6 +7,13 @@ complexity:
- '**/test/**'
- '**/androidTest/**'

naming:
active: true
FunctionNaming:
active: true
ignoreAnnotated:
- 'Composable'

style:
active: true
MaxLineLength:
Expand Down
11 changes: 11 additions & 0 deletions dependencies.gradle
Expand Up @@ -24,6 +24,7 @@ ext {
detekt_gradle_plugin_version = "1.23.0"
dokka_version = "1.8.20"
hilt_version = "2.46.1"
compose_compiler_version = '1.4.8'

// Code quality
detekt_version = "1.23.0"
Expand All @@ -40,6 +41,11 @@ ext {
recyclerview_version = "1.3.0"
constraintlayout_version = '2.1.4'

// Compose Dependencies
compose_activity_version = '1.7.2'
compose_bom_version = '2023.06.01'
compose_viewmodel_version = '2.6.1'

// Adyen Dependencies
adyen3ds2_version = "2.2.13"

Expand Down Expand Up @@ -84,6 +90,11 @@ ext {
preference : "androidx.preference:preference-ktx:$preference_version",
recyclerview : "androidx.recyclerview:recyclerview:$recyclerview_version"
],
compose : [
activity : "androidx.activity:activity-compose:$compose_activity_version",
bom : "androidx.compose:compose-bom:$compose_bom_version",
viewmodel: "androidx.lifecycle:lifecycle-viewmodel-compose:$compose_viewmodel_version"
],
hilt : "com.google.dagger:hilt-android:$hilt_version",
hiltCompiler : "com.google.dagger:hilt-compiler:$hilt_version",
kotlinCoroutines : [
Expand Down
50 changes: 50 additions & 0 deletions drop-in-compose/build.gradle
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by oscars on 1/6/2023.
*/

plugins {
id 'com.android.library'
id 'kotlin-android'
}

// Maven artifact
ext.mavenArtifactId = "drop-in-compose"
ext.mavenArtifactName = "Adyen checkout drop-in component compose"
ext.mavenArtifactDescription = "Compose compat Adyen checkout drop-in component."

apply from: "${rootDir}/config/gradle/sharedTasks.gradle"

android {
namespace 'com.adyen.checkout.dropin.compose'
compileSdkVersion compile_sdk_version

defaultConfig {
minSdkVersion min_sdk_version
targetSdkVersion target_sdk_version
versionCode version_code
versionName version_name

testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
consumerProguardFiles "consumer-rules.pro"
}

buildFeatures {
compose true
}

composeOptions {
kotlinCompilerExtensionVersion = compose_compiler_version
}
}

dependencies {
// Checkout
api project(':drop-in')

implementation platform(libraries.compose.bom)
implementation libraries.compose.activity
}
Empty file.
10 changes: 10 additions & 0 deletions drop-in-compose/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2023 Adyen N.V.
~
~ This file is open source and available under the MIT license. See the LICENSE file for more info.
~
~ Created by oscars on 1/6/2023.
-->

<manifest />