diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt index 3663d1d3..9fb20a0e 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt @@ -58,6 +58,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -71,6 +72,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.taler.common.Amount import net.taler.common.ContractChoice @@ -83,6 +85,7 @@ import net.taler.common.ContractTokenFamily import net.taler.common.Exchange import net.taler.common.Merchant import net.taler.common.TalerUtils +import net.taler.common.Timestamp import net.taler.lib.android.base64Bitmap import net.taler.wallet.R import net.taler.wallet.cleanExchange @@ -182,8 +185,12 @@ fun PromptPaymentComposable( } } + val timeToPay = rememberTimeToPay(contractTerms.payDeadline) BottomButtonBox(Modifier.fillMaxWidth(), - heading = if (contractTerms is ContractTerms.V0) { -> + heading = if (contractTerms is ContractTerms.V0 || timeToPay != null) { -> + timeToPay?.let { TimeToPay(it) } + + if (contractTerms !is ContractTerms.V0) return@BottomButtonBox val choice = status.choices.firstOrNull() ?: error("no v0 choice") @@ -247,6 +254,40 @@ fun PromptPaymentComposable( } } +/** Orders due within this time show a countdown, as in the iOS wallet. */ +private const val TIME_TO_PAY_WARNING_MS = 5 * 60 * 1000L + +/** + * Milliseconds left until the payment deadline once it is less than + * [TIME_TO_PAY_WARNING_MS] away, updated every second; null before that. + */ +@Composable +private fun rememberTimeToPay(payDeadline: Timestamp?): Long? { + val deadlineMs = payDeadline?.ms?.takeIf { it > 0 } ?: return null + var now by remember { mutableLongStateOf(System.currentTimeMillis()) } + val remaining = deadlineMs - now + LaunchedEffect(now) { + if (remaining > 0) { + delay(if (remaining > TIME_TO_PAY_WARNING_MS) remaining - TIME_TO_PAY_WARNING_MS else 1000L) + now = System.currentTimeMillis() + } + } + return if (remaining > TIME_TO_PAY_WARNING_MS) null else remaining.coerceAtLeast(0) +} + +@Composable +private fun TimeToPay(remainingMs: Long) { + val seconds = (remainingMs + 999) / 1000 + Text( + modifier = Modifier.padding(bottom = 3.dp), + text = stringResource( + R.string.payment_time_to_pay, + "%d:%02d".format(seconds / 60, seconds % 60), + ), + style = MaterialTheme.typography.labelLarge, + ) +} + @Composable fun MerchantSection( contractTerms: ContractTerms, diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml index 89750709..77b6b67c 100644 --- a/wallet/src/main/res/values/strings.xml +++ b/wallet/src/main/res/values/strings.xml @@ -320,6 +320,7 @@ GNU Taler is immune to many types of fraud such as credit card data theft, phish Unexpected merchant %1$s, your token could be stolen. Merchant %1$s is untrusted, cannot spend this token. Error creating order + Time to pay: %1$s Payment