Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
dc23997
POScannedCard
vitalii-vanziak-cko Feb 18, 2025
445e414
POCardScannerConfiguration
vitalii-vanziak-cko Feb 18, 2025
63eb36e
CardScannerActivity and CardScannerActivityContract
vitalii-vanziak-cko Feb 18, 2025
3d64285
POCardScannerLauncher
vitalii-vanziak-cko Feb 18, 2025
7c786cf
CardScannerBottomSheet
vitalii-vanziak-cko Feb 18, 2025
4949f5a
States and events
vitalii-vanziak-cko Feb 18, 2025
f20cebb
CardScannerInteractor
vitalii-vanziak-cko Feb 18, 2025
1391166
CardScannerViewModel
vitalii-vanziak-cko Feb 18, 2025
e16371a
Init VM and handle completion
vitalii-vanziak-cko Feb 18, 2025
71d8544
CardScannerScreen
vitalii-vanziak-cko Feb 19, 2025
c903fc1
Add card scanner to Example app
vitalii-vanziak-cko Feb 19, 2025
09ee4bd
Compose 2025.02.00
vitalii-vanziak-cko Feb 19, 2025
c557d76
CameraX dependencies
vitalii-vanziak-cko Feb 19, 2025
e09cb80
Camera permission in Example app
vitalii-vanziak-cko Feb 19, 2025
6a50b4c
Request camera permission
vitalii-vanziak-cko Feb 19, 2025
a198604
CameraPreview
vitalii-vanziak-cko Feb 20, 2025
154209b
Clip
vitalii-vanziak-cko Feb 20, 2025
781aefc
Layout, wrap content bottom sheet, camera preview init
vitalii-vanziak-cko Feb 21, 2025
5263f6e
Move clip out of CameraPreview
vitalii-vanziak-cko Feb 25, 2025
28cfaa7
Setup camera controller and image analyzer
vitalii-vanziak-cko Feb 28, 2025
8b53a81
comment
vitalii-vanziak-cko Feb 28, 2025
91b9774
croppedBitmap
vitalii-vanziak-cko Feb 28, 2025
655bb34
AGP 8.8.2
vitalii-vanziak-cko Mar 4, 2025
60ec68d
CardRecognitionSession setup
vitalii-vanziak-cko Mar 4, 2025
638c90d
mlkit
vitalii-vanziak-cko Mar 4, 2025
0c15ffb
1024x768
vitalii-vanziak-cko Mar 4, 2025
fdb169a
1280x960 YUV
vitalii-vanziak-cko Mar 4, 2025
5ffa8f3
Text recognition
vitalii-vanziak-cko Mar 5, 2025
16fb912
confidentLines
vitalii-vanziak-cko Mar 5, 2025
4d5bbb1
CardAttributeDetector
vitalii-vanziak-cko Mar 5, 2025
9fd31fc
CardNumberDetector
vitalii-vanziak-cko Mar 6, 2025
644104c
CardExpirationDetector
vitalii-vanziak-cko Mar 6, 2025
d18ef11
shouldScanExpiredCard
vitalii-vanziak-cko Mar 11, 2025
2f1a2ab
CardExpirationDetector expiration logic
vitalii-vanziak-cko Mar 11, 2025
dda88df
Formatted number and expiration
vitalii-vanziak-cko Mar 11, 2025
e097a14
setOf() expiration separators
vitalii-vanziak-cko Mar 11, 2025
7f791df
Basic CardholderNameDetector
vitalii-vanziak-cko Mar 11, 2025
8fe1c18
stripDiacritics()
vitalii-vanziak-cko Mar 11, 2025
1cd588d
CardholderNameDetector impl
vitalii-vanziak-cko Mar 11, 2025
830a087
Include lines when all elements is confident
vitalii-vanziak-cko Mar 12, 2025
86f40d6
Improve CardholderNameDetector
vitalii-vanziak-cko Mar 12, 2025
bcd834f
Collect recognized cards correctly in interactor
vitalii-vanziak-cko Mar 13, 2025
7850620
Add current card to VM state
vitalii-vanziak-cko Mar 13, 2025
f2ad3fe
Recognition logic
vitalii-vanziak-cko Mar 13, 2025
fb6a62d
Remove local coroutine scope from CardRecognitionSession
vitalii-vanziak-cko Mar 13, 2025
d4884ef
VM state
vitalii-vanziak-cko Mar 14, 2025
f0cba63
Localizations
vitalii-vanziak-cko Mar 14, 2025
af41495
Map strings
vitalii-vanziak-cko Mar 17, 2025
9a1fedb
POButtonToggle
vitalii-vanziak-cko Mar 17, 2025
2c6681f
Icon colors
vitalii-vanziak-cko Mar 17, 2025
65bcf06
Torch icons
vitalii-vanziak-cko Mar 17, 2025
3a839ce
Map cancel action in VM
vitalii-vanziak-cko Mar 18, 2025
d049199
Fix CardExpirationDetector
vitalii-vanziak-cko Mar 18, 2025
a307eed
Screen, layout, style, customization, events
vitalii-vanziak-cko Mar 18, 2025
d05d16b
Semi transparent overlay on camera preview
vitalii-vanziak-cko Mar 19, 2025
51ccc6a
Finalize CardRecognitionSession logic
vitalii-vanziak-cko Mar 20, 2025
e614afc
Clear interactor dependencies
vitalii-vanziak-cko Mar 20, 2025
f66d4ae
POTextAutoSize
vitalii-vanziak-cko Mar 20, 2025
81ad258
Scanned card UI and style
vitalii-vanziak-cko Mar 20, 2025
c0c8aa4
Add step to POTextAutoSize
vitalii-vanziak-cko Mar 21, 2025
d97a114
Card number font size
vitalii-vanziak-cko Mar 21, 2025
ae04ee6
Animations
vitalii-vanziak-cko Mar 21, 2025
04c16f6
Card position
vitalii-vanziak-cko Mar 21, 2025
6336b22
Cancellation logic
vitalii-vanziak-cko Mar 21, 2025
5c791b4
Code improvement
vitalii-vanziak-cko Mar 21, 2025
acea428
Finalized customization
vitalii-vanziak-cko Mar 22, 2025
e768073
Fix month detection
vitalii-vanziak-cko Mar 22, 2025
570e235
Improve expiration UI
vitalii-vanziak-cko Mar 22, 2025
6cb299e
expirationRegex
vitalii-vanziak-cko Mar 22, 2025
d64c5f0
POScannedCard KDoc
vitalii-vanziak-cko Mar 24, 2025
a3b001c
DC KDoc
vitalii-vanziak-cko Mar 24, 2025
7da6697
POCardScannerConfiguration KDoc
vitalii-vanziak-cko Mar 24, 2025
94ba965
AGP 8.9.0
vitalii-vanziak-cko Mar 24, 2025
048a409
AndroidProjectSystem.xml
vitalii-vanziak-cko Mar 24, 2025
63da729
Remove WIP
vitalii-vanziak-cko Mar 24, 2025
7df47ad
Improve CardRecognitionSession
vitalii-vanziak-cko Mar 24, 2025
e2169ae
Compose 2025.03.00
vitalii-vanziak-cko Mar 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/AndroidProjectSystem.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

