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) }
}
}