diff --git a/.circleci/config.yml b/.circleci/config.yml index 6dd5d1889..c4075d39a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,9 +10,7 @@ aliases: - &environment working_directory: ~/code docker: - - image: cimg/android:2023.06.1 - environment: - JVM_OPTS: -Xmx3200m + - image: cimg/android:2024.01 jobs: "Run_Unit_Test_And_Build": @@ -64,7 +62,7 @@ jobs: fi - run: name: Generate apk - command: ./gradlew assembleDebug assembleAndroidTest + command: ./gradlew app:assembleDebug app:assembleAndroidTest --no-daemon - persist_to_workspace: root: ~/code paths: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2ec0b2899..d27fe523a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## Feature [CSDK-###] +## Feature [DEVX-###] ## Description of changes (insert text here) diff --git a/app/build.gradle b/app/build.gradle index a0de66b6b..96ac43173 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,13 +91,13 @@ dependencies { implementation project(":vgscollect-cardio") implementation project(":vgscollect-blinkcard") - implementation 'com.google.android.gms:play-services-wallet:19.2.1' + implementation 'com.google.android.gms:play-services-wallet:19.3.0' implementation libs.androidx.appcompat implementation libs.androidx.core.ktx implementation libs.material - implementation platform('androidx.compose:compose-bom:2023.10.01') + implementation platform('androidx.compose:compose-bom:2024.02.01') implementation libs.compose.activity implementation libs.compose.material diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 76e3da980..728c5dc23 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -76,8 +76,12 @@ android:label="Instrumented Demo" /> + android:name=".compose.activity_case.ComposeActivity" + android:label="Compose Demo (Activity)" /> + + ) { diff --git a/app/src/main/java/com/verygoodsecurity/demoapp/compose/activity_case/ComposeActivity.kt b/app/src/main/java/com/verygoodsecurity/demoapp/compose/activity_case/ComposeActivity.kt new file mode 100644 index 000000000..283b2b437 --- /dev/null +++ b/app/src/main/java/com/verygoodsecurity/demoapp/compose/activity_case/ComposeActivity.kt @@ -0,0 +1,80 @@ +package com.verygoodsecurity.demoapp.compose.activity_case + +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.verygoodsecurity.demoapp.StartActivity +import com.verygoodsecurity.demoapp.compose.components.BaseCollect +import com.verygoodsecurity.demoapp.getStringExtra +import com.verygoodsecurity.vgscollect.VGSCollectLogger +import com.verygoodsecurity.vgscollect.core.VGSCollect +import com.verygoodsecurity.vgscollect.core.VgsCollectResponseListener +import com.verygoodsecurity.vgscollect.core.model.network.VGSResponse + +class ComposeActivity : AppCompatActivity(), VgsCollectResponseListener { + + private val path: String by lazy { getStringExtra(StartActivity.KEY_BUNDLE_PATH) } + + private val collect: VGSCollect by lazy { + VGSCollect( + this, + getStringExtra(StartActivity.KEY_BUNDLE_VAULT_ID, ""), + getStringExtra(StartActivity.KEY_BUNDLE_ENVIRONMENT, "") + ).also { + it.addOnResponseListeners(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + VGSCollectLogger.logLevel = VGSCollectLogger.Level.DEBUG + VGSCollectLogger.isEnabled = true + setContent { + Content( + collect = collect, + path = path + ) + } + } + + override fun onResponse(response: VGSResponse?) { + Toast.makeText( + this, + when (response) { + is VGSResponse.SuccessResponse -> "Success" + is VGSResponse.ErrorResponse -> "Error" + else -> throw IllegalStateException() + }, + Toast.LENGTH_SHORT + ).show() + + Log.d("VGS", "response = ${response.body}") + } +} + +@Composable +private fun Content( + collect: VGSCollect?, + path: String +) { + MaterialTheme { + BaseCollect( + collect = collect, + path = path + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun Preview() { + Content( + collect = null, + path = "" + ) +} diff --git a/app/src/main/java/com/verygoodsecurity/demoapp/compose/components/BaseCollect.kt b/app/src/main/java/com/verygoodsecurity/demoapp/compose/components/BaseCollect.kt new file mode 100644 index 000000000..722afae52 --- /dev/null +++ b/app/src/main/java/com/verygoodsecurity/demoapp/compose/components/BaseCollect.kt @@ -0,0 +1,137 @@ +package com.verygoodsecurity.demoapp.compose.components + +import android.content.res.ColorStateList +import android.view.Gravity +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import com.google.android.material.textfield.TextInputLayout +import com.verygoodsecurity.demoapp.R +import com.verygoodsecurity.vgscollect.core.HTTPMethod +import com.verygoodsecurity.vgscollect.core.VGSCollect +import com.verygoodsecurity.vgscollect.view.InputFieldView +import com.verygoodsecurity.vgscollect.widget.VGSTextInputLayout +import com.verygoodsecurity.vgscollect.widget.compose.CardVerificationCodeEditTextWrapper +import com.verygoodsecurity.vgscollect.widget.compose.VGSCardNumberEditTextWrapper + +@Composable +fun BaseCollect( + collect: VGSCollect?, + path: String +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + val radius = with(LocalDensity.current) { 4.dp.toPx() } + val paddingVertical = with(LocalDensity.current) { 16.dp.roundToPx() } + val paddingHorizontal = with(LocalDensity.current) { 8.dp.roundToPx() } + + VGSCardNumberEditTextWrapper( + collect = collect, + fieldName = "card.number", + modifier = Modifier.fillMaxWidth(), + onViewCreate = { layout, input -> + configureTextInputLayout( + layout = layout, + hint = "Card Number", + radius = radius + ) + configureEditText( + input = input, + paddingVertical = paddingVertical, + paddingHorizontal = paddingHorizontal, + ) + input.setDivider(' ') + input.setCardBrandIconGravity(Gravity.END) + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + CardVerificationCodeEditTextWrapper( + collect = collect, + fieldName = "card.cvc", + modifier = Modifier.fillMaxWidth(), + onViewCreate = { layout, input -> + configureTextInputLayout( + layout = layout, + hint = "CVC", + radius = radius + ) + configureEditText( + input = input, + paddingVertical = paddingVertical, + paddingHorizontal = paddingHorizontal, + ) + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + + OutlinedButton( + modifier = Modifier.align(alignment = Alignment.End), + contentPadding = PaddingValues(16.dp), + onClick = { collect?.asyncSubmit(path, HTTPMethod.POST) } + ) { + Text( + text = "SUBMIT", + color = Color(0xff4b5d69) + ) + } + } +} + +private fun configureTextInputLayout( + layout: VGSTextInputLayout, + hint: String, + radius: Float +) { + layout.setHint(hint) + layout.setHintTextColor( + ColorStateList.valueOf( + ContextCompat.getColor( + layout.context, + R.color.fiord + ) + ) + ) + layout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_OUTLINE) + layout.setBoxCornerRadius(radius, radius, radius, radius) +} + +private fun configureEditText( + input: InputFieldView, + paddingVertical: Int, + paddingHorizontal: Int +) { + input.setPadding(paddingVertical, paddingHorizontal, paddingVertical, paddingHorizontal) +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun Preview() { + MaterialTheme { + BaseCollect( + collect = null, + path = "" + ) + } +} diff --git a/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragment.kt b/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragment.kt new file mode 100644 index 000000000..89de36528 --- /dev/null +++ b/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragment.kt @@ -0,0 +1,76 @@ +package com.verygoodsecurity.demoapp.compose.fragment_case + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.tooling.preview.Preview +import androidx.fragment.app.Fragment +import com.verygoodsecurity.demoapp.compose.components.BaseCollect +import com.verygoodsecurity.vgscollect.core.VGSCollect +import com.verygoodsecurity.vgscollect.core.VgsCollectResponseListener +import com.verygoodsecurity.vgscollect.core.model.network.VGSResponse + +private const val PATH = "" +private const val VAULT_ID = "" +private const val ENVIRONMENT = "" + +class ComposeFragment : Fragment(), VgsCollectResponseListener { + + private val collect: VGSCollect by lazy { + VGSCollect( + requireContext(), + VAULT_ID, + ENVIRONMENT + ).also { + it.addOnResponseListeners(this) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setContent { + Content(collect = collect) + } + } + } + + override fun onResponse(response: VGSResponse?) { + Toast.makeText( + requireContext(), + when (response) { + is VGSResponse.SuccessResponse -> "Success" + is VGSResponse.ErrorResponse -> "Error" + else -> throw IllegalStateException() + }, + Toast.LENGTH_SHORT + ).show() + + Log.d("VGS", "response = ${response.body}") + } +} + +@Composable +private fun Content(collect: VGSCollect?) { + MaterialTheme { + BaseCollect( + collect = collect, + path = PATH + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun Preview() { + Content(collect = null) +} diff --git a/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragmentActivity.kt b/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragmentActivity.kt new file mode 100644 index 000000000..59c11f6aa --- /dev/null +++ b/app/src/main/java/com/verygoodsecurity/demoapp/compose/fragment_case/ComposeFragmentActivity.kt @@ -0,0 +1,6 @@ +package com.verygoodsecurity.demoapp.compose.fragment_case + +import androidx.appcompat.app.AppCompatActivity +import com.verygoodsecurity.demoapp.R + +class ComposeFragmentActivity: AppCompatActivity(R.layout.activity_fragment_compose) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_fragment_compose.xml b/app/src/main/res/layout/activity_fragment_compose.xml new file mode 100644 index 000000000..6563c711d --- /dev/null +++ b/app/src/main/res/layout/activity_fragment_compose.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_start.xml b/app/src/main/res/layout/activity_start.xml index 31674e401..6bf0a4684 100644 --- a/app/src/main/res/layout/activity_start.xml +++ b/app/src/main/res/layout/activity_start.xml @@ -103,7 +103,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_padding_material_medium" android:columnCount="2" - android:rowCount="4"> + android:rowCount="5"> @@ -383,7 +382,6 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_padding_material_micro" android:text="@string/start_activity_start_btn_title" /> - @@ -425,9 +422,48 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_padding_material_micro" android:text="@string/start_activity_start_btn_title" /> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc1f068c1..a794e6ea8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,8 +29,11 @@ Date Range (Activity) Example of collecting date range using Activity. - Compose - Example of collecting date wit Jetpack Compose. + Compose (Activity) + Example of collecting date wit Jetpack Compose and Activity. + + Compose (Fragment) + Example of collecting date wit Jetpack Compose and Fragment. Payopt Collecting data with payment optimization example. diff --git a/settings.gradle b/settings.gradle index 50f297c8c..8dfecc0b2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,8 +10,8 @@ dependencyResolutionManagement { library('maven', 'com.github.dcendents:android-maven-gradle-plugin:2.1') library('maven-publish', 'com.vanniktech:gradle-maven-publish-plugin:0.27.0') library('dokka', 'org.jetbrains.dokka:dokka-gradle-plugin:1.9.10') - library('detekt', 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.4') - library('kover', 'org.jetbrains.kotlinx:kover-gradle-plugin:0.7.5') + library('detekt', 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.5') + library('kover', 'org.jetbrains.kotlinx:kover-gradle-plugin:0.7.6') } libs { // Libraries @@ -22,8 +22,8 @@ dependencyResolutionManagement { library('dokka-base', 'org.jetbrains.dokka:dokka-base:1.9.10') library('cardio', 'io.card:android-sdk:5.5.1') library('compose-activity', 'androidx.activity:activity-compose:1.8.2') - library('compose-material', 'androidx.compose.material:material:1.5.4') - library('compose-ui-tooling-preview', 'androidx.compose.ui:ui-tooling-preview:1.5.4') + library('compose-material', 'androidx.compose.material:material:1.6.2') + library('compose-ui-tooling-preview', 'androidx.compose.ui:ui-tooling-preview:1.6.2') } demoLibs { library('androidx-constraintlayout', 'androidx.constraintlayout:constraintlayout:2.1.4') @@ -35,7 +35,7 @@ dependencyResolutionManagement { } debugLibs { library('leakcanary', 'com.squareup.leakcanary:leakcanary-android:2.13') - library('compose-ui-tooling', 'androidx.compose.ui:ui-tooling:1.5.4') + library('compose-ui-tooling', 'androidx.compose.ui:ui-tooling:1.6.2') } testLibs { library('junit', 'junit:junit:4.13.2') @@ -43,7 +43,7 @@ dependencyResolutionManagement { library('mockito-inline', 'org.mockito:mockito-inline:5.2.0') library('robolectric', 'org.robolectric:robolectric:4.11.1') library('mockk', 'io.mockk:mockk:1.12.4') - library('json', 'org.json:json:20231013') + library('json', 'org.json:json:20240205') library('jsonassert', 'org.skyscreamer:jsonassert:1.5.1') } androidTestLibs { @@ -52,7 +52,7 @@ dependencyResolutionManagement { library('androidx-rules', 'androidx.test:rules:1.5.0') library('androidx-junit-ext', 'androidx.test.ext:junit:1.1.5') library('androidx-junit-ext-ktx', 'androidx.test.ext:junit-ktx:1.1.5') - library('androidx-uiautomator', 'androidx.test.uiautomator:uiautomator:2.2.0') + library('androidx-uiautomator', 'androidx.test.uiautomator:uiautomator:2.3.0') library('androidx-espresso-core', 'androidx.test.espresso:espresso-core:3.5.1') library('androidx-espresso-intents', 'androidx.test.espresso:espresso-intents:3.5.1') library('espresso-contrib', 'com.android.support.test.espresso:espresso-contrib:3.0.2') diff --git a/vgscollect/build.gradle b/vgscollect/build.gradle index 78877e6fc..a80a2cc86 100644 --- a/vgscollect/build.gradle +++ b/vgscollect/build.gradle @@ -79,7 +79,7 @@ dependencies { implementation libs.androidx.appcompat implementation libs.androidx.core.ktx - implementation platform('androidx.compose:compose-bom:2023.10.01') + implementation platform('androidx.compose:compose-bom:2024.02.01') implementation libs.compose.material testImplementation testLibs.junit diff --git a/vgscollect/gradle.properties b/vgscollect/gradle.properties index 0edad60bd..ec91a5b2e 100644 --- a/vgscollect/gradle.properties +++ b/vgscollect/gradle.properties @@ -6,5 +6,5 @@ POM_INCEPTION_YEAR=2021 POM_PACKAGING=aar # Version code & name -VERSION_NAME=1.9.0 -VERSION_CODE=1900 \ No newline at end of file +VERSION_NAME=1.9.1 +VERSION_CODE=1901 \ No newline at end of file diff --git a/vgscollect/src/main/java/com/verygoodsecurity/vgscollect/view/card/formatter/digit/DigitInputFormatter.kt b/vgscollect/src/main/java/com/verygoodsecurity/vgscollect/view/card/formatter/digit/DigitInputFormatter.kt index 95b1b3289..ca6e42e47 100644 --- a/vgscollect/src/main/java/com/verygoodsecurity/vgscollect/view/card/formatter/digit/DigitInputFormatter.kt +++ b/vgscollect/src/main/java/com/verygoodsecurity/vgscollect/view/card/formatter/digit/DigitInputFormatter.kt @@ -18,11 +18,9 @@ class DigitInputFormatter(private var mask: String) : TextWatcher, Formatter { } override fun afterTextChanged(s: Editable?) { - formatted?.let { - if (s?.toString() != it.first) { - s?.replaceIgnoreFilters(0, s.length, it.first) - it.second?.let { position -> Selection.setSelection(s, position) } - } + if (s?.toString() != formatted?.first) { + formatted?.first?.let { s?.replaceIgnoreFilters(0, s.length, it) } + formatted?.second?.let { position -> Selection.setSelection(s, position) } } }