buildscript {
ext {
androidGradlePluginVersion = '8.8.0'
androidGradlePluginVersion = '8.9.0'
kotlinVersion = '2.1.10'
kspVersion = '2.1.10-1.0.29'
dokkaVersion = '1.9.20'
Expand Down Expand Up @@ -45,13 +45,15 @@ ext {
androidxRecyclerViewVersion = '1.4.0'
androidxSwipeRefreshLayoutVersion = '1.1.0'
androidxBrowserVersion = '1.8.0'
androidxCameraVersion = '1.4.1'

androidxComposeBOMVersion = '2025.01.01'
androidxComposeBOMVersion = '2025.03.00'
composeGooglePayButtonVersion = '1.0.0'

materialVersion = '1.12.0'

gmsWalletVersion = '19.4.0'
mlkitTextRecognitionVersion = '19.0.1'
kotlinxCoroutinesPlayServicesVersion = '1.9.0'

retrofitVersion = '2.11.0'
Expand Down
3 changes: 3 additions & 0 deletions example/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

<uses-permission android:name="android.permission.INTERNET" />

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />

<application
android:name=".ProcessOutExampleApplication"
android:enableOnBackInvokedCallback="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import com.processout.sdk.api.model.request.POCardTokenizationRequest
import com.processout.sdk.api.model.response.POCard
import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData
import com.processout.sdk.core.*
import com.processout.sdk.ui.card.scanner.POCardScannerConfiguration
import com.processout.sdk.ui.card.scanner.POCardScannerLauncher
import com.processout.sdk.ui.card.scanner.recognition.POScannedCard
import com.processout.sdk.ui.card.update.POCardUpdateConfiguration
import com.processout.sdk.ui.card.update.POCardUpdateConfiguration.CardInformation
import com.processout.sdk.ui.card.update.POCardUpdateLauncher
Expand All @@ -35,6 +38,7 @@ class FeaturesFragment : BaseFragment<FragmentFeaturesBinding>(
private val cardsRepository = ProcessOut.instance.cards

private lateinit var cardUpdateLauncher: POCardUpdateLauncher
private lateinit var cardScannerLauncher: POCardScannerLauncher
private lateinit var googlePayLauncher: POGooglePayCardTokenizationLauncher

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -43,6 +47,10 @@ class FeaturesFragment : BaseFragment<FragmentFeaturesBinding>(
from = this,
callback = ::handleCardUpdateResult
)
cardScannerLauncher = POCardScannerLauncher.create(
from = this,
callback = ::handleCardScannerResult
)
googlePayLauncher = POGooglePayCardTokenizationLauncher.create(
from = this,
walletOptions = WalletOptions.Builder()
Expand Down Expand Up @@ -76,6 +84,7 @@ class FeaturesFragment : BaseFragment<FragmentFeaturesBinding>(
}
}
setupCardUpdate()
setupCardScanner()
setupGooglePay()
}

Expand Down Expand Up @@ -134,6 +143,28 @@ class FeaturesFragment : BaseFragment<FragmentFeaturesBinding>(
}
}

private fun setupCardScanner() {
binding.cardScannerButton.setOnClickListener {
cardScannerLauncher.launch(POCardScannerConfiguration())
}
}

private fun handleCardScannerResult(result: ProcessOutActivityResult<POScannedCard>) {
result
.onSuccess {
showAlert(
title = getString(R.string.card_scanner),
message = it.toString()
)
}
.onFailure {
showAlert(
title = getString(R.string.card_scanner),
message = it.toMessage()
)
}
}

private fun setupGooglePay() {
lifecycleScope.launch {
if (!googlePayLauncher.isReadyToPay(GooglePayConfiguration.isReadyToPayRequest())) {
Expand Down
8 changes: 8 additions & 0 deletions example/src/main/res/layout/fragment_features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
android:layout_marginTop="@dimen/button_space_vertical"
android:text="@string/card_update" />

<Button
android:id="@+id/card_scanner_button"
style="@style/Widget.ProcessOut.Button.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/button_space_vertical"
android:text="@string/card_scanner" />

<com.google.android.gms.wallet.button.PayButton
android:id="@+id/google_pay_button"
android:layout_width="match_parent"
Expand Down
3 changes: 2 additions & 1 deletion example/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<resources>
<string name="app_name" translatable="false">ProcessOut SDK Example</string>

<string name="dynamic_checkout" translatable="false">Dynamic Checkout (WIP)</string>
<string name="dynamic_checkout" translatable="false">Dynamic Checkout (Beta)</string>
<string name="native_apm" translatable="false">Native Alternative Payment</string>
<string name="launch_native_apm" translatable="false">Launch Native APM</string>
<string name="launch_native_apm_compose" translatable="false">Launch Native APM (Compose)</string>
Expand All @@ -13,6 +13,7 @@

<string name="card_payment" translatable="false">Card Payment</string>
<string name="card_update" translatable="false">Card Update</string>
<string name="card_scanner" translatable="false">Card Scanner</string>
<string name="card_details" translatable="false">Card Details</string>
<string name="card_number" translatable="false">Number</string>
<string name="card_expiration_month" translatable="false">Expiration Month</string>
Expand Down
2 changes: 1 addition & 1 deletion sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ dependencies {

api "com.google.android.material:material:$materialVersion"
api "com.google.android.gms:play-services-wallet:$gmsWalletVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesPlayServicesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesPlayServicesVersion"

implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
<item quantity="other">الطول غير صالح، المتوقع %d أحرف</item>
</plurals>

<!-- Card Scanner -->
<string name="po_card_scanner_title">امسح البطاقة</string>
<string name="po_card_scanner_description">ضع بطاقتك داخل الإطار لمسحها ضوئيًا</string>
<string name="po_card_scanner_button_cancel">إضافة يدويًا</string>

<!-- Card Update -->
<string name="po_card_update_title">تفاصيل الدفع</string>
<string name="po_card_update_cvc">كود التحقق CVV</string>
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<item quantity="other">Longueur incorrecte, %d caractères sont attendus.</item>
</plurals>

<!-- Card Scanner -->
<string name="po_card_scanner_title">Scanner la carte</string>
<string name="po_card_scanner_description">Placez votre carte dans le cadre pour la scanner.</string>
<string name="po_card_scanner_button_cancel">Ajouter manuellement</string>

<!-- Card Update -->
<string name="po_card_update_title">Informations de Paiement</string>
<string name="po_card_update_cvc">CVV</string>
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<item quantity="other">Nieprawidłowa długość. Oczekiwano %d znaków.</item>
</plurals>

<!-- Card Scanner -->
<string name="po_card_scanner_title">Skanowanie karty</string>
<string name="po_card_scanner_description">Umieść kartę w ramce, aby ją zeskanować.</string>
<string name="po_card_scanner_button_cancel">Dodaj ręcznie</string>

<!-- Card Update -->
<string name="po_card_update_title">Szczegóły Płatności</string>
<string name="po_card_update_cvc">CVC</string>
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<item quantity="other">Quantidade de caracteres incorreta, espera-se %d caracteres.</item>
</plurals>

<!-- Card Scanner -->
<string name="po_card_scanner_title">Digitalizar cartão</string>
<string name="po_card_scanner_description">Posicione o cartão na moldura para digitalizá-lo.</string>
<string name="po_card_scanner_button_cancel">Adicionar manualmente</string>

<!-- Card Update -->
<string name="po_card_update_title">Detalhes do pagamento</string>
<string name="po_card_update_cvc">CVC</string>
Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
<item quantity="other">Invalid length, expected %d characters.</item>
</plurals>

<!-- Card Scanner -->
<string name="po_card_scanner_title">Scan card</string>
<string name="po_card_scanner_description">Position your card in the frame to scan it.</string>
<string name="po_card_scanner_button_cancel">Add manually</string>

<!-- Card Update -->
<string name="po_card_update_title">Payment Details</string>
<string name="po_card_update_cvc">CVC</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package com.processout.sdk.ui.core.component

import androidx.compose.foundation.BorderStroke
Expand Down Expand Up @@ -48,22 +50,36 @@ fun POButton(
style: POButton.Style = POButton.primary,
enabled: Boolean = true,
loading: Boolean = false,
checked: Boolean = false,
onCheckedChange: ((Boolean) -> Unit)? = null,
leadingContent: @Composable RowScope.() -> Unit = {},
icon: PODrawableImage? = null,
iconSize: Dp = dimensions.iconSizeMedium,
progressIndicatorSize: POButton.ProgressIndicatorSize = Medium,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
val onClickHandler = if (onCheckedChange != null) {
{
onClick()
onCheckedChange(!checked)
}
} else {
onClick
}
val pressed by interactionSource.collectIsPressedAsState()
val colors = colors(style = style, enabled = enabled, loading = loading, pressed = pressed)
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) {
val colors = colors(style = style, enabled = enabled, loading = loading, pressed = pressed, checked = checked)
val rippleConfiguration = if (onCheckedChange != null) null else LocalRippleConfiguration.current
CompositionLocalProvider(
LocalRippleConfiguration provides rippleConfiguration,
LocalMinimumInteractiveComponentSize provides Dp.Unspecified
) {
Button(
onClick = onClick,
onClick = onClickHandler,
modifier = modifier,
enabled = enabled && !loading,
colors = colors,
shape = if (enabled) style.normal.shape else style.disabled.shape,
border = border(style = style, enabled = enabled, pressed = pressed),
border = border(style = style, enabled = enabled, pressed = pressed, checked = checked),
elevation = elevation(style = style, enabled = enabled, loading = loading),
contentPadding = contentPadding(style = style, enabled = enabled),
interactionSource = interactionSource
Expand Down Expand Up @@ -142,6 +158,7 @@ fun POButton(
style = style,
enabled = enabled,
loading = loading,
checked = checked,
leadingContent = leadingContent,
icon = icon,
iconSize = iconSize,
Expand Down Expand Up @@ -343,11 +360,12 @@ object POButton {
style: Style,
enabled: Boolean,
loading: Boolean,
pressed: Boolean
pressed: Boolean,
checked: Boolean
): ButtonColors {
val normalTextColor: Color
val normalBackgroundColor: Color
if (pressed) with(style.highlighted) {
if (pressed || checked) with(style.highlighted) {
normalTextColor = textColor
normalBackgroundColor = backgroundColor
} else with(style.normal) {
Expand Down Expand Up @@ -376,9 +394,10 @@ object POButton {
internal fun border(
style: Style,
enabled: Boolean,
pressed: Boolean
pressed: Boolean,
checked: Boolean
): BorderStroke {
val normalBorderColor = if (pressed) style.highlighted.borderColor else style.normal.border.color
val normalBorderColor = if (pressed || checked) style.highlighted.borderColor else style.normal.border.color
return if (enabled) style.normal.border.solid(color = normalBorderColor)
else style.disabled.border.solid()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.processout.sdk.ui.core.component

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi
import com.processout.sdk.ui.core.component.POButton.ProgressIndicatorSize.Medium
import com.processout.sdk.ui.core.shared.image.PODrawableImage
import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions

/** @suppress */
@ProcessOutInternalApi
@Composable
fun POButtonToggle(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
text: String? = null,
style: POButton.Style = POButton.ghostEqualPadding,
enabled: Boolean = true,
loading: Boolean = false,
icon: PODrawableImage? = null,
iconSize: Dp = dimensions.iconSizeMedium,
progressIndicatorSize: POButton.ProgressIndicatorSize = Medium,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
POButton(
text = text ?: String(),
onClick = {},
modifier = modifier,
style = style,
enabled = enabled,
loading = loading,
checked = checked,
onCheckedChange = onCheckedChange,
icon = icon,
iconSize = iconSize,
progressIndicatorSize = progressIndicatorSize,
interactionSource = interactionSource
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.processout.sdk.ui.core.component

import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi
import com.processout.sdk.ui.core.theme.ProcessOutTheme

/** @suppress */
@ProcessOutInternalApi
@Composable
fun POTextAutoSize(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
style: TextStyle = ProcessOutTheme.typography.body1,
fontStyle: FontStyle? = null,
textAlign: TextAlign? = null,
step: Float = 0.01f
) {
var resizedStyle by remember { mutableStateOf(style) }
var readyToDraw by remember { mutableStateOf(false) }
POText(
text = text,
modifier = modifier.drawWithContent {
if (readyToDraw) {
drawContent()
}
},
color = color,
style = resizedStyle,
fontStyle = fontStyle,
textAlign = textAlign,
onTextLayout = { result ->
if (result.didOverflowWidth) {
resizedStyle = resizedStyle.copy(
fontSize = resizedStyle.fontSize * (1 - step),
lineHeight = resizedStyle.lineHeight * (1 - step)
)
} else {
readyToDraw = true
}
},
softWrap = false,
maxLines = 1
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ data class POActionState(
val primary: Boolean,
val enabled: Boolean = true,
val loading: Boolean = false,
val checked: Boolean = false,
val icon: PODrawableImage? = null,
val confirmation: Confirmation? = null
) {
Expand Down
Loading