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 }