Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import java.util.UUID
internal class RetryInterceptor(
private val retryStrategy: PORetryStrategy = Exponential(
maxRetries = 4,
initialDelay = 100,
seedDelay = 100,
maxDelay = 1000,
factor = 3.0
)
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().addIdempotencyKey()
val iterator = retryStrategy.iterator
val backoffIterator = retryStrategy.newBackoffIterator()
repeat(retryStrategy.maxRetries - 1) {
var response: Response? = null
try {
Expand All @@ -34,7 +34,7 @@ internal class RetryInterceptor(
// network issue, retry
}
response?.body?.close()
Thread.sleep(iterator.next())
Thread.sleep(backoffIterator.next())
}
return chain.proceed(request)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlin.math.roundToLong
@ProcessOutInternalApi
sealed class PORetryStrategy(
val maxRetries: Int,
private val initialDelay: Long,
private val seedDelay: Long,
private val minDelay: Long,
private val maxDelay: Long,
private val factor: Double
Expand All @@ -18,31 +18,31 @@ sealed class PORetryStrategy(
delay: Long
) : PORetryStrategy(
maxRetries = maxRetries,
initialDelay = delay,
seedDelay = delay,
minDelay = delay,
maxDelay = delay,
factor = 1.0
)

class Exponential(
maxRetries: Int,
initialDelay: Long,
minDelay: Long = initialDelay,
seedDelay: Long,
minDelay: Long = seedDelay,
maxDelay: Long,
factor: Double
) : PORetryStrategy(
maxRetries = maxRetries,
initialDelay = initialDelay,
seedDelay = seedDelay,
minDelay = minDelay,
maxDelay = maxDelay,
factor = factor
)

class Iterator(
private val iterator: kotlin.collections.Iterator<Double>,
class BackoffIterator(
private val iterator: Iterator<Double>,
private val minDelay: Long,
private val maxDelay: Long
) : kotlin.collections.Iterator<Long> {
) : Iterator<Long> {

override fun hasNext(): Boolean = iterator.hasNext()

Expand All @@ -53,12 +53,11 @@ sealed class PORetryStrategy(
}
}

val iterator: Iterator
get() = Iterator(
iterator = generateSequence(initialDelay.toDouble()) { previous ->
previous * factor
}.iterator(),
minDelay = minDelay,
maxDelay = maxDelay
)
fun newBackoffIterator() = BackoffIterator(
iterator = generateSequence(seed = seedDelay.toDouble()) { previous ->
previous * factor
}.iterator(),
minDelay = minDelay,
maxDelay = maxDelay
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.processout.sdk.ui.nativeapm
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Patterns
import android.view.View
import android.view.inputmethod.EditorInfo
Expand Down Expand Up @@ -79,7 +80,7 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
eventDispatcher = PODefaultEventDispatchers.defaultNativeAlternativePaymentMethod,
captureRetryStrategy = Exponential(
maxRetries = Int.MAX_VALUE,
initialDelay = 150,
seedDelay = 150,
minDelay = 3 * 1000,
maxDelay = 90 * 1000,
factor = 1.45
Expand All @@ -104,8 +105,8 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(

var animateViewTransition = true

private var captureStartTimestamp = 0L
private var capturePassedTimestamp = 0L
private var captureStartTime = 0L
private var captureElapsedTime = 0L

private val handler by lazy { Handler(Looper.getMainLooper()) }

Expand Down Expand Up @@ -521,26 +522,26 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
}

private fun capture() {
if (captureStartTimestamp != 0L) {
if (captureStartTime != 0L) {
return
}
captureStartTimestamp = System.currentTimeMillis()
captureStartTime = SystemClock.elapsedRealtime()
options.showPaymentConfirmationProgressIndicatorAfterSeconds?.let { afterSeconds ->
showPaymentConfirmationProgressIndicator(
afterMillis = TimeUnit.SECONDS.toMillis(afterSeconds.toLong())
)
}
viewModelScope.launch {
val iterator = captureRetryStrategy.iterator
while (capturePassedTimestamp <= options.paymentConfirmationTimeoutSeconds * 1000) {
val backoffIterator = captureRetryStrategy.newBackoffIterator()
while (captureElapsedTime <= options.paymentConfirmationTimeoutSeconds * 1000L) {
val result = invoicesService.captureNativeAlternativePayment(invoiceId, gatewayConfigurationId)
POLogger.debug("Attempted to capture invoice.")
if (isCaptureRetryable(result)) {
delay(iterator.next())
capturePassedTimestamp = System.currentTimeMillis() - captureStartTimestamp
delay(timeMillis = backoffIterator.next())
captureElapsedTime = SystemClock.elapsedRealtime() - captureStartTime
} else {
captureStartTimestamp = 0L
capturePassedTimestamp = 0L
captureStartTime = 0L
captureElapsedTime = 0L
when (result) {
is ProcessOutResult.Success ->
_uiState.value.doWhenCapture { uiModel ->
Expand All @@ -552,8 +553,8 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
return@launch
}
}
captureStartTimestamp = 0L
capturePassedTimestamp = 0L
captureStartTime = 0L
captureElapsedTime = 0L
_uiState.value = Failure(
ProcessOutResult.Failure(
Timeout(), "Payment confirmation timed out."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import android.os.SystemClock
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
Expand All @@ -16,23 +17,25 @@ import kotlinx.coroutines.delay
fun POCountdownTimerText(
textFormat: String,
timeoutSeconds: Int,
initialElapsedRealtime: Long,
modifier: Modifier = Modifier,
style: POText.Style = POText.Style(
color = colors.text.primary,
textStyle = typography.s15(FontWeight.Medium)
)
) {
var secondsLeft by remember { mutableIntStateOf(timeoutSeconds) }
val formattedText = remember(secondsLeft) {
val minutes = secondsLeft / 60
val seconds = secondsLeft % 60
var remainingSeconds by remember { mutableIntStateOf(timeoutSeconds) }
val formattedText = remember(remainingSeconds) {
val minutes = remainingSeconds / 60
val seconds = remainingSeconds % 60
val formattedTime = String.format("%02d:%02d", minutes, seconds)
String.format(textFormat, formattedTime)
}
LaunchedEffect(secondsLeft) {
if (secondsLeft > 0) {
LaunchedEffect(Unit) {
while (remainingSeconds > 0) {
val elapsedSeconds = ((SystemClock.elapsedRealtime() - initialElapsedRealtime) / 1000L).toInt()
remainingSeconds = (timeoutSeconds - elapsedSeconds).coerceAtLeast(minimumValue = 0)
delay(timeMillis = 1000)
secondsLeft -= 1
}
}
POText(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.processout.sdk.ui.core.component.stepper

import android.os.SystemClock
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
Expand All @@ -22,7 +23,8 @@ object POStepper {

data class CountdownTimerText(
val textFormat: String,
val timeoutSeconds: Int
val timeoutSeconds: Int,
val initialElapsedRealtime: Long = SystemClock.elapsedRealtime()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fun POVerticalStepper(
POCountdownTimerText(
textFormat = description.textFormat,
timeoutSeconds = description.timeoutSeconds,
initialElapsedRealtime = description.initialElapsedRealtime,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = spacing.space4),
Expand Down
1 change: 1 addition & 0 deletions ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ dependencies {

api "androidx.activity:activity-compose:$androidxActivityVersion"
api "androidx.lifecycle:lifecycle-viewmodel-compose:$androidxLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-process:$androidxLifecycleVersion"

implementation "androidx.camera:camera-camera2:$androidxCameraVersion"
implementation "androidx.camera:camera-lifecycle:$androidxCameraVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.processout.sdk.ui.card.scanner.recognition

import android.app.Application
import android.graphics.Bitmap
import android.os.SystemClock
import androidx.camera.core.ImageProxy
import com.google.android.gms.common.moduleinstall.InstallStatusListener
import com.google.android.gms.common.moduleinstall.ModuleInstall
Expand Down Expand Up @@ -50,7 +51,7 @@ internal class CardRecognitionSession(
private val moduleInstallClient = ModuleInstall.getClient(app)
private val textRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

private var startTimestamp = 0L
private var startTime = 0L
private val recognizedCards = mutableListOf<POScannedCard>()

init {
Expand Down Expand Up @@ -163,8 +164,8 @@ internal class CardRecognitionSession(
val candidates = text.candidates(MIN_CONFIDENCE)
val number = numberDetector.firstMatch(candidates)
if (number != null) {
if (startTimestamp == 0L) {
startTimestamp = System.currentTimeMillis()
if (startTime == 0L) {
startTime = SystemClock.elapsedRealtime()
}
val card = POScannedCard(
number = number,
Expand All @@ -178,12 +179,12 @@ internal class CardRecognitionSession(
_currentCard.send(card)
}
}
if (System.currentTimeMillis() - startTimestamp > RECOGNITION_DURATION_MS) {
if (SystemClock.elapsedRealtime() - startTime > RECOGNITION_DURATION_MS) {
if (recognizedCards.isNotEmpty()) {
sendMostFrequentCard()
recognizedCards.clear()
}
startTimestamp = 0L
startTime = 0L
}
imageProxy.close()
}
Expand Down
Loading
Loading