diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6a36733c3..4406239fd 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -40,7 +40,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - api-level: [ 33 ] + api-level: [ 35 ] needs: build steps: - name: Free Disk Space (Ubuntu) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6af68a51a..4b774f2ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - api-level: [ 33 ] + api-level: [ 35 ] needs: build steps: - name: Free Disk Space (Ubuntu) diff --git a/app/build.gradle b/app/build.gradle index 3e3323443..6a3c26d97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,9 +1,13 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-android' android { + buildFeatures { + viewBinding true + } + defaultConfig { namespace "co.omise.android.example" minSdkVersion min_sdk_version @@ -47,8 +51,11 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' } } @@ -66,4 +73,4 @@ dependencies { } repositories { mavenCentral() -} +} \ No newline at end of file diff --git a/app/src/kotlin/java/co/omise/android/example/CheckoutActivity.kt b/app/src/kotlin/java/co/omise/android/example/CheckoutActivity.kt index 109fa89de..29150f714 100644 --- a/app/src/kotlin/java/co/omise/android/example/CheckoutActivity.kt +++ b/app/src/kotlin/java/co/omise/android/example/CheckoutActivity.kt @@ -7,16 +7,11 @@ import android.os.Parcelable import android.util.Log import android.view.Menu import android.view.MenuItem -import android.widget.Button -import android.widget.EditText -import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import co.omise.android.AuthorizingPaymentURLVerifier.Companion.EXTRA_AUTHORIZED_URLSTRING import co.omise.android.AuthorizingPaymentURLVerifier.Companion.EXTRA_EXPECTED_RETURN_URLSTRING_PATTERNS -import co.omise.android.api.Client -import co.omise.android.api.RequestListener import co.omise.android.config.ButtonCustomization import co.omise.android.config.ButtonCustomizationBuilder import co.omise.android.config.ButtonType @@ -25,6 +20,7 @@ import co.omise.android.config.TextBoxCustomizationBuilder import co.omise.android.config.ThemeConfig import co.omise.android.config.ToolbarCustomizationBuilder import co.omise.android.config.UiCustomizationBuilder +import co.omise.android.example.databinding.ActivityCheckoutBinding import co.omise.android.models.Amount import co.omise.android.models.CardHolderDataField import co.omise.android.models.CardHolderDataList @@ -38,11 +34,6 @@ import co.omise.android.ui.CreditCardActivity import co.omise.android.ui.OmiseActivity import co.omise.android.ui.PaymentCreatorActivity import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_checkout.amount_edit -import kotlinx.android.synthetic.main.activity_checkout.authorize_url_button -import kotlinx.android.synthetic.main.activity_checkout.choose_payment_method_button -import kotlinx.android.synthetic.main.activity_checkout.credit_card_button -import kotlinx.android.synthetic.main.activity_checkout.currency_edit inline fun Intent.parcelable(key: String?): T? = when { // https://stackoverflow.com/questions/72571804/getserializableextra-and-getparcelableextra-are-deprecated-what-is-the-alternat/73543350#73543350 @@ -50,31 +41,23 @@ inline fun Intent.parcelable(key: String?): T? = when { else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T } -class CheckoutActivity : AppCompatActivity() { +class CheckoutActivity : OmiseActivity() { companion object { - private const val TAG = "CheckoutActivity" private const val PUBLIC_KEY = "[PUBLIC_KEY]" private const val GOOGLEPAY_MERCHANT_ID = "[GOOGLEPAY_MERCHANT_ID]" private const val GOOGLEPAY_REQUEST_BILLING_ADDRESS = false private const val GOOGLEPAY_REQUEST_PHONE_NUMBER = false - private val CARD_HOLDER_DATA = CardHolderDataList(arrayListOf(CardHolderDataField.EMAIL,CardHolderDataField.PHONE_NUMBER)) - + private val CARD_HOLDER_DATA = CardHolderDataList(arrayListOf(CardHolderDataField.EMAIL, CardHolderDataField.PHONE_NUMBER)) private const val AUTHORIZING_PAYMENT_REQUEST_CODE = 0x3D5 private const val PAYMENT_CREATOR_REQUEST_CODE = 0x3D6 private const val CREDIT_CARD_REQUEST_CODE = 0x3D7 } - private val amountEdit: EditText by lazy { amount_edit } - private val currencyEdit: EditText by lazy { currency_edit } - private val choosePaymentMethodButton: Button by lazy { choose_payment_method_button } - private val creditCardButton: Button by lazy { credit_card_button } - private val authorizeUrlButton: Button by lazy { authorize_url_button } - private val snackbar: Snackbar by lazy { - Snackbar.make(findViewById(R.id.content), "", Snackbar.LENGTH_SHORT) - } + private lateinit var binding: ActivityCheckoutBinding + private val snackbar: Snackbar by lazy { Snackbar.make(binding.content, "", Snackbar.LENGTH_SHORT) } private lateinit var authorizingPaymentLauncher: ActivityResultLauncher private lateinit var paymentCreatorLauncher: ActivityResultLauncher @@ -82,58 +65,44 @@ class CheckoutActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_checkout) + binding = ActivityCheckoutBinding.inflate(layoutInflater) + setContentView(binding.root) supportActionBar?.title = getString(R.string.activity_checkout) - authorizingPaymentLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result: ActivityResult -> - handleActivityResult( - AUTHORIZING_PAYMENT_REQUEST_CODE, - result.resultCode, - result.data - ) - } - - paymentCreatorLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result: ActivityResult -> - handleActivityResult( - PAYMENT_CREATOR_REQUEST_CODE, - result.resultCode, - result.data - ) - } - - creditCardLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result: ActivityResult -> - handleActivityResult( - CREDIT_CARD_REQUEST_CODE, - result.resultCode, - result.data - ) - } + setupActivityLaunchers() - choosePaymentMethodButton.setOnClickListener { choosePaymentMethod() } - creditCardButton.setOnClickListener { payByCreditCard() } - authorizeUrlButton.setOnClickListener { + binding.choosePaymentMethodButton.setOnClickListener { choosePaymentMethod() } + binding.creditCardButton.setOnClickListener { payByCreditCard() } + binding.authorizeUrlButton.setOnClickListener { AuthorizingPaymentDialog.showAuthorizingPaymentDialog(this) { authorizeUrl, returnUrl -> startAuthoringPaymentActivity(authorizeUrl, returnUrl) } } + } + + private fun setupActivityLaunchers() { + authorizingPaymentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleActivityResult(AUTHORIZING_PAYMENT_REQUEST_CODE, result.resultCode, result.data) + } + + paymentCreatorLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleActivityResult(PAYMENT_CREATOR_REQUEST_CODE, result.resultCode, result.data) + } + creditCardLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleActivityResult(CREDIT_CARD_REQUEST_CODE, result.resultCode, result.data) + } } private fun choosePaymentMethod() { val isUsedSpecificsPaymentMethods = PaymentSetting.isUsedSpecificsPaymentMethods(this) - val localAmount = amountEdit.text.toString().trim().toDouble() - val currency = currencyEdit.text.toString().trim().lowercase() + val localAmount = binding.amountEdit.text.toString().trim().toDouble() + val currency = binding.currencyEdit.text.toString().trim().lowercase() val amount = Amount.fromLocalAmount(localAmount, currency) - Intent(this, PaymentCreatorActivity::class.java).run { + val intent = Intent(this, PaymentCreatorActivity::class.java).apply { putExtra(OmiseActivity.EXTRA_PKEY, PUBLIC_KEY) putExtra(OmiseActivity.EXTRA_AMOUNT, amount.amount) putExtra(OmiseActivity.EXTRA_CURRENCY, amount.currency) @@ -145,18 +114,16 @@ class CheckoutActivity : AppCompatActivity() { if (isUsedSpecificsPaymentMethods) { putExtra(OmiseActivity.EXTRA_CAPABILITY, PaymentSetting.createCapabilityFromPreferences(this@CheckoutActivity)) } - - paymentCreatorLauncher.launch(this) } + paymentCreatorLauncher.launch(intent) } - private fun payByCreditCard() { - Intent(this, CreditCardActivity::class.java).run { + val intent = Intent(this, CreditCardActivity::class.java).apply { putExtra(OmiseActivity.EXTRA_PKEY, PUBLIC_KEY) putExtra(OmiseActivity.EXTRA_CARD_HOLDER_DATA, CARD_HOLDER_DATA) - creditCardLauncher.launch(this) } + creditCardLauncher.launch(intent) } private fun startAuthoringPaymentActivity(authorizeUrl: String, returnUrl: String) { @@ -202,6 +169,7 @@ class CheckoutActivity : AppCompatActivity() { .textColor("#1A56F0") .backgroundColor("#FFFFFF") .build() + val buttonCustomizations: MutableMap = mutableMapOf() buttonCustomizations[ButtonType.SUBMIT] = primaryButtonCustomization buttonCustomizations[ButtonType.CONTINUE] = primaryButtonCustomization @@ -211,7 +179,6 @@ class CheckoutActivity : AppCompatActivity() { buttonCustomizations[ButtonType.RESEND] = secondaryButtonCustomization buttonCustomizations[ButtonType.CANCEL] = secondaryButtonCustomization - val uiCustomization = UiCustomizationBuilder() .setDefaultTheme(ThemeConfig( labelCustomization, @@ -219,34 +186,26 @@ class CheckoutActivity : AppCompatActivity() { textBoxCustomization, buttonCustomizations )) - .setDarkTheme(ThemeConfig( - buttonCustomizations = buttonCustomizations - )) + .setDarkTheme(ThemeConfig(buttonCustomizations = buttonCustomizations)) .setMonoChromeTheme(ThemeConfig()) .build() - Log.d( - TAG, """ + Log.d(TAG, """ authorizeUrl=$authorizeUrl returnUrl=$returnUrl - """.trimIndent() - ) - Intent(this, AuthorizingPaymentActivity::class.java).run { + """.trimIndent()) + + val intent = Intent(this, AuthorizingPaymentActivity::class.java).apply { putExtra(EXTRA_AUTHORIZED_URLSTRING, authorizeUrl) putExtra(EXTRA_EXPECTED_RETURN_URLSTRING_PATTERNS, arrayOf(returnUrl)) putExtra(EXTRA_UI_CUSTOMIZATION, uiCustomization) - putExtra( - EXTRA_THREE_DS_REQUESTOR_APP_URL, - "sampleapp://omise.co/authorize_return" - ) - authorizingPaymentLauncher.launch(this) + putExtra(EXTRA_THREE_DS_REQUESTOR_APP_URL, "sampleapp://omise.co/authorize_return") } + authorizingPaymentLauncher.launch(intent) } private fun openPaymentSetting() { - Intent(this, PaymentSettingActivity::class.java).run { - startActivity(this) - } + startActivity(Intent(this, PaymentSettingActivity::class.java)) } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -263,7 +222,6 @@ class CheckoutActivity : AppCompatActivity() { } private fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - // custom result code when web view is closed if (resultCode == AuthorizingPaymentActivity.WEBVIEW_CLOSED_RESULT_CODE) { snackbar.setText(R.string.webview_closed).show() @@ -291,7 +249,6 @@ class CheckoutActivity : AppCompatActivity() { Log.e(TAG, throwable.message, throwable.cause) throwable.message ?: "Unknown error." } - null -> "Not found the authorization result." } Log.d(TAG, resultMessage) @@ -302,7 +259,7 @@ class CheckoutActivity : AppCompatActivity() { PAYMENT_CREATOR_REQUEST_CODE -> { // if the payment method requires both source and token then you will receive both objects // otherwise one object will be received - if(data.hasExtra(OmiseActivity.EXTRA_TOKEN) && data.hasExtra(OmiseActivity.EXTRA_SOURCE_OBJECT)){ + if (data.hasExtra(OmiseActivity.EXTRA_TOKEN) && data.hasExtra(OmiseActivity.EXTRA_SOURCE_OBJECT)) { val source = data.parcelable(OmiseActivity.EXTRA_SOURCE_OBJECT) val token = data.parcelable(OmiseActivity.EXTRA_TOKEN_OBJECT) snackbar.setText((source?.id ?: "No source object.") + "/" + (token?.id ?: "No token object.")).show() @@ -324,7 +281,6 @@ class CheckoutActivity : AppCompatActivity() { snackbar.setText(token?.id ?: "No token object.").show() Log.d(TAG, "token: ${token?.id}") } - } } } diff --git a/app/src/main/java/co/omise/android/example/PaymentResultActivity.kt b/app/src/main/java/co/omise/android/example/PaymentResultActivity.kt index 48c592f39..dbd99570f 100644 --- a/app/src/main/java/co/omise/android/example/PaymentResultActivity.kt +++ b/app/src/main/java/co/omise/android/example/PaymentResultActivity.kt @@ -5,10 +5,12 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.TextView +import co.omise.android.ui.OmiseActivity + /** * The example activity to receive the result of the payment. */ -class PaymentResultActivity : AppCompatActivity() { +class PaymentResultActivity : OmiseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_payment_result) @@ -22,9 +24,9 @@ class PaymentResultActivity : AppCompatActivity() { } } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) // Depending on your application status, handle the result from the payment app as needed } -} +} \ No newline at end of file diff --git a/app/src/main/java/co/omise/android/example/PaymentSettingActivity.kt b/app/src/main/java/co/omise/android/example/PaymentSettingActivity.kt index 0e04f4780..de944884f 100644 --- a/app/src/main/java/co/omise/android/example/PaymentSettingActivity.kt +++ b/app/src/main/java/co/omise/android/example/PaymentSettingActivity.kt @@ -1,9 +1,9 @@ package co.omise.android.example import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import co.omise.android.ui.OmiseActivity -class PaymentSettingActivity : AppCompatActivity() { +class PaymentSettingActivity : OmiseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/build.gradle b/build.gradle index 8d3931423..b8c211a09 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ apply plugin: 'io.github.gradle-nexus.publish-plugin' // Global group id required for nexus publish group 'co.omise' + buildscript { ext { sample_app_version = '1.0' @@ -12,44 +13,44 @@ buildscript { omise_sdk_version = '5.9.0' omise_sdk_code_version = 61 - compile_sdk_version = 34 + compile_sdk_version = 36 build_tools_version = '29.0.3' min_sdk_version = 21 - target_sdk_version = 34 + target_sdk_version = 36 netcetera_threeds_sdk_version = '2.5.3.0' - kotlin_version = '1.7.10' - android_plugin_version = '8.2.2' - appcompat_version = '1.3.0' + kotlin_version = '2.1.0' + android_plugin_version = '8.13.1' + appcompat_version = '1.7.1' joda_time_version = '2.9.2' - okhttp_version = '4.9.0' - material_version = '1.0.0' + okhttp_version = '4.12.0' + material_version = '1.12.0' support_annotations_version = '28.0.0' guava_version = '19.0' jackson_version = '2.11.0' preference_version = '1.1.0' annotation_version = '1.0.0' - fragment_testing_version = '1.2.0-alpha01' + fragment_testing_version = '1.8.8' junit_version = '4.12' - test_version = '1.3.0-alpha01' - robolectric_version = '4.10.3' - espresso_version = '3.5.1' - junit_ext_version = '1.1.1' - mockito_version = '5.3.1' - mockito_kotlin_version = '5.0.0' + test_version = '1.7.0' + test_runner_version = '1.7.0' + robolectric_version = '4.16' + espresso_version = '3.7.0' + junit_ext_version = '1.3.0' + dexmaker_mockito_inline_version = '2.28.6' + mockito_kotlin_version = '6.1.0' constraintlayout_version = '1.1.3' coroutines_version = '1.6.4' lifecycle_version = '2.2.0' - arch_version = '2.1.0' + arch_version = '2.2.0' dexopener_version = '2.0.5' jacoco_version = '0.8.10' sonarqube_version = '4.3.1.3277' - uiautomator_version = '2.2.0' - activity_version = '1.2.4' + uiautomator_version = '2.3.0' + activity_version = '1.11.0' } - repositories { google() mavenCentral() @@ -101,4 +102,4 @@ nexusPublishing { password = System.getenv("SONATYPE_PASSWORD") } } -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 926cbc322..5db46d4fd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Jan 24 15:28:22 ICT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sdk/build.gradle b/sdk/build.gradle index 4cb306e49..0022f8813 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' apply plugin: 'maven-publish' apply plugin: 'signing' -apply plugin: 'kotlin-android-extensions' apply plugin: 'jacoco' apply plugin: 'org.sonarqube' apply plugin: 'org.jlleitschuh.gradle.ktlint' @@ -10,6 +10,7 @@ apply plugin: 'org.jlleitschuh.gradle.ktlint' android { buildFeatures { buildConfig true + viewBinding true } defaultConfig { namespace 'co.omise.android' @@ -20,6 +21,8 @@ android { compileSdk compile_sdk_version buildConfigField "int", "VERSION_CODE", "$omise_sdk_code_version" buildConfigField "String", "VERSION_NAME", "\"$omise_sdk_version\"" + buildConfigField "String", "OMISE_PUBLIC_KEY", "\"${System.getenv("OMISE_PUBLIC_KEY") ?: ""}\"" + buildConfigField "String", "GOOGLE_PAY_MERCHANT_ID", "\"${System.getenv("GOOGLE_PAY_MERCHANT_ID") ?: ""}\"" testInstrumentationRunner "co.omise.android.OmiseTestRunner" // Consumer proguard rules is set to avoid cluttering the consumer's pro-guard rules // with rules specific to the SDK which may break the SDK if not added to the consumer's side @@ -51,10 +54,6 @@ android { disable 'InvalidPackage' // okio } - androidExtensions { - experimental = true - } - testOptions { // It's required for Sonar reportDir "${project.buildDir}/reports/coverage/androidTest/production/debug/connected" @@ -164,7 +163,7 @@ dependencies { testImplementation "junit:junit:$junit_version" testImplementation "org.robolectric:robolectric:$robolectric_version" testImplementation "androidx.test:rules:$test_version" - testImplementation "androidx.test:runner:$test_version" + testImplementation "androidx.test:runner:$test_runner_version" testImplementation "androidx.test.espresso:espresso-core:$espresso_version" testImplementation "androidx.test.espresso:espresso-intents:$espresso_version" testImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" @@ -178,25 +177,24 @@ dependencies { androidTestImplementation "androidx.annotation:annotation:$annotation_version" androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version" androidTestImplementation "org.robolectric:annotations:$robolectric_version" - androidTestImplementation "org.mockito:mockito-android:$mockito_version" + androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline:$dexmaker_mockito_inline_version" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "androidx.test:rules:$test_version" - androidTestImplementation "androidx.test:runner:$test_version" + androidTestImplementation "androidx.test:runner:$test_runner_version" androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-web:$espresso_version" androidTestImplementation "androidx.test.ext:junit:$junit_ext_version" androidTestImplementation "androidx.test.uiautomator:uiautomator:$uiautomator_version" - androidTestImplementation "com.github.tmurakami:dexopener:$dexopener_version" // Netcetera 3SD SDK implementation "org.slf4j:slf4j-api:1.7.36" implementation "com.github.tony19:logback-android:1.3.0-3" implementation("com.netcetera.nca-341-2.3ds-sdk.android:3ds-sdk:$netcetera_threeds_sdk_version") } - repositories { mavenCentral() maven { url 'https://jitpack.io' } @@ -277,4 +275,4 @@ sonar { reporter "checkstyle" } } -} +} \ No newline at end of file diff --git a/sdk/src/androidTest/java/co/omise/android/OmiseTestRunner.kt b/sdk/src/androidTest/java/co/omise/android/OmiseTestRunner.kt index 8130836da..4dc0a3b5a 100644 --- a/sdk/src/androidTest/java/co/omise/android/OmiseTestRunner.kt +++ b/sdk/src/androidTest/java/co/omise/android/OmiseTestRunner.kt @@ -3,7 +3,6 @@ package co.omise.android import android.app.Application import android.content.Context import androidx.test.runner.AndroidJUnitRunner -import com.github.tmurakami.dexopener.DexOpener class OmiseTestRunner : AndroidJUnitRunner() { override fun newApplication( @@ -11,7 +10,6 @@ class OmiseTestRunner : AndroidJUnitRunner() { className: String?, context: Context?, ): Application { - DexOpener.install(this) return super.newApplication(cl, Application::class.java.name, context) } } diff --git a/sdk/src/androidTest/java/co/omise/android/models/ModelTest.kt b/sdk/src/androidTest/java/co/omise/android/models/ModelTest.kt index 505321440..aa0428751 100644 --- a/sdk/src/androidTest/java/co/omise/android/models/ModelTest.kt +++ b/sdk/src/androidTest/java/co/omise/android/models/ModelTest.kt @@ -5,7 +5,7 @@ import android.os.Parcelable import androidx.test.ext.junit.runners.AndroidJUnit4 import co.omise.android.SDKTest import co.omise.android.extensions.getParcelableCompat -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime import org.joda.time.DateTimeZone import org.junit.Test diff --git a/sdk/src/androidTest/java/co/omise/android/ui/AuthorizingPaymentActivityTest.kt b/sdk/src/androidTest/java/co/omise/android/ui/AuthorizingPaymentActivityTest.kt index a91774969..e199b2c6b 100644 --- a/sdk/src/androidTest/java/co/omise/android/ui/AuthorizingPaymentActivityTest.kt +++ b/sdk/src/androidTest/java/co/omise/android/ui/AuthorizingPaymentActivityTest.kt @@ -218,7 +218,7 @@ class AuthorizingPaymentActivityTest { onView(withId(R.id.authorizing_payment_webview)) .check(matches(isDisplayed())) - checkWebViewUrl(authorizeAcsUrl) + .check(matches(withUrl(authorizeAcsUrl))) } @Test @@ -234,7 +234,7 @@ class AuthorizingPaymentActivityTest { error.postValue(testException) onView(withId(R.id.authorizing_payment_webview)) .check(matches(isDisplayed())) - checkWebViewUrl(authorizeAcsUrl) + .check(matches(withUrl(authorizeAcsUrl))) onView(withId(R.id.authorizing_payment_webview)).perform(loadUrl(returnUrl)) onView(withId(R.id.authorizing_payment_webview)).perform(loadUrl(returnUrl)) } @@ -252,7 +252,7 @@ class AuthorizingPaymentActivityTest { error.postValue(testException) onView(withId(R.id.authorizing_payment_webview)) .check(matches(isDisplayed())) - checkWebViewUrl(authorizeAcsUrl) + .check(matches(withUrl(authorizeAcsUrl))) onView(withId(R.id.authorizing_payment_webview)).perform(loadUrl(returnUrl)) } @@ -269,7 +269,7 @@ class AuthorizingPaymentActivityTest { error.postValue(testException) onView(withId(R.id.authorizing_payment_webview)) .check(matches(isDisplayed())) - checkWebViewUrl(authorizeAcsUrl) + .check(matches(withUrl(authorizeAcsUrl))) onView(withId(R.id.authorizing_payment_webview)).perform(loadUrl(returnUrl)) } @@ -283,14 +283,9 @@ class AuthorizingPaymentActivityTest { } val scenario = ActivityScenario.launchActivityForResult(intent) - scenario.onActivity { activity -> - val client = activity.setTestWebView() - client.onPageStarted( - activity.findViewById(R.id.authorizing_payment_webview), - returnUrl, - null, - ) - } + scenario.onActivity { activity -> activity.setTestWebView() } + + onView(withId(R.id.authorizing_payment_webview)).perform(loadUrl(returnUrl)) val activityResult = scenario.result // Due to issue BadParcelableException: ClassNotFoundException when unmarshalling. @@ -581,21 +576,4 @@ class AuthorizingPaymentActivityTest { verify(progressView, never()).showProgress() verify(progressView, never()).hideProgress() } - - private fun checkWebViewUrl(url: String) { - // Retry checking the URL as WebView loadUrl is asynchronous - val maxAttempts = 20 - var lastError: Throwable? = null - for (i in 0 until maxAttempts) { - try { - onView(withId(R.id.authorizing_payment_webview)) - .check(matches(withUrl(url))) - return - } catch (e: Throwable) { - lastError = e - Thread.sleep(250) - } - } - throw lastError ?: AssertionError("Timeout waiting for WebView url: $url") - } } diff --git a/sdk/src/androidTest/java/co/omise/android/ui/PaymentChooserFragmentTest.kt b/sdk/src/androidTest/java/co/omise/android/ui/PaymentChooserFragmentTest.kt index aab617f79..b5f6224a4 100644 --- a/sdk/src/androidTest/java/co/omise/android/ui/PaymentChooserFragmentTest.kt +++ b/sdk/src/androidTest/java/co/omise/android/ui/PaymentChooserFragmentTest.kt @@ -156,7 +156,7 @@ class PaymentChooserFragmentTest { assertListAtIndexHasResource(18, R.string.payment_method_duitnow_obw_title) assertListAtIndexHasResource(19, R.string.payment_method_duitnow_qr_title) - onView(withId(R.id.recycler_view)).perform(scrollToPosition(26)) + onView(withId(R.id.recycler_view)).perform(scrollToPosition(25)) assertListAtIndexHasResource(20, R.string.payment_method_maybank_qr_title) assertListAtIndexHasResource(21, R.string.payment_method_rabbit_linepay_title) diff --git a/sdk/src/androidTest/java/co/omise/android/ui/PaymentCreatorActivityTest.kt b/sdk/src/androidTest/java/co/omise/android/ui/PaymentCreatorActivityTest.kt index 2292dc322..9c9773d2e 100644 --- a/sdk/src/androidTest/java/co/omise/android/ui/PaymentCreatorActivityTest.kt +++ b/sdk/src/androidTest/java/co/omise/android/ui/PaymentCreatorActivityTest.kt @@ -167,10 +167,10 @@ class PaymentCreatorActivityTest { } val scenario = ActivityScenario.launchActivityForResult(intent).onActivity { - it.performActivityResult(100, RESULT_OK, creditCardIntent) + it.handleCreditCardResult(androidx.activity.result.ActivityResult(Activity.RESULT_OK, creditCardIntent)) } - assertEquals(RESULT_OK, scenario.result.resultCode) + assertEquals(Activity.RESULT_OK, scenario.result.resultCode) } @Test diff --git a/sdk/src/androidTest/java/co/omise/android/ui/TestFragmentActivity.kt b/sdk/src/androidTest/java/co/omise/android/ui/TestFragmentActivity.kt index 85e7e353b..23fbb8da3 100644 --- a/sdk/src/androidTest/java/co/omise/android/ui/TestFragmentActivity.kt +++ b/sdk/src/androidTest/java/co/omise/android/ui/TestFragmentActivity.kt @@ -3,24 +3,21 @@ package co.omise.android.ui import android.app.Instrumentation import android.content.Intent import android.os.Bundle -import android.os.PersistableBundle import android.view.ViewGroup import android.widget.FrameLayout -import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import co.omise.android.R -class TestFragmentActivity : AppCompatActivity() { +class TestFragmentActivity : OmiseActivity() { private var activityResult: Instrumentation.ActivityResult? = null - override fun onCreate( - savedInstanceState: Bundle?, - persistentState: PersistableBundle?, - ) { - super.onCreate(savedInstanceState, persistentState) + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.OmiseTheme) + super.onCreate(savedInstanceState) + val frameLayout = FrameLayout(this).apply { - id = android.R.id.content + id = R.id.payment_creator_container layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -28,7 +25,6 @@ class TestFragmentActivity : AppCompatActivity() { ) } setContentView(frameLayout) - setTheme(R.style.OmiseTheme) } override fun onActivityResult( @@ -42,8 +38,7 @@ class TestFragmentActivity : AppCompatActivity() { fun replaceFragment(fragment: Fragment) { supportFragmentManager.beginTransaction() - .add(android.R.id.content, fragment) - .addToBackStack("test_fragment") + .replace(R.id.payment_creator_container, fragment) .commit() } } diff --git a/sdk/src/main/java/co/omise/android/OmiseException.kt b/sdk/src/main/java/co/omise/android/OmiseException.kt index b22b7a61d..6cec6a11b 100644 --- a/sdk/src/main/java/co/omise/android/OmiseException.kt +++ b/sdk/src/main/java/co/omise/android/OmiseException.kt @@ -2,7 +2,7 @@ package co.omise.android import android.content.Intent import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import java.lang.Exception /** diff --git a/sdk/src/main/java/co/omise/android/config/UiCustomization.kt b/sdk/src/main/java/co/omise/android/config/UiCustomization.kt index a73cc4f59..23a3b2916 100644 --- a/sdk/src/main/java/co/omise/android/config/UiCustomization.kt +++ b/sdk/src/main/java/co/omise/android/config/UiCustomization.kt @@ -1,7 +1,7 @@ package co.omise.android.config import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize abstract class CustomizationBuilder { protected var textFontSize: Int? = null diff --git a/sdk/src/main/java/co/omise/android/models/Authentication.kt b/sdk/src/main/java/co/omise/android/models/Authentication.kt index b31e73e80..f7213cbf9 100644 --- a/sdk/src/main/java/co/omise/android/models/Authentication.kt +++ b/sdk/src/main/java/co/omise/android/models/Authentication.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import co.omise.android.api.RequestBuilder import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.RequestBody diff --git a/sdk/src/main/java/co/omise/android/models/Bank.kt b/sdk/src/main/java/co/omise/android/models/Bank.kt index a919fbc75..966cc092a 100644 --- a/sdk/src/main/java/co/omise/android/models/Bank.kt +++ b/sdk/src/main/java/co/omise/android/models/Bank.kt @@ -1,7 +1,7 @@ package co.omise.android.models import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Bank( diff --git a/sdk/src/main/java/co/omise/android/models/Barcode.kt b/sdk/src/main/java/co/omise/android/models/Barcode.kt index 0936d3314..ae6d35265 100644 --- a/sdk/src/main/java/co/omise/android/models/Barcode.kt +++ b/sdk/src/main/java/co/omise/android/models/Barcode.kt @@ -1,6 +1,6 @@ package co.omise.android.models -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime @Parcelize diff --git a/sdk/src/main/java/co/omise/android/models/Billing.kt b/sdk/src/main/java/co/omise/android/models/Billing.kt index 20276ff5a..99870648b 100644 --- a/sdk/src/main/java/co/omise/android/models/Billing.kt +++ b/sdk/src/main/java/co/omise/android/models/Billing.kt @@ -2,7 +2,7 @@ package co.omise.android.models import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Billing( diff --git a/sdk/src/main/java/co/omise/android/models/Capability.kt b/sdk/src/main/java/co/omise/android/models/Capability.kt index e61811e88..474e12be7 100644 --- a/sdk/src/main/java/co/omise/android/models/Capability.kt +++ b/sdk/src/main/java/co/omise/android/models/Capability.kt @@ -4,7 +4,7 @@ import co.omise.android.api.Endpoint import co.omise.android.api.RequestBuilder import co.omise.android.models.PaymentMethod.Companion.createSourceTypeMethod import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import okhttp3.HttpUrl import org.joda.time.DateTime diff --git a/sdk/src/main/java/co/omise/android/models/Card.kt b/sdk/src/main/java/co/omise/android/models/Card.kt index 781a9011c..f1d1d188a 100644 --- a/sdk/src/main/java/co/omise/android/models/Card.kt +++ b/sdk/src/main/java/co/omise/android/models/Card.kt @@ -1,7 +1,7 @@ package co.omise.android.models import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime /** diff --git a/sdk/src/main/java/co/omise/android/models/CardBrand.kt b/sdk/src/main/java/co/omise/android/models/CardBrand.kt index 0c7f54b00..014cd999c 100644 --- a/sdk/src/main/java/co/omise/android/models/CardBrand.kt +++ b/sdk/src/main/java/co/omise/android/models/CardBrand.kt @@ -2,8 +2,8 @@ package co.omise.android.models import android.os.Parcelable import co.omise.android.R -import kotlinx.android.parcel.IgnoredOnParcel -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize import java.util.regex.Pattern /** diff --git a/sdk/src/main/java/co/omise/android/models/CardHolderDataField.kt b/sdk/src/main/java/co/omise/android/models/CardHolderDataField.kt index c66d82870..f73236b7f 100644 --- a/sdk/src/main/java/co/omise/android/models/CardHolderDataField.kt +++ b/sdk/src/main/java/co/omise/android/models/CardHolderDataField.kt @@ -1,7 +1,7 @@ package co.omise.android.models import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize enum class CardHolderDataField { EMAIL, diff --git a/sdk/src/main/java/co/omise/android/models/ChargeStatus.kt b/sdk/src/main/java/co/omise/android/models/ChargeStatus.kt index 6e3ea292d..eaffe4929 100644 --- a/sdk/src/main/java/co/omise/android/models/ChargeStatus.kt +++ b/sdk/src/main/java/co/omise/android/models/ChargeStatus.kt @@ -3,7 +3,7 @@ package co.omise.android.models import android.os.Parcel import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue -import kotlinx.android.parcel.Parceler +import kotlinx.parcelize.Parceler /** * Represents charge.status field. diff --git a/sdk/src/main/java/co/omise/android/models/CountryInfo.kt b/sdk/src/main/java/co/omise/android/models/CountryInfo.kt index a03b8bfa3..09221c2a1 100644 --- a/sdk/src/main/java/co/omise/android/models/CountryInfo.kt +++ b/sdk/src/main/java/co/omise/android/models/CountryInfo.kt @@ -1,7 +1,7 @@ package co.omise.android.models import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class CountryInfo(val name: String, val code: String) : Parcelable { diff --git a/sdk/src/main/java/co/omise/android/models/Document.kt b/sdk/src/main/java/co/omise/android/models/Document.kt index 9679d4cb2..75011a973 100644 --- a/sdk/src/main/java/co/omise/android/models/Document.kt +++ b/sdk/src/main/java/co/omise/android/models/Document.kt @@ -1,7 +1,7 @@ package co.omise.android.models import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime @Parcelize diff --git a/sdk/src/main/java/co/omise/android/models/FlowType.kt b/sdk/src/main/java/co/omise/android/models/FlowType.kt index c90945592..3a7298b12 100644 --- a/sdk/src/main/java/co/omise/android/models/FlowType.kt +++ b/sdk/src/main/java/co/omise/android/models/FlowType.kt @@ -4,7 +4,7 @@ import android.annotation.SuppressLint import android.os.Parcel import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue -import kotlinx.android.parcel.Parceler +import kotlinx.parcelize.Parceler /** * Represents Source Flow object. diff --git a/sdk/src/main/java/co/omise/android/models/Item.kt b/sdk/src/main/java/co/omise/android/models/Item.kt index 7e4956a92..311a63cd3 100644 --- a/sdk/src/main/java/co/omise/android/models/Item.kt +++ b/sdk/src/main/java/co/omise/android/models/Item.kt @@ -3,7 +3,7 @@ package co.omise.android.models import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize // see https://stackoverflow.com/questions/77639841/a-field-stability-is-added-in-my-parcelable-classes for more info diff --git a/sdk/src/main/java/co/omise/android/models/Limits.kt b/sdk/src/main/java/co/omise/android/models/Limits.kt index 50e1ce8b2..e828ea0b9 100644 --- a/sdk/src/main/java/co/omise/android/models/Limits.kt +++ b/sdk/src/main/java/co/omise/android/models/Limits.kt @@ -2,7 +2,7 @@ package co.omise.android.models import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Limits( diff --git a/sdk/src/main/java/co/omise/android/models/NetceteraConfig.kt b/sdk/src/main/java/co/omise/android/models/NetceteraConfig.kt index acf1b670e..107ca06bd 100644 --- a/sdk/src/main/java/co/omise/android/models/NetceteraConfig.kt +++ b/sdk/src/main/java/co/omise/android/models/NetceteraConfig.kt @@ -1,7 +1,7 @@ package co.omise.android.models import co.omise.android.api.RequestBuilder import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import org.joda.time.DateTime diff --git a/sdk/src/main/java/co/omise/android/models/PaymentMethod.kt b/sdk/src/main/java/co/omise/android/models/PaymentMethod.kt index 9ed8ebde9..9aa5bea1f 100644 --- a/sdk/src/main/java/co/omise/android/models/PaymentMethod.kt +++ b/sdk/src/main/java/co/omise/android/models/PaymentMethod.kt @@ -1,7 +1,7 @@ package co.omise.android.models import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime /** diff --git a/sdk/src/main/java/co/omise/android/models/References.kt b/sdk/src/main/java/co/omise/android/models/References.kt index 92c4079d2..165edc3dd 100644 --- a/sdk/src/main/java/co/omise/android/models/References.kt +++ b/sdk/src/main/java/co/omise/android/models/References.kt @@ -2,7 +2,7 @@ package co.omise.android.models import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime /** diff --git a/sdk/src/main/java/co/omise/android/models/Shipping.kt b/sdk/src/main/java/co/omise/android/models/Shipping.kt index 362e2a025..6a210742c 100644 --- a/sdk/src/main/java/co/omise/android/models/Shipping.kt +++ b/sdk/src/main/java/co/omise/android/models/Shipping.kt @@ -2,7 +2,7 @@ package co.omise.android.models import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Shipping( diff --git a/sdk/src/main/java/co/omise/android/models/Source.kt b/sdk/src/main/java/co/omise/android/models/Source.kt index 5f459a589..68a75aa5c 100644 --- a/sdk/src/main/java/co/omise/android/models/Source.kt +++ b/sdk/src/main/java/co/omise/android/models/Source.kt @@ -3,8 +3,8 @@ package co.omise.android.models import co.omise.android.api.Endpoint import co.omise.android.api.RequestBuilder import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize -import kotlinx.android.parcel.TypeParceler +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.TypeParceler import okhttp3.HttpUrl import okhttp3.RequestBody import org.joda.time.DateTime diff --git a/sdk/src/main/java/co/omise/android/models/SourceType.kt b/sdk/src/main/java/co/omise/android/models/SourceType.kt index cbdec25c8..d87aea326 100644 --- a/sdk/src/main/java/co/omise/android/models/SourceType.kt +++ b/sdk/src/main/java/co/omise/android/models/SourceType.kt @@ -5,8 +5,8 @@ import android.os.Parcel import android.os.Parcelable import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue -import kotlinx.android.parcel.Parceler -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parceler +import kotlinx.parcelize.Parcelize /** * Represents Source Type object. diff --git a/sdk/src/main/java/co/omise/android/models/Token.kt b/sdk/src/main/java/co/omise/android/models/Token.kt index 8a1b86d97..fb96366f6 100644 --- a/sdk/src/main/java/co/omise/android/models/Token.kt +++ b/sdk/src/main/java/co/omise/android/models/Token.kt @@ -3,8 +3,8 @@ package co.omise.android.models import co.omise.android.api.Endpoint import co.omise.android.api.RequestBuilder import com.fasterxml.jackson.annotation.JsonProperty -import kotlinx.android.parcel.Parcelize -import kotlinx.android.parcel.TypeParceler +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.TypeParceler import okhttp3.HttpUrl import okhttp3.RequestBody import org.joda.time.DateTime diff --git a/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt index 43d473f7b..52eb2544e 100644 --- a/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.CheckBox import co.omise.android.R +import co.omise.android.databinding.FragmentAtomeFormBinding import co.omise.android.extensions.setOnAfterTextChangeListener import co.omise.android.extensions.setOnClickListener import co.omise.android.models.Billing @@ -16,24 +17,6 @@ import co.omise.android.models.Item import co.omise.android.models.Shipping import co.omise.android.models.Source import co.omise.android.models.SourceType -import kotlinx.android.synthetic.main.fragment_atome_form.billing_address -import kotlinx.android.synthetic.main.fragment_atome_form.button_submit -import kotlinx.android.synthetic.main.fragment_atome_form.checkbox_billing_shipping -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_city -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_country -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_postal -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_street -import kotlinx.android.synthetic.main.fragment_atome_form.edit_email -import kotlinx.android.synthetic.main.fragment_atome_form.edit_full_name -import kotlinx.android.synthetic.main.fragment_atome_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_city -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_country -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_postal -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_street -import kotlinx.android.synthetic.main.fragment_atome_form.text_atome_email_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_billing_address_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_phone_number_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_shipping_address_error /** * AtomeFormFragment is the UI class for handling all Atome payment methods. @@ -41,33 +24,42 @@ import kotlinx.android.synthetic.main.fragment_atome_form.text_shipping_address_ class AtomeFormFragment : OmiseFragment() { var requester: PaymentCreatorRequester? = null - private val fullNameEdit: OmiseEditText by lazy { edit_full_name } - private val emailEdit: OmiseEditText by lazy { edit_email } - private val emailErrorText by lazy { text_atome_email_error } - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val phoneNumberErrorText by lazy { text_phone_number_error } + private var _binding: FragmentAtomeFormBinding? = null + private val binding get() = _binding!! - private val shippingStreetEdit by lazy { edit_shipping_street } - private val shippingPostalEdit by lazy { edit_shipping_postal } - private val shippingCityEdit by lazy { edit_shipping_city } - private val shippingCountryEdit by lazy { edit_shipping_country } - private val shippingAddressErrorText by lazy { text_shipping_address_error } + private val fullNameEdit: OmiseEditText get() = binding.editFullName + private val emailEdit: OmiseEditText get() = binding.editEmail + private val emailErrorText get() = binding.textAtomeEmailError + private val phoneNumberEdit: OmiseEditText get() = binding.editPhoneNumber + private val phoneNumberErrorText get() = binding.textPhoneNumberError - private val billingStreetEdit by lazy { edit_billing_street } - private val billingPostalEdit by lazy { edit_billing_postal } - private val billingCityEdit by lazy { edit_billing_city } - private val billingCountryEdit by lazy { edit_billing_country } - private val billingAddressErrorText by lazy { text_billing_address_error } + private val shippingStreetEdit get() = binding.editShippingStreet + private val shippingPostalEdit get() = binding.editShippingPostal + private val shippingCityEdit get() = binding.editShippingCity + private val shippingCountryEdit get() = binding.editShippingCountry + private val shippingAddressErrorText get() = binding.textShippingAddressError - private val checkBoxBillingShipping by lazy { checkbox_billing_shipping } - private val submitButton: Button by lazy { button_submit } + private val billingStreetEdit get() = binding.editBillingStreet + private val billingPostalEdit get() = binding.editBillingPostal + private val billingCityEdit get() = binding.editBillingCity + private val billingCountryEdit get() = binding.editBillingCountry + private val billingAddressErrorText get() = binding.textBillingAddressError + + private val checkBoxBillingShipping get() = binding.checkboxBillingShipping + private val submitButton: Button get() = binding.buttonSubmit override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_atome_form, container, false) + ): View { + _binding = FragmentAtomeFormBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( @@ -204,9 +196,9 @@ class AtomeFormFragment : OmiseFragment() { when (view.id) { R.id.checkbox_billing_shipping -> { if (checked) { - billing_address.visibility = GONE + binding.billingAddress.visibility = GONE } else { - billing_address.visibility = VISIBLE + binding.billingAddress.visibility = VISIBLE } } } diff --git a/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentActivity.kt b/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentActivity.kt index 66e9ae107..2461b799e 100644 --- a/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentActivity.kt @@ -32,12 +32,12 @@ import co.omise.android.OmiseException import co.omise.android.R import co.omise.android.config.UiCustomization import co.omise.android.config.UiCustomizationType +import co.omise.android.databinding.ActivityAuthorizingPaymentBinding import co.omise.android.extensions.parcelable import co.omise.android.models.Authentication import co.omise.android.ui.AuthorizingPaymentResult.Failure import co.omise.android.ui.AuthorizingPaymentResult.ThreeDS1Completed import co.omise.android.ui.AuthorizingPaymentResult.ThreeDS2Completed -import kotlinx.android.synthetic.main.activity_authorizing_payment.authorizing_payment_webview import org.jetbrains.annotations.TestOnly /** @@ -47,7 +47,7 @@ import org.jetbrains.annotations.TestOnly * app by default but the Intent callback needs to be handled by the implementer. */ class AuthorizingPaymentActivity : OmiseActivity() { - private val webView: WebView by lazy { authorizing_payment_webview } + private lateinit var binding: ActivityAuthorizingPaymentBinding private val verifier: AuthorizingPaymentURLVerifier by lazy { AuthorizingPaymentURLVerifier(intent) } private val uiCustomization: UiCustomization by lazy { intent.parcelable(EXTRA_UI_CUSTOMIZATION) ?: UiCustomization.default } private lateinit var threeDSRequestorAppURL: String @@ -67,11 +67,15 @@ class AuthorizingPaymentActivity : OmiseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // Enable View Binding + binding = ActivityAuthorizingPaymentBinding.inflate(layoutInflater) + setContentView(binding.root) + if (intent.getBooleanExtra(OmiseActivity.EXTRA_IS_SECURE, true)) { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } - setContentView(R.layout.activity_authorizing_payment) setupActionBarTitle() externalActivityLauncher = registerForActivityResult( @@ -141,8 +145,8 @@ class AuthorizingPaymentActivity : OmiseActivity() { // onPageStarted callback which should not have an impact on the logic. // More info at https://github.com/delight-im/Android-AdvancedWebView/issues/279 @TestOnly - fun setTestWebView(): WebViewClient { - val client = + fun setTestWebView() { + binding.authorizingPaymentWebview.webViewClient = object : WebViewClient() { override fun onPageStarted( view: WebView?, @@ -158,30 +162,30 @@ class AuthorizingPaymentActivity : OmiseActivity() { } } } - webView.webViewClient = client - return client } private fun setupWebViewClient() { - webView.webViewClient = + binding.authorizingPaymentWebview.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView, request: WebResourceRequest, ): Boolean { val uri = request.url - return if (verifier.verifyURL(uri)) { - finishActivityWithSuccessful(uri.toString()) - true - } else if (verifier.verifyExternalURL(uri)) { - openDeepLink(uri) - true - } else { - false + return when { + verifier.verifyURL(uri) -> { + finishActivityWithSuccessful(uri.toString()) + true + } + verifier.verifyExternalURL(uri) -> { + openDeepLink(uri) + true + } + else -> false } } } - webView.webChromeClient = + binding.authorizingPaymentWebview.webChromeClient = object : WebChromeClient() { override fun onJsAlert( view: WebView?, @@ -206,7 +210,7 @@ class AuthorizingPaymentActivity : OmiseActivity() { AlertDialog.Builder(this@AuthorizingPaymentActivity) .setMessage(message) .setPositiveButton(android.R.string.ok) { _, _ -> result?.confirm() } - .setNegativeButton(android.R.string.cancel) { _, _ -> result?.confirm() } + .setNegativeButton(android.R.string.cancel) { _, _ -> result?.cancel() } .setOnCancelListener { result?.cancel() } .show() return true @@ -329,29 +333,27 @@ class AuthorizingPaymentActivity : OmiseActivity() { } override fun onDestroy() { - // Cleanup WebView - webView.clearCache(true) - webView.clearHistory() - val cookieManager = CookieManager.getInstance() - cookieManager.removeAllCookies(null) - cookieManager.flush() - + binding.authorizingPaymentWebview.clearCache(true) + binding.authorizingPaymentWebview.clearHistory() + CookieManager.getInstance().apply { + removeAllCookies(null) + flush() + } super.onDestroy() } private fun setupWebView() { isWebViewSetup = true setupWebViewClient() - with(webView.settings) { + with(binding.authorizingPaymentWebview.settings) { javaScriptEnabled = true domStorageEnabled = true databaseEnabled = true } - runOnUiThread { if (verifier.isReady) { - webView.visibility = View.VISIBLE - webView.loadUrl(verifier.authorizedURLString) + binding.authorizingPaymentWebview.visibility = View.VISIBLE + binding.authorizingPaymentWebview.loadUrl(verifier.authorizedURLString) } } } @@ -369,10 +371,7 @@ class AuthorizingPaymentActivity : OmiseActivity() { private fun finishActivityWithSuccessful(status: TransactionStatus) { val resultIntent = Intent().apply { - putExtra( - EXTRA_AUTHORIZING_PAYMENT_RESULT, - ThreeDS2Completed(status), - ) + putExtra(EXTRA_AUTHORIZING_PAYMENT_RESULT, ThreeDS2Completed(status)) } setResult(Activity.RESULT_OK, resultIntent) finish() diff --git a/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentResult.kt b/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentResult.kt index d0843ae80..dcf14ed41 100644 --- a/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentResult.kt +++ b/sdk/src/main/java/co/omise/android/ui/AuthorizingPaymentResult.kt @@ -1,7 +1,7 @@ package co.omise.android.ui import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize /** * The result from the [AuthorizingPaymentResult]. diff --git a/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt b/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt index 94eaa6d43..004c958ea 100644 --- a/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt +++ b/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt @@ -1,13 +1,11 @@ package co.omise.android.ui import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.TextView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView.ViewHolder -import co.omise.android.R +import co.omise.android.databinding.ListCountryItemBinding import co.omise.android.models.CountryInfo internal class CountryListAdapter(private val onClick: (CountryInfo) -> Unit) : @@ -16,8 +14,8 @@ internal class CountryListAdapter(private val onClick: (CountryInfo) -> Unit) : parent: ViewGroup, viewType: Int, ): CountryViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.list_country_item, parent, false) - return CountryViewHolder(view, onClick) + val binding = ListCountryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return CountryViewHolder(binding, onClick) } override fun onBindViewHolder( @@ -28,12 +26,11 @@ internal class CountryListAdapter(private val onClick: (CountryInfo) -> Unit) : holder.bind(country) } - class CountryViewHolder(itemView: View, val onClick: (CountryInfo) -> Unit) : ViewHolder(itemView) { + class CountryViewHolder(val binding: ListCountryItemBinding, val onClick: (CountryInfo) -> Unit) : ViewHolder(binding.root) { private var currentCountry: CountryInfo? = null - private val titleTextView: TextView = itemView.findViewById(R.id.text_item_title) init { - itemView.setOnClickListener { + binding.root.setOnClickListener { currentCountry?.let { onClick(it) } @@ -42,7 +39,7 @@ internal class CountryListAdapter(private val onClick: (CountryInfo) -> Unit) : fun bind(country: CountryInfo) { currentCountry = country - titleTextView.text = country.name + binding.textItemTitle.text = country.name } } } diff --git a/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt b/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt index 18261ad06..e68ac4a59 100644 --- a/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt @@ -10,9 +10,8 @@ import androidx.appcompat.widget.Toolbar import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.RecyclerView import co.omise.android.R +import co.omise.android.databinding.DialogCountryListBinding import co.omise.android.models.CountryInfo -import kotlinx.android.synthetic.main.dialog_country_list.country_list -import kotlinx.android.synthetic.main.dialog_country_list.toolbar_country_list import java.text.Collator /** @@ -26,8 +25,11 @@ class CountryListDialogFragment : DialogFragment() { fun onCountrySelected(country: CountryInfo) } - private val listView: RecyclerView by lazy { country_list } - private val toolbar: Toolbar by lazy { toolbar_country_list } + private var _binding: DialogCountryListBinding? = null + private val binding get() = _binding!! + + private val listView: RecyclerView get() = binding.countryList + private val toolbar: Toolbar get() = binding.toolbarCountryList var listener: CountryListDialogListener? = null @@ -39,8 +41,14 @@ class CountryListDialogFragment : DialogFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.dialog_country_list, container) + ): View { + _binding = DialogCountryListBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( @@ -49,7 +57,17 @@ class CountryListDialogFragment : DialogFragment() { ) { super.onViewCreated(view, savedInstanceState) - dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + dialog?.window?.let { window -> + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + androidx.core.view.WindowCompat.setDecorFitsSystemWindows(window, false) + } + + androidx.core.view.ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> + val systemBars = insets.getInsets(androidx.core.view.WindowInsetsCompat.Type.systemBars()) + v.setPadding(0, systemBars.top, 0, 0) + insets + } + toolbar.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.close_menu -> dismiss() diff --git a/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt b/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt index 38cf0f88b..e2395221f 100644 --- a/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt @@ -8,11 +8,7 @@ import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.WindowManager -import android.widget.Button import android.widget.EditText -import android.widget.ImageButton -import android.widget.LinearLayout -import android.widget.ScrollView import android.widget.TextView import androidx.activity.OnBackPressedCallback import co.omise.android.CardNumber @@ -20,6 +16,7 @@ import co.omise.android.R import co.omise.android.api.Client import co.omise.android.api.Request import co.omise.android.api.RequestListener +import co.omise.android.databinding.ActivityCreditCardBinding import co.omise.android.extensions.getMessageFromResources import co.omise.android.extensions.parcelable import co.omise.android.extensions.setOnAfterTextChangeListener @@ -37,34 +34,6 @@ import co.omise.android.models.Source import co.omise.android.models.Token import co.omise.android.models.backendType import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_credit_card.billing_address_container -import kotlinx.android.synthetic.main.activity_credit_card.button_security_code_tooltip -import kotlinx.android.synthetic.main.activity_credit_card.button_submit -import kotlinx.android.synthetic.main.activity_credit_card.edit_card_name -import kotlinx.android.synthetic.main.activity_credit_card.edit_card_number -import kotlinx.android.synthetic.main.activity_credit_card.edit_city -import kotlinx.android.synthetic.main.activity_credit_card.edit_country -import kotlinx.android.synthetic.main.activity_credit_card.edit_email -import kotlinx.android.synthetic.main.activity_credit_card.edit_expiry_date -import kotlinx.android.synthetic.main.activity_credit_card.edit_phone_number -import kotlinx.android.synthetic.main.activity_credit_card.edit_postal_code -import kotlinx.android.synthetic.main.activity_credit_card.edit_security_code -import kotlinx.android.synthetic.main.activity_credit_card.edit_state -import kotlinx.android.synthetic.main.activity_credit_card.edit_street1 -import kotlinx.android.synthetic.main.activity_credit_card.scrollview -import kotlinx.android.synthetic.main.activity_credit_card.text_card_name_error -import kotlinx.android.synthetic.main.activity_credit_card.text_card_number_error -import kotlinx.android.synthetic.main.activity_credit_card.text_city_error -import kotlinx.android.synthetic.main.activity_credit_card.text_country_error -import kotlinx.android.synthetic.main.activity_credit_card.text_email_error -import kotlinx.android.synthetic.main.activity_credit_card.text_email_title -import kotlinx.android.synthetic.main.activity_credit_card.text_expiry_date_error -import kotlinx.android.synthetic.main.activity_credit_card.text_phone_number_error -import kotlinx.android.synthetic.main.activity_credit_card.text_phone_number_title -import kotlinx.android.synthetic.main.activity_credit_card.text_postal_code_error -import kotlinx.android.synthetic.main.activity_credit_card.text_security_code_error -import kotlinx.android.synthetic.main.activity_credit_card.text_state_error -import kotlinx.android.synthetic.main.activity_credit_card.text_street1_error import org.jetbrains.annotations.TestOnly import java.io.IOError import java.util.Locale @@ -76,38 +45,7 @@ class CreditCardActivity : OmiseActivity() { private lateinit var pKey: String private lateinit var cardHolderData: CardHolderDataList private lateinit var client: Client - private val cardNumberEdit: CreditCardEditText by lazy { edit_card_number } - private val cardNameEdit: CardNameEditText by lazy { edit_card_name } - private val expiryDateEdit: ExpiryDateEditText by lazy { edit_expiry_date } - private val securityCodeEdit: SecurityCodeEditText by lazy { edit_security_code } - private val countryEdit: OmiseEditText by lazy { edit_country } - private val street1Edit: OmiseEditText by lazy { edit_street1 } - private val cityEdit: OmiseEditText by lazy { edit_city } - private val stateEdit: OmiseEditText by lazy { edit_state } - private val postalCodeEdit: OmiseEditText by lazy { edit_postal_code } - - private val submitButton: Button by lazy { button_submit } - private val scrollView: ScrollView by lazy { scrollview } - private val cardNumberErrorText: TextView by lazy { text_card_number_error } - private val cardNameErrorText: TextView by lazy { text_card_name_error } - private val expiryDateErrorText: TextView by lazy { text_expiry_date_error } - private val securityCodeErrorText: TextView by lazy { text_security_code_error } - private val countryErrorText: TextView by lazy { text_country_error } - private val street1ErrorText: TextView by lazy { text_street1_error } - private val cityErrorText: TextView by lazy { text_city_error } - private val stateErrorText: TextView by lazy { text_state_error } - private val postalCodeErrorText: TextView by lazy { text_postal_code_error } - - private val emailEdit: OmiseEditText by lazy { edit_email } - private val emailErrorText by lazy { text_email_error } - private val emailTextTitle by lazy { text_email_title } - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val phoneNumberErrorText by lazy { text_phone_number_error } - private val phoneNumberTextTitle by lazy { text_phone_number_title } - - private val securityCodeTooltipButton: ImageButton by lazy { button_security_code_tooltip } - - private val billingAddressContainer: LinearLayout by lazy { billing_address_container } + private lateinit var binding: ActivityCreditCardBinding /** * Target countries that supports AVS or the Address Verification System. @@ -117,27 +55,27 @@ class CreditCardActivity : OmiseActivity() { private val editTexts: Map by lazy { mapOf( - cardNumberEdit to cardNumberErrorText, - cardNameEdit to cardNameErrorText, - expiryDateEdit to expiryDateErrorText, - securityCodeEdit to securityCodeErrorText, - countryEdit to countryErrorText, - street1Edit to street1ErrorText, - cityEdit to cityErrorText, - stateEdit to stateErrorText, - postalCodeEdit to postalCodeErrorText, - emailEdit to emailErrorText, - phoneNumberEdit to phoneNumberErrorText, + binding.editCardNumber to binding.textCardNumberError, + binding.editCardName to binding.textCardNameError, + binding.editExpiryDate to binding.textExpiryDateError, + binding.editSecurityCode to binding.textSecurityCodeError, + binding.editCountry to binding.textCountryError, + binding.editStreet1 to binding.textStreet1Error, + binding.editCity to binding.textCityError, + binding.editState to binding.textStateError, + binding.editPostalCode to binding.textPostalCodeError, + binding.editEmail to binding.textEmailError, + binding.editPhoneNumber to binding.textPhoneNumberError, ) } private val billingAddressEditTexts: Map by lazy { mapOf( - countryEdit to countryErrorText, - street1Edit to street1ErrorText, - cityEdit to cityErrorText, - stateEdit to stateErrorText, - postalCodeEdit to postalCodeErrorText, + binding.editCountry to binding.textCountryError, + binding.editStreet1 to binding.textStreet1Error, + binding.editCity to binding.textCityError, + binding.editState to binding.textStateError, + binding.editPostalCode to binding.textPostalCodeError, ) } @@ -160,7 +98,7 @@ class CreditCardActivity : OmiseActivity() { private fun updateEmailErrorText(hasFocus: Boolean) { // Clear error when field has focus (consistent with other fields) if (hasFocus) { - with(emailErrorText) { + with(binding.textEmailError) { text = "" visibility = GONE } @@ -168,22 +106,22 @@ class CreditCardActivity : OmiseActivity() { } // When field loses focus, validate only if not empty - if (emailEdit.text?.isEmpty() == true || isEmailValid(emailEdit)) { - with(emailErrorText) { + if (binding.editEmail.text?.isEmpty() == true || isEmailValid(binding.editEmail)) { + with(binding.textEmailError) { text = "" visibility = GONE } return } - emailErrorText.visibility = VISIBLE - emailErrorText.text = getString(R.string.error_invalid_email) + binding.textEmailError.visibility = VISIBLE + binding.textEmailError.text = getString(R.string.error_invalid_email) } private fun updatePhoneErrorText(hasFocus: Boolean) { // Clear error when field has focus (consistent with other fields) if (hasFocus) { - with(phoneNumberErrorText) { + with(binding.textPhoneNumberError) { text = "" visibility = GONE } @@ -191,16 +129,16 @@ class CreditCardActivity : OmiseActivity() { } // When field loses focus, validate only if not empty - if (phoneNumberEdit.text?.isEmpty() == true || isPhoneNumberValid(phoneNumberEdit)) { - with(phoneNumberErrorText) { + if (binding.editPhoneNumber.text?.isEmpty() == true || isPhoneNumberValid(binding.editPhoneNumber)) { + with(binding.textPhoneNumberError) { text = "" visibility = GONE } return } - phoneNumberErrorText.visibility = VISIBLE - phoneNumberErrorText.text = getString(R.string.error_invalid_phone_number) + binding.textPhoneNumberError.visibility = VISIBLE + binding.textPhoneNumberError.text = getString(R.string.error_invalid_phone_number) } override fun onCreate(savedInstanceState: Bundle?) { @@ -210,7 +148,8 @@ class CreditCardActivity : OmiseActivity() { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } - setContentView(R.layout.activity_credit_card) + binding = ActivityCreditCardBinding.inflate(layoutInflater) + setContentView(binding.root) require(intent.hasExtra(EXTRA_PKEY)) { "Could not find ${::EXTRA_PKEY.name}." } pKey = requireNotNull(intent.getStringExtra(EXTRA_PKEY)) { "${::EXTRA_PKEY.name} must not be null." } @@ -234,16 +173,16 @@ class CreditCardActivity : OmiseActivity() { private fun EditText.getErrorMessage(): String? { return when (this) { - cardNumberEdit -> getString(R.string.error_invalid_card_number) - cardNameEdit -> getString(R.string.error_invalid_card_name) - expiryDateEdit -> getString(R.string.error_invalid_expiration_date) - securityCodeEdit -> getString(R.string.error_invalid_security_code) - street1Edit -> getString(R.string.error_required_street1) - cityEdit -> getString(R.string.error_required_city) - stateEdit -> getString(R.string.error_required_state) - postalCodeEdit -> getString(R.string.error_required_postal_code) - emailEdit -> getString(R.string.error_invalid_email) - phoneNumberEdit -> getString(R.string.error_invalid_phone_number) + binding.editCardNumber -> getString(R.string.error_invalid_card_number) + binding.editCardName -> getString(R.string.error_invalid_card_name) + binding.editExpiryDate -> getString(R.string.error_invalid_expiration_date) + binding.editSecurityCode -> getString(R.string.error_invalid_security_code) + binding.editStreet1 -> getString(R.string.error_required_street1) + binding.editCity -> getString(R.string.error_required_city) + binding.editState -> getString(R.string.error_required_state) + binding.editPostalCode -> getString(R.string.error_required_postal_code) + binding.editEmail -> getString(R.string.error_invalid_email) + binding.editPhoneNumber -> getString(R.string.error_invalid_phone_number) else -> null } } @@ -251,19 +190,19 @@ class CreditCardActivity : OmiseActivity() { private fun initialize() { setTitle(R.string.default_form_title) supportActionBar?.setDisplayHomeAsUpEnabled(true) - submitButton.setOnClickListener(::submit) - securityCodeTooltipButton.setOnClickListener(::showSecurityCodeTooltipDialog) - countryEdit.setOnClickListener(::showCountryDropdownDialog) + binding.buttonSubmit.setOnClickListener(::submit) + binding.buttonSecurityCodeTooltip.setOnClickListener(::showSecurityCodeTooltipDialog) + binding.editCountry.setOnClickListener(::showCountryDropdownDialog) cardHolderDataVisibility() editTexts.forEach { (editText, errorText) -> editText.setOnFocusChangeListener { _, hasFocus -> // Handle card holder data fields (email and phone) with their special logic - if (editText == emailEdit && cardHolderData.fields.contains(CardHolderDataField.EMAIL)) { + if (editText == binding.editEmail && cardHolderData.fields.contains(CardHolderDataField.EMAIL)) { updateEmailErrorText(hasFocus) - } else if (editText == phoneNumberEdit && cardHolderData.fields.contains(CardHolderDataField.PHONE_NUMBER)) { + } else if (editText == binding.editPhoneNumber && cardHolderData.fields.contains(CardHolderDataField.PHONE_NUMBER)) { updatePhoneErrorText(hasFocus) - } else if (editText != emailEdit && editText != phoneNumberEdit) { + } else if (editText != binding.editEmail && editText != binding.editPhoneNumber) { // Handle regular fields - skip email and phone as they have special handling if (!hasFocus) { try { @@ -314,7 +253,7 @@ class CreditCardActivity : OmiseActivity() { } override fun onRequestFailed(throwable: Throwable) { - Snackbar.make(scrollView, throwable.message.toString(), Snackbar.LENGTH_LONG).show() + Snackbar.make(binding.scrollview, throwable.message.toString(), Snackbar.LENGTH_LONG).show() } }, ) @@ -340,7 +279,7 @@ class CreditCardActivity : OmiseActivity() { private fun setFormEnabled(enabled: Boolean) { editTexts.forEach { (editText, _) -> editText.isEnabled = enabled } - submitButton.isEnabled = enabled + binding.buttonSubmit.isEnabled = enabled } private fun handleRequestFailed(throwable: Throwable) { @@ -353,7 +292,7 @@ class CreditCardActivity : OmiseActivity() { else -> getString(R.string.error_unknown, throwable.message) } - Snackbar.make(scrollView, message, Snackbar.LENGTH_LONG).show() + Snackbar.make(binding.scrollview, message, Snackbar.LENGTH_LONG).show() } private fun submit() { @@ -361,18 +300,18 @@ class CreditCardActivity : OmiseActivity() { val cardParam = CardParam( - name = cardNameEdit.cardName, - number = cardNumberEdit.cardNumber, - expirationMonth = expiryDateEdit.expiryMonth, - expirationYear = expiryDateEdit.expiryYear, - securityCode = securityCodeEdit.securityCode, + name = binding.editCardName.cardName, + number = binding.editCardNumber.cardNumber, + expirationMonth = binding.editExpiryDate.expiryMonth, + expirationYear = binding.editExpiryDate.expiryYear, + securityCode = binding.editSecurityCode.securityCode, country = selectedCountry?.code, - street1 = street1Edit.textOrNull?.toString(), - city = cityEdit.textOrNull?.toString(), - state = stateEdit.textOrNull?.toString(), - postalCode = postalCodeEdit.textOrNull?.toString(), - email = emailEdit.textOrNull?.toString(), - phoneNumber = phoneNumberEdit.textOrNull?.toString(), + street1 = binding.editStreet1.textOrNull?.toString(), + city = binding.editCity.textOrNull?.toString(), + state = binding.editState.textOrNull?.toString(), + postalCode = binding.editPostalCode.textOrNull?.toString(), + email = binding.editEmail.textOrNull?.toString(), + phoneNumber = binding.editPhoneNumber.textOrNull?.toString(), ) val request = Token.CreateTokenRequestBuilder(cardParam).build() @@ -405,10 +344,12 @@ class CreditCardActivity : OmiseActivity() { request, object : RequestListener { override fun onRequestSucceed(model: Token) { - val data = Intent() - data.putExtra(EXTRA_TOKEN, model.id) - data.putExtra(EXTRA_TOKEN_OBJECT, model) - data.putExtra(EXTRA_CARD_OBJECT, model.card) + val data = + Intent().apply { + putExtra(EXTRA_TOKEN, model.id) + putExtra(EXTRA_TOKEN_OBJECT, model) + putExtra(EXTRA_CARD_OBJECT, model.card) + } if (sourceRequest == null) { setResult(Activity.RESULT_OK, data) finish() @@ -418,7 +359,9 @@ class CreditCardActivity : OmiseActivity() { sourceRequest, object : RequestListener { override fun onRequestSucceed(model: Source) { - data.putExtra(EXTRA_SOURCE_OBJECT, model) + data.apply { + putExtra(EXTRA_SOURCE_OBJECT, model) + } setResult(Activity.RESULT_OK, data) finish() } @@ -444,7 +387,7 @@ class CreditCardActivity : OmiseActivity() { editTexts.keys.forEach { editText -> when { // Required fields (card info) - editText in listOf(cardNumberEdit, cardNameEdit, expiryDateEdit, securityCodeEdit) -> { + editText in listOf(binding.editCardNumber, binding.editCardName, binding.editExpiryDate, binding.editSecurityCode) -> { validationResults[editText] = editText.isValid } // Billing address fields (only required for AVS countries) @@ -455,12 +398,12 @@ class CreditCardActivity : OmiseActivity() { // If billing address not required, these fields don't affect validation } // Email field (required when requested by merchant) - editText == emailEdit && cardHolderData.fields.contains(CardHolderDataField.EMAIL) -> { - validationResults[editText] = isEmailValid(emailEdit) + editText == binding.editEmail && cardHolderData.fields.contains(CardHolderDataField.EMAIL) -> { + validationResults[editText] = isEmailValid(binding.editEmail) } // Phone number field (required when requested by merchant) - editText == phoneNumberEdit && cardHolderData.fields.contains(CardHolderDataField.PHONE_NUMBER) -> { - validationResults[editText] = isPhoneNumberValid(phoneNumberEdit) + editText == binding.editPhoneNumber && cardHolderData.fields.contains(CardHolderDataField.PHONE_NUMBER) -> { + validationResults[editText] = isPhoneNumberValid(binding.editPhoneNumber) } // Other fields that are not visible/required don't affect validation } @@ -468,11 +411,11 @@ class CreditCardActivity : OmiseActivity() { // All included fields must be valid val isFormValid = validationResults.values.all { it } - submitButton.isEnabled = isFormValid + binding.buttonSubmit.isEnabled = isFormValid } private fun showSecurityCodeTooltipDialog() { - val brand = CardNumber.brand(cardNumberEdit.cardNumber) + val brand = CardNumber.brand(binding.editCardNumber.cardNumber) val dialog = SecurityCodeTooltipDialogFragment.newInstant(brand) dialog.show(supportFragmentManager, null) } @@ -489,10 +432,10 @@ class CreditCardActivity : OmiseActivity() { } private fun invalidateBillingAddressForm() { - countryEdit.setText(selectedCountry?.name) - billingAddressContainer.visibility = if (isBillingAddressRequired()) View.VISIBLE else View.GONE + binding.editCountry.setText(selectedCountry?.name) + binding.billingAddressContainer.visibility = if (isBillingAddressRequired()) View.VISIBLE else View.GONE billingAddressEditTexts.forEach { (editText, errorText) -> - if (editText != countryEdit) { + if (editText != binding.editCountry) { editText.text = null errorText.text = null } @@ -502,21 +445,21 @@ class CreditCardActivity : OmiseActivity() { private fun cardHolderDataVisibility() { if (cardHolderData.fields.contains(CardHolderDataField.EMAIL)) { - emailEdit.visibility = View.VISIBLE - emailTextTitle.visibility = View.VISIBLE + binding.editEmail.visibility = View.VISIBLE + binding.textEmailTitle.visibility = View.VISIBLE } else { - emailEdit.visibility = View.GONE - emailTextTitle.visibility = View.GONE - emailErrorText.visibility = View.GONE + binding.editEmail.visibility = View.GONE + binding.textEmailTitle.visibility = View.GONE + binding.textEmailError.visibility = View.GONE } if (cardHolderData.fields.contains(CardHolderDataField.PHONE_NUMBER)) { - phoneNumberEdit.visibility = View.VISIBLE - phoneNumberTextTitle.visibility = View.VISIBLE + binding.editPhoneNumber.visibility = View.VISIBLE + binding.textPhoneNumberTitle.visibility = View.VISIBLE } else { - phoneNumberEdit.visibility = View.GONE - phoneNumberTextTitle.visibility = View.GONE - phoneNumberErrorText.visibility = View.GONE + binding.editPhoneNumber.visibility = View.GONE + binding.textPhoneNumberTitle.visibility = View.GONE + binding.textPhoneNumberError.visibility = View.GONE } } diff --git a/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt index 575505099..e4aa63779 100644 --- a/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt @@ -9,19 +9,13 @@ import android.view.ViewGroup import android.widget.Button import android.widget.TextView import co.omise.android.R +import co.omise.android.databinding.FragmentEcontextFormBinding import co.omise.android.extensions.getParcelableCompat import co.omise.android.extensions.setOnAfterTextChangeListener import co.omise.android.extensions.setOnClickListener import co.omise.android.models.Source import co.omise.android.models.SourceType import co.omise.android.models.SupportedEcontext -import kotlinx.android.synthetic.main.fragment_econtext_form.button_submit -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_email -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_full_name -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_econtext_form.text_email_error -import kotlinx.android.synthetic.main.fragment_econtext_form.text_full_name_error -import kotlinx.android.synthetic.main.fragment_econtext_form.text_phone_number_error /** * EContextFormFragment is the UI class for handling all EContext payment methods. @@ -29,30 +23,38 @@ import kotlinx.android.synthetic.main.fragment_econtext_form.text_phone_number_e class EContextFormFragment : OmiseFragment() { var requester: PaymentCreatorRequester? = null + private var _binding: FragmentEcontextFormBinding? = null + private val binding get() = _binding!! + private val type: SupportedEcontext? by lazy { arguments?.getParcelableCompat(EXTRA_ECONTEXT_TYPE) } - private val fullNameEdit: OmiseEditText by lazy { edit_full_name } - private val emailEdit: OmiseEditText by lazy { edit_email } - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val fullNameErrorText by lazy { text_full_name_error } - private val emailErrorText by lazy { text_email_error } - private val phoneNumberErrorText by lazy { text_phone_number_error } - private val submitButton: Button by lazy { button_submit } - private val formInputWithErrorTexts: List> by lazy { + private val fullNameEdit: OmiseEditText get() = binding.editFullName + private val emailEdit: OmiseEditText get() = binding.editEmail + private val phoneNumberEdit: OmiseEditText get() = binding.editPhoneNumber + private val fullNameErrorText: TextView get() = binding.textFullNameError + private val emailErrorText: TextView get() = binding.textEmailError + private val phoneNumberErrorText: TextView get() = binding.textPhoneNumberError + private val submitButton: Button get() = binding.buttonSubmit + private val formInputWithErrorTexts: List> get() = listOf( Pair(fullNameEdit, fullNameErrorText), Pair(emailEdit, emailErrorText), Pair(phoneNumberEdit, phoneNumberErrorText), ) - } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_econtext_form, container, false) + ): View { + _binding = FragmentEcontextFormBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( @@ -60,7 +62,6 @@ class EContextFormFragment : OmiseFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - title = when (type) { SupportedEcontext.ConvenienceStore -> getString(R.string.title_convenience_store) @@ -69,12 +70,10 @@ class EContextFormFragment : OmiseFragment() { null -> getString(R.string.econtext_title) } setHasOptionsMenu(true) - formInputWithErrorTexts.forEach { it.first.setOnFocusChangeListener(::updateErrorText) it.first.setOnAfterTextChangeListener(::updateSubmitButton) } - submitButton.setOnClickListener(::submitForm) } @@ -84,13 +83,11 @@ class EContextFormFragment : OmiseFragment() { ) { val editText = view as OmiseEditText val errorText = formInputWithErrorTexts.first { it.first == editText }.second - if (hasFocus || editText.isValid) { errorText.text = "" errorText.visibility = INVISIBLE return } - errorText.text = when (editText) { fullNameEdit -> getString(R.string.error_invalid_full_name) @@ -102,30 +99,19 @@ class EContextFormFragment : OmiseFragment() { } private fun updateSubmitButton() { - val isFormValid = - formInputWithErrorTexts.map { it.first.isValid } - .reduce { acc, b -> acc && b } - submitButton.isEnabled = isFormValid + submitButton.isEnabled = formInputWithErrorTexts.map { it.first.isValid }.reduce { acc, b -> acc && b } } private fun submitForm() { val requester = requester ?: return - val fullName = fullNameEdit.text?.toString()?.trim().orEmpty() val email = emailEdit.text?.toString()?.trim().orEmpty() val phoneNumber = phoneNumberEdit.text?.toString()?.trim().orEmpty() - val request = Source.CreateSourceRequestBuilder(requester.amount, requester.currency, SourceType.Econtext) - .name(fullName) - .email(email) - .phoneNumber(phoneNumber) - .build() - + .name(fullName).email(email).phoneNumber(phoneNumber).build() view?.let { setAllViewsEnabled(it, false) } - requester.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } + requester.request(request) { view?.let { setAllViewsEnabled(it, true) } } } companion object { @@ -133,10 +119,7 @@ class EContextFormFragment : OmiseFragment() { fun newInstance(eContext: SupportedEcontext): EContextFormFragment = EContextFormFragment().apply { - arguments = - Bundle().apply { - putParcelable(EXTRA_ECONTEXT_TYPE, eContext) - } + arguments = Bundle().apply { putParcelable(EXTRA_ECONTEXT_TYPE, eContext) } } } } diff --git a/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt index f055a9215..e1b719468 100644 --- a/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt @@ -6,11 +6,10 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import co.omise.android.R +import co.omise.android.databinding.FragmentFpxEmailFormBinding import co.omise.android.extensions.setOnAfterTextChangeListener import co.omise.android.extensions.setOnClickListener import co.omise.android.models.Source -import kotlinx.android.synthetic.main.fragment_fpx_email_form.button_submit -import kotlinx.android.synthetic.main.fragment_fpx_email_form.edit_email /** * FpxEmailFormFragment is the UI class to show an email form for FPX payments. @@ -19,16 +18,25 @@ internal class FpxEmailFormFragment : OmiseFragment() { var navigation: PaymentCreatorNavigation? = null var requester: PaymentCreatorRequester? = null - private val emailEdit: OmiseEditText by lazy { edit_email } - private val submitButton: Button by lazy { button_submit } + private var _binding: FragmentFpxEmailFormBinding? = null + private val binding get() = _binding!! + + private val emailEdit: OmiseEditText get() = binding.editEmail + private val submitButton: Button get() = binding.buttonSubmit private val allowedEmailFormat = "\\A[\\w+\\-.]+@[a-z\\d\\-.]+\\.[a-z]{2,}\\z" override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_fpx_email_form, container, false) + ): View { + _binding = FragmentFpxEmailFormBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( diff --git a/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt b/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt index 5fc014af8..15e91668e 100644 --- a/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting import co.omise.android.R import co.omise.android.api.Client import co.omise.android.api.RequestListener +import co.omise.android.databinding.ActivityGooglePayBinding import co.omise.android.extensions.getMessageFromResources import co.omise.android.models.APIError import co.omise.android.models.Token @@ -23,7 +24,6 @@ import com.google.android.gms.wallet.IsReadyToPayRequest import com.google.android.gms.wallet.PaymentData import com.google.android.gms.wallet.PaymentDataRequest import com.google.android.gms.wallet.PaymentsClient -import kotlinx.android.synthetic.main.activity_google_pay.googlePayButton import org.json.JSONException import org.json.JSONObject import java.io.IOError @@ -31,6 +31,7 @@ import java.io.IOError class GooglePayActivity : OmiseActivity() { private lateinit var pKey: String private lateinit var googlePay: GooglePay + private lateinit var binding: ActivityGooglePayBinding @VisibleForTesting internal lateinit var paymentsClient: PaymentsClient @@ -55,7 +56,8 @@ class GooglePayActivity : OmiseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_google_pay) + binding = ActivityGooglePayBinding.inflate(layoutInflater) + setContentView(binding.root) onBackPressedDispatcher.addCallback(this, onBackPressedCallback) @@ -81,7 +83,7 @@ class GooglePayActivity : OmiseActivity() { possiblyShowGooglePayButton() - googlePayButton.setOnClickListener { requestPayment() } + binding.googlePayButton.setOnClickListener { requestPayment() } } private fun initialize() { @@ -154,7 +156,7 @@ class GooglePayActivity : OmiseActivity() { @VisibleForTesting internal fun setGooglePayAvailable(available: Boolean) { if (available) { - googlePayButton.visibility = View.VISIBLE + binding.googlePayButton.visibility = View.VISIBLE } else { Toast.makeText( this, @@ -168,7 +170,7 @@ class GooglePayActivity : OmiseActivity() { @VisibleForTesting internal fun requestPayment() { // Disables the button to prevent multiple clicks. - googlePayButton.isClickable = false + binding.googlePayButton.isClickable = false val paymentDataRequestJson = googlePay.getPaymentDataRequest() if (paymentDataRequestJson == null) { @@ -225,7 +227,7 @@ class GooglePayActivity : OmiseActivity() { } // Re-enables the Google Pay payment button. - googlePayButton.isClickable = true + binding.googlePayButton.isClickable = true } } } @@ -295,7 +297,7 @@ class GooglePayActivity : OmiseActivity() { billingPhoneNumber = billingPhoneNumber, ) - googlePayButton.isClickable = false + binding.googlePayButton.isClickable = false val request = Token.CreateTokenRequestBuilder(tokenization = tokenParam).build() @@ -329,10 +331,12 @@ class GooglePayActivity : OmiseActivity() { @VisibleForTesting internal inner class CreateTokenRequestListener : RequestListener { override fun onRequestSucceed(model: Token) { - val data = Intent() - data.putExtra(OmiseActivity.EXTRA_TOKEN, model.id) - data.putExtra(OmiseActivity.EXTRA_TOKEN_OBJECT, model) - data.putExtra(OmiseActivity.EXTRA_CARD_OBJECT, model.card) + val data = + Intent().apply { + putExtra(OmiseActivity.EXTRA_TOKEN, model.id) + putExtra(OmiseActivity.EXTRA_TOKEN_OBJECT, model) + putExtra(OmiseActivity.EXTRA_CARD_OBJECT, model.card) + } setResult(Activity.RESULT_OK, data) finish() diff --git a/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt index 6b22f28bb..b6479e847 100644 --- a/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt @@ -2,7 +2,6 @@ package co.omise.android.ui import android.os.Bundle import android.view.View -import android.widget.LinearLayout import co.omise.android.R import co.omise.android.extensions.getParcelableArrayCompat import co.omise.android.models.BackendType @@ -74,8 +73,7 @@ internal class InstallmentChooserFragment : OmiseListFragment(R.id.message_layout) - noBanksMessageLayOut?.visibility = View.VISIBLE + messageLayout.visibility = View.VISIBLE } override fun onListItemClicked(item: InstallmentResource) { diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseActivity.kt b/sdk/src/main/java/co/omise/android/ui/OmiseActivity.kt index e66795d3e..6cfddc165 100644 --- a/sdk/src/main/java/co/omise/android/ui/OmiseActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/OmiseActivity.kt @@ -3,7 +3,6 @@ package co.omise.android.ui import android.content.Intent import android.os.Bundle import android.view.View -import android.view.ViewTreeObserver import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat @@ -52,24 +51,23 @@ abstract class OmiseActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) } - override fun setContentView(layoutResID: Int) { - super.setContentView(layoutResID) - setupInsetsHandling() + // Override the View version for View Binding support + override fun setContentView(view: View?) { + super.setContentView(view) + handleWindowInsets() } - /** - * Sets up insets handling after the content view is available - */ - private fun setupInsetsHandling() { - val decorView = window.decorView - decorView.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - decorView.viewTreeObserver.removeOnGlobalLayoutListener(this) - handleWindowInsets() - } - }, - ) + override fun setContentView( + view: View?, + params: android.view.ViewGroup.LayoutParams?, + ) { + super.setContentView(view, params) + handleWindowInsets() + } + + override fun setContentView(layoutResID: Int) { + super.setContentView(layoutResID) + handleWindowInsets() } /** @@ -77,24 +75,21 @@ abstract class OmiseActivity : AppCompatActivity() { * Child activities can override this method for custom inset handling. */ protected open fun handleWindowInsets() { - val decorView = window.decorView - val rootView = decorView.findViewById(android.R.id.content) + val rootView = findViewById(android.R.id.content) ?: return - ViewCompat.setOnApplyWindowInsetsListener(decorView) { _, insets -> + ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - if (systemBars.top > 0 || systemBars.bottom > 0 || systemBars.left > 0 || systemBars.right > 0) { - rootView?.setPadding( - systemBars.left, - systemBars.top, - systemBars.right, - systemBars.bottom, - ) - } + v.setPadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom, + ) insets } // Force immediate application of insets - ViewCompat.requestApplyInsets(decorView) + ViewCompat.requestApplyInsets(rootView) } @VisibleForTesting diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt b/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt index fccf68397..7e562e2a9 100644 --- a/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt @@ -15,15 +15,28 @@ import androidx.fragment.app.Fragment */ abstract class OmiseFragment : Fragment() { var title: String? = null + set(value) { + field = value + if (isResumed) { + actionBar?.title = value + } + } private val actionBar: ActionBar? get() = (activity as? AppCompatActivity)?.supportActionBar + override fun onResume() { + super.onResume() + actionBar?.title = title + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) parentFragmentManager.addOnBackStackChangedListener { - actionBar?.title = title + if (isVisible) { + actionBar?.title = title + } } } diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt b/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt index 2a8b67638..4a07d978d 100644 --- a/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt @@ -8,16 +8,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_POSITION import co.omise.android.R -import kotlinx.android.synthetic.main.fragment_list.no_data_text -import kotlinx.android.synthetic.main.fragment_list.recycler_view +import co.omise.android.databinding.FragmentListBinding +import co.omise.android.databinding.ListItemBinding /** * OmiseListFragment is the base class for all list-based UI classes. @@ -27,8 +24,12 @@ abstract class OmiseListFragment : OmiseFragment() { abstract fun listItems(): List - protected val noDataText: TextView by lazy { no_data_text } - private val recyclerView: RecyclerView by lazy { recycler_view } + private var _binding: FragmentListBinding? = null + private val binding get() = _binding!! + + protected val noDataText get() = binding.noDataText + protected val messageLayout get() = binding.messageLayout + private val recyclerView get() = binding.recyclerView private val onClickListener = object : OmiseListItemClickListener { @@ -43,8 +44,14 @@ abstract class OmiseListFragment : OmiseFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_list, container, false) + ): View { + _binding = FragmentListBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( @@ -78,9 +85,8 @@ class OmiseListAdapter(val list: List, val listener: OmiseListIte parent: ViewGroup, viewType: Int, ): OmiseItemViewHolder { - val itemView = - LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false) - return OmiseItemViewHolder(itemView, listener) + val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return OmiseItemViewHolder(binding, listener) } override fun getItemCount(): Int = list.size @@ -94,14 +100,14 @@ class OmiseListAdapter(val list: List, val listener: OmiseListIte } } -class OmiseItemViewHolder(val view: View, val listener: OmiseListItemClickListener?) : - RecyclerView.ViewHolder(view) { +class OmiseItemViewHolder(val binding: ListItemBinding, val listener: OmiseListItemClickListener?) : + RecyclerView.ViewHolder(binding.root) { fun bind(item: OmiseListItem) { - val listItemView = view.findViewById(R.id.list_item_view) - val optionImage = view.findViewById(R.id.image_item_icon) - val nameText = view.findViewById(R.id.text_item_title) - val subtitleText = view.findViewById(R.id.subtext_item_title) - val typeImage = view.findViewById(R.id.image_indicator_icon) + val listItemView = binding.listItemView + val optionImage = binding.imageItemIcon + val nameText = binding.textItemTitle + val subtitleText = binding.subtextItemTitle + val typeImage = binding.imageIndicatorIcon if (item.iconRes != null) { optionImage.setImageResource(item.iconRes!!) @@ -110,8 +116,8 @@ class OmiseItemViewHolder(val view: View, val listener: OmiseListItemClickListen optionImage.visibility = View.GONE } - nameText.text = item.titleRes?.let { view.context.getString(it) } ?: item.title - subtitleText.text = item.subtitleRes?.let { view.context.getString(it) } ?: item.subtitle + nameText.text = item.titleRes?.let { itemView.context.getString(it) } ?: item.title + subtitleText.text = item.subtitleRes?.let { itemView.context.getString(it) } ?: item.subtitle if (item.enabled == true) { item.indicatorIconRes?.let { typeImage.setImageResource(it) } @@ -129,7 +135,7 @@ class OmiseItemViewHolder(val view: View, val listener: OmiseListItemClickListen subtitleText.visibility = View.GONE } - view.setOnClickListener { listener?.onClick(item) } + itemView.setOnClickListener { listener?.onClick(item) } } } @@ -177,7 +183,8 @@ private class OmiseItemDecoration(val context: Context) : RecyclerView.ItemDecor val params = view.layoutParams as RecyclerView.LayoutParams - val titleText = view.findViewById(R.id.text_item_title) + val binding = ListItemBinding.bind(view) + val titleText = binding.textItemTitle val left = titleText.left val right = parent.width val top = view.bottom + params.bottomMargin diff --git a/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt b/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt index 5455d8060..4935814b7 100644 --- a/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt @@ -9,12 +9,16 @@ import android.view.WindowManager import android.widget.ProgressBar import android.widget.TextView import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.VisibleForTesting import androidx.fragment.app.Fragment import co.omise.android.R import co.omise.android.api.Client import co.omise.android.api.Request import co.omise.android.api.RequestListener +import co.omise.android.databinding.ActivityPaymentCreatorBinding import co.omise.android.extensions.getMessageFromResources import co.omise.android.extensions.parcelable import co.omise.android.extensions.parcelableNullable @@ -39,7 +43,6 @@ import co.omise.android.ui.OmiseActivity.Companion.EXTRA_IS_SECURE import co.omise.android.ui.OmiseActivity.Companion.EXTRA_PKEY import co.omise.android.ui.OmiseActivity.Companion.EXTRA_SOURCE_OBJECT import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_payment_creator.payment_creator_container import org.jetbrains.annotations.TestOnly import java.io.IOError @@ -57,7 +60,8 @@ class PaymentCreatorActivity : OmiseActivity() { private var googlepayRequestBillingAddress: Boolean = false private var googlepayRequestPhoneNumber: Boolean = false private lateinit var cardHolderDataList: CardHolderDataList - private val snackbar: Snackbar by lazy { Snackbar.make(payment_creator_container, "", Snackbar.LENGTH_SHORT) } + private lateinit var binding: ActivityPaymentCreatorBinding + private val snackbar: Snackbar by lazy { Snackbar.make(binding.paymentCreatorContainer, "", Snackbar.LENGTH_SHORT) } private lateinit var client: Client @@ -71,8 +75,8 @@ class PaymentCreatorActivity : OmiseActivity() { @VisibleForTesting lateinit var navigation: PaymentCreatorNavigation - private lateinit var progressBar: ProgressBar - private lateinit var errorMessage: TextView + private lateinit var creditCardLauncher: ActivityResultLauncher + private lateinit var googlePayLauncher: ActivityResultLauncher override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -80,15 +84,17 @@ class PaymentCreatorActivity : OmiseActivity() { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } - setContentView(R.layout.activity_payment_creator) + binding = ActivityPaymentCreatorBinding.inflate(layoutInflater) + setContentView(binding.root) - progressBar = findViewById(R.id.progressBar) - errorMessage = findViewById(R.id.errorMessage) // Initially hide the ProgressBar and error message - progressBar.visibility = ProgressBar.GONE - errorMessage.visibility = TextView.GONE + binding.progressBar.visibility = ProgressBar.GONE + binding.errorMessage.visibility = TextView.GONE title = getString(R.string.payment_chooser_title) + + setupActivityLaunchers() + val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { @@ -105,6 +111,37 @@ class PaymentCreatorActivity : OmiseActivity() { loadCapability() } + private fun setupActivityLaunchers() { + creditCardLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleCreditCardResult(result) + } + + googlePayLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleCreditCardResult(result) // GooglePay returns same format + } + } + + @VisibleForTesting + internal fun handleCreditCardResult(result: ActivityResult) { + if (result.resultCode == Activity.RESULT_OK) { + val data = result.data + val token = data?.parcelable(EXTRA_TOKEN_OBJECT) + val source = data?.parcelable(EXTRA_SOURCE_OBJECT) + + val intent = + Intent().apply { + putExtra(EXTRA_TOKEN, token?.id) + putExtra(EXTRA_TOKEN_OBJECT, token) + putExtra(EXTRA_CARD_OBJECT, token?.card) + source?.let { putExtra(EXTRA_SOURCE_OBJECT, it) } + } + setResult(Activity.RESULT_OK, intent) + finish() + } + } + // Set the menu button to close the view by the user override fun onCreateOptionsMenu(menu: Menu): Boolean { if (supportFragmentManager.findFragmentById(R.id.payment_creator_container) !is PaymentChooserFragment) { @@ -126,9 +163,9 @@ class PaymentCreatorActivity : OmiseActivity() { private fun loadCapability() { // Start loading - progressBar.visibility = ProgressBar.VISIBLE + binding.progressBar.visibility = ProgressBar.VISIBLE // Hide error message - errorMessage.visibility = TextView.GONE + binding.errorMessage.visibility = TextView.GONE // Get capability val capabilityRequest = Capability.GetCapabilitiesRequestBuilder().build() client.send( @@ -140,14 +177,14 @@ class PaymentCreatorActivity : OmiseActivity() { // as new button will come from the next view invalidateOptionsMenu() // Hide loading - progressBar.visibility = ProgressBar.GONE + binding.progressBar.visibility = ProgressBar.GONE } override fun onRequestFailed(throwable: Throwable) { - progressBar.visibility = ProgressBar.GONE + binding.progressBar.visibility = ProgressBar.GONE // Show the error message - errorMessage.text = getString(R.string.error_loading_payment_methods) - errorMessage.visibility = TextView.VISIBLE + binding.errorMessage.text = getString(R.string.error_loading_payment_methods) + binding.errorMessage.visibility = TextView.VISIBLE } }, ) @@ -171,7 +208,8 @@ class PaymentCreatorActivity : OmiseActivity() { googlepayMerchantId, googlepayRequestBillingAddress, googlepayRequestPhoneNumber, - REQUEST_CREDIT_CARD, + creditCardLauncher, + googlePayLauncher, requester, newCapability, cardHolderDataList, @@ -255,40 +293,6 @@ class PaymentCreatorActivity : OmiseActivity() { return capability } - // TODO: find a way to unit test ActivityResult launcher in order to be able to move from deprecated onActivityResult - override fun onActivityResult( - requestCode: Int, - resultCode: Int, - data: Intent?, - ) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CREDIT_CARD_WITH_SOURCE && resultCode == Activity.RESULT_OK) { - val token = data?.parcelable(EXTRA_TOKEN_OBJECT) - val source = data?.parcelable(EXTRA_SOURCE_OBJECT) - val intent = - Intent().apply { - putExtra(EXTRA_TOKEN, token?.id) - putExtra(EXTRA_TOKEN_OBJECT, token) - putExtra(EXTRA_CARD_OBJECT, token?.card) - putExtra(EXTRA_SOURCE_OBJECT, source) - } - setResult(Activity.RESULT_OK, intent) - finish() - } - - if (requestCode == REQUEST_CREDIT_CARD && resultCode == Activity.RESULT_OK) { - val token = data?.parcelable(EXTRA_TOKEN_OBJECT) - val intent = - Intent().apply { - putExtra(EXTRA_TOKEN, token?.id) - putExtra(EXTRA_TOKEN_OBJECT, token) - putExtra(EXTRA_CARD_OBJECT, token?.card) - } - setResult(Activity.RESULT_OK, intent) - finish() - } - } - private fun initialize() { listOf(EXTRA_PKEY, EXTRA_AMOUNT, EXTRA_CURRENCY).forEach { require(intent.hasExtra(it)) { "Could not found $it." } @@ -354,7 +358,8 @@ private class PaymentCreatorNavigationImpl( private var googlepayMerchantId: String, private var googlepayRequestBillingAddress: Boolean, private var googlepayRequestPhoneNumber: Boolean, - private val requestCode: Int, + private val creditCardLauncher: ActivityResultLauncher, + private val googlePayLauncher: ActivityResultLauncher, private val requester: PaymentCreatorRequester, private val capability: Capability, private val cardHolderDataList: CardHolderDataList, @@ -388,7 +393,7 @@ private class PaymentCreatorNavigationImpl( putExtra(EXTRA_IS_SECURE, activity.intent.getBooleanExtra(EXTRA_IS_SECURE, true)) putExtra(EXTRA_CARD_HOLDER_DATA, cardHolderDataList) } - activity.startActivityForResult(intent, requestCode) + creditCardLauncher.launch(intent) } override fun navigateToMobileBankingChooser(allowedBanks: List) { @@ -482,7 +487,7 @@ private class PaymentCreatorNavigationImpl( putExtra(EXTRA_GOOGLEPAY_REQUEST_BILLING_ADDRESS, googlepayRequestBillingAddress) putExtra(EXTRA_GOOGLEPAY_REQUEST_PHONE_NUMBER, googlepayRequestPhoneNumber) } - activity.startActivityForResult(intent, requestCode) + googlePayLauncher.launch(intent) } override fun navigateToDuitNowOBWBankChooser(capability: Capability) { diff --git a/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt b/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt index bd4a607db..1075034f4 100644 --- a/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt @@ -11,25 +11,25 @@ import android.widget.ImageView import android.widget.TextView import androidx.fragment.app.DialogFragment import co.omise.android.R +import co.omise.android.databinding.DialogSecurityCodeTooltipBinding import co.omise.android.extensions.getParcelableCompat import co.omise.android.models.CardBrand -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.close_button -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.cvv_description_text -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.cvv_image /** * SecurityCodeTooltipDialogFragment is a UI class to show the user information about * the security code and where it is found on the card. */ class SecurityCodeTooltipDialogFragment : DialogFragment() { - private val cvvImage: ImageView by lazy { cvv_image } - private val cvvDescriptionText: TextView by lazy { cvv_description_text } - private val closeButton: ImageButton by lazy { close_button } + private var _binding: DialogSecurityCodeTooltipBinding? = null + private val binding get() = _binding!! + + private val cvvImage: ImageView get() = binding.cvvImage + private val cvvDescriptionText: TextView get() = binding.cvvDescriptionText + private val closeButton: ImageButton get() = binding.closeButton private var cardBrand: CardBrand? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - cardBrand = arguments?.getParcelableCompat(EXTRA_CARD_BRAND) } @@ -37,8 +37,14 @@ class SecurityCodeTooltipDialogFragment : DialogFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.dialog_security_code_tooltip, container) + ): View { + _binding = DialogSecurityCodeTooltipBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( @@ -46,11 +52,8 @@ class SecurityCodeTooltipDialogFragment : DialogFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - closeButton.setOnClickListener { dismiss() } - when (cardBrand) { CardBrand.AMEX -> { cvvImage.setImageResource(R.drawable.cvv_4_digits) @@ -69,10 +72,8 @@ class SecurityCodeTooltipDialogFragment : DialogFragment() { fun newInstant(brand: CardBrand? = null): SecurityCodeTooltipDialogFragment { val argument = Bundle() argument.putParcelable(EXTRA_CARD_BRAND, brand) - val dialogFragment = SecurityCodeTooltipDialogFragment() dialogFragment.arguments = argument - return dialogFragment } } diff --git a/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt index 78f050d76..d3d0e8fd6 100644 --- a/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt +++ b/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt @@ -7,13 +7,11 @@ import android.view.View.INVISIBLE import android.view.ViewGroup import android.widget.Button import co.omise.android.R +import co.omise.android.databinding.FragmentTrueMoneyFormBinding import co.omise.android.extensions.setOnAfterTextChangeListener import co.omise.android.extensions.setOnClickListener import co.omise.android.models.Source import co.omise.android.models.SourceType -import kotlinx.android.synthetic.main.fragment_true_money_form.button_submit -import kotlinx.android.synthetic.main.fragment_true_money_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_true_money_form.text_phone_number_error /** * TrueMoneyFormFragment is the UI class for handling TrueMoney payment method. @@ -21,16 +19,25 @@ import kotlinx.android.synthetic.main.fragment_true_money_form.text_phone_number class TrueMoneyFormFragment : OmiseFragment() { var requester: PaymentCreatorRequester? = null - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val phoneNumberErrorText by lazy { text_phone_number_error } - private val submitButton: Button by lazy { button_submit } + private var _binding: FragmentTrueMoneyFormBinding? = null + private val binding get() = _binding!! + + private val phoneNumberEdit: OmiseEditText get() = binding.editPhoneNumber + private val phoneNumberErrorText get() = binding.textPhoneNumberError + private val submitButton: Button get() = binding.buttonSubmit override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_true_money_form, container, false) + ): View { + _binding = FragmentTrueMoneyFormBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null } override fun onViewCreated( diff --git a/sdk/src/main/res/layout/activity_google_pay.xml b/sdk/src/main/res/layout/activity_google_pay.xml index 35fd32f76..1c01d46e7 100644 --- a/sdk/src/main/res/layout/activity_google_pay.xml +++ b/sdk/src/main/res/layout/activity_google_pay.xml @@ -1,37 +1,42 @@ - - + android:layout_height="match_parent" + android:background="@android:color/white"> - + android:layout_height="48dp" + android:layout_margin="16dp" + android:background="@drawable/googlepay_button_background" + android:padding="2dp" + android:layout_gravity="center" + android:contentDescription="@string/pay_with_googlepay_button_content_description"> - - + android:gravity="center" + android:orientation="vertical"> - - + + + + + + diff --git a/sdk/src/main/res/menu/menu_toolbar.xml b/sdk/src/main/res/menu/menu_toolbar.xml index 8055fdb12..fa8c7264b 100644 --- a/sdk/src/main/res/menu/menu_toolbar.xml +++ b/sdk/src/main/res/menu/menu_toolbar.xml @@ -5,6 +5,6 @@ android:id="@+id/close_menu" android:icon="@drawable/ic_close" android:title="@string/menu_title_close" - app:iconTint="?colorAccent" + app:iconTint="?attr/colorControlNormal" app:showAsAction="always" /> diff --git a/sdk/src/main/res/values/styles.xml b/sdk/src/main/res/values/styles.xml index 9f29a6e29..4ab0ca830 100644 --- a/sdk/src/main/res/values/styles.xml +++ b/sdk/src/main/res/values/styles.xml @@ -59,9 +59,10 @@ @dimen/list_item_text_size - diff --git a/sdk/src/test/java/co/omise/android/ParcelableTest.kt b/sdk/src/test/java/co/omise/android/ParcelableTest.kt index 7a6d011bc..8cd6627b7 100644 --- a/sdk/src/test/java/co/omise/android/ParcelableTest.kt +++ b/sdk/src/test/java/co/omise/android/ParcelableTest.kt @@ -13,7 +13,7 @@ import co.omise.android.models.PaymentMethod import co.omise.android.models.Source import co.omise.android.models.SourceType import co.omise.android.models.Token -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize import org.joda.time.DateTime import org.junit.Assert.assertEquals import org.junit.Test diff --git a/testsupport/build.gradle b/testsupport/build.gradle index e697c35af..e4e25f995 100644 --- a/testsupport/build.gradle +++ b/testsupport/build.gradle @@ -1,10 +1,9 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' android { + namespace 'co.omise.testsupport' defaultConfig { - namespace 'co.omise.testsupport' minSdkVersion min_sdk_version targetSdk target_sdk_version compileSdk compile_sdk_version