taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

PromptPaymentScreen.kt (6622B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.payment
     18 
     19 import android.graphics.Bitmap
     20 import android.util.Log
     21 import androidx.compose.foundation.Image
     22 import androidx.compose.foundation.clickable
     23 import androidx.compose.foundation.layout.fillMaxSize
     24 import androidx.compose.foundation.layout.fillMaxWidth
     25 import androidx.compose.foundation.layout.padding
     26 import androidx.compose.material3.Card
     27 import androidx.compose.material3.MaterialTheme
     28 import androidx.compose.material3.Text
     29 import androidx.compose.runtime.Composable
     30 import androidx.compose.runtime.LaunchedEffect
     31 import androidx.compose.runtime.getValue
     32 import androidx.compose.runtime.livedata.observeAsState
     33 import androidx.compose.runtime.mutableStateOf
     34 import androidx.compose.runtime.remember
     35 import androidx.compose.runtime.setValue
     36 import androidx.compose.ui.Modifier
     37 import androidx.compose.ui.graphics.asImageBitmap
     38 import androidx.compose.ui.res.stringResource
     39 import androidx.compose.ui.unit.dp
     40 import androidx.compose.ui.window.Dialog
     41 import net.taler.wallet.NavigateCallback
     42 import net.taler.wallet.R
     43 import net.taler.wallet.WalletDestination
     44 import net.taler.wallet.backend.TalerErrorInfo
     45 import net.taler.wallet.compose.GlobalScaffold
     46 import net.taler.wallet.compose.LoadingScreen
     47 import net.taler.wallet.main.MainViewModel
     48 import net.taler.wallet.main.TAG
     49 import net.taler.wallet.transactions.TransactionManager
     50 
     51 @Composable
     52 fun PromptPaymentScreen(
     53     model: MainViewModel,
     54     onNavigate: NavigateCallback,
     55     onNavigateBack: () -> Unit,
     56     onShowError: (TalerErrorInfo) -> Unit,
     57 ) {
     58     val paymentManager = model.paymentManager
     59     val transactionManager = model.transactionManager
     60     val payStatus by paymentManager.payStatus.observeAsState(PayStatus.None)
     61     var showImage by remember { mutableStateOf<Bitmap?>(null) }
     62 
     63     LaunchedEffect(payStatus) {
     64         val status = payStatus
     65         when (status) {
     66             is PayStatus.Success -> {
     67                 navigateToTransaction(status.transactionId, transactionManager, onNavigate, onNavigateBack)
     68                 paymentManager.resetPayStatus()
     69             }
     70 
     71             is PayStatus.AlreadyPaid -> {
     72                 navigateToTransaction(status.transactionId, transactionManager, onNavigate, onNavigateBack)
     73                 paymentManager.resetPayStatus()
     74             }
     75 
     76             is PayStatus.Pending -> {
     77                 navigateToTransaction(status.transactionId, transactionManager, onNavigate, onNavigateBack)
     78                 paymentManager.resetPayStatus()
     79                 status.error?.let { error ->
     80                     onShowError(error)
     81                 }
     82             }
     83 
     84             else -> {}
     85         }
     86     }
     87 
     88     GlobalScaffold(
     89         model = model,
     90         modifier = Modifier.fillMaxSize(),
     91         title = { Text(stringResource(R.string.payment_title)) },
     92         onNavigateBack = onNavigateBack,
     93     ) { paddingValues ->
     94         when (val status = payStatus) {
     95             is PayStatus.None,
     96             is PayStatus.Loading,
     97             is PayStatus.Prepared -> LoadingScreen(Modifier.padding(paddingValues))
     98             is PayStatus.Choices -> {
     99                 PromptPaymentComposable(
    100                     modifier = Modifier.padding(paddingValues),
    101                     status = status,
    102                     onConfirm = { index, useDonau ->
    103                         paymentManager.confirmPay(
    104                             transactionId = status.transactionId,
    105                             choiceIndex = index,
    106                             useDonau = useDonau,
    107                         )
    108                     },
    109                     onCancel = {
    110                         transactionManager.abortTransaction(
    111                             status.transactionId,
    112                             onSuccess = {
    113                                 onNavigateBack()
    114                             },
    115                             onError = { error ->
    116                                 Log.e(TAG, "Error abortTransaction $error")
    117                                 onShowError(error)
    118                             }
    119                         )
    120                     },
    121                     onClickImage = { bitmap ->
    122                         showImage = bitmap
    123                     },
    124                     checkDonauStatus = { index ->
    125                         status.choices.find { it.choiceIndex == index }?.let { choice ->
    126                             paymentManager.checkDonauForChoice(choice)
    127                         } ?: DonauStatus.Unavailable
    128                     },
    129                     onSetupDonau = { donauBaseUrl ->
    130                         onNavigate(WalletDestination.SetDonau(donauBaseUrl), false)
    131                     },
    132                 )
    133             }
    134             else -> {}
    135         }
    136     }
    137 
    138     if (showImage != null) {
    139         // ProductImageFragment was a DialogFragment. 
    140         // For now, we can just use a simple Modal or similar if needed, 
    141         // but let's just keep it simple or implement a basic compose dialog.
    142         ProductImageDialog(showImage!!) { showImage = null }
    143     }
    144 }
    145 
    146 @Composable
    147 fun ProductImageDialog(bitmap: Bitmap, onDismiss: () -> Unit) {
    148     Dialog(onDismissRequest = onDismiss) {
    149         Card(
    150             shape = MaterialTheme.shapes.medium,
    151             modifier = Modifier
    152                 .padding(16.dp)
    153                 .fillMaxWidth(),
    154         ) {
    155             Image(
    156                 bitmap = bitmap.asImageBitmap(),
    157                 contentDescription = null,
    158                 modifier = Modifier
    159                     .fillMaxWidth()
    160                     .clickable { onDismiss() }
    161             )
    162         }
    163     }
    164 }
    165 
    166 private suspend fun navigateToTransaction(
    167     id: String?,
    168     transactionManager: TransactionManager,
    169     onNavigate: NavigateCallback,
    170     onNavigateBack: () -> Unit,
    171 ) {
    172     if (id != null && transactionManager.selectTransaction(id)) {
    173         onNavigate(WalletDestination.TransactionPayment, true)
    174     } else {
    175         onNavigateBack()
    176     }
    177 }