TransactionPeerFragment.kt (8268B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2022 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.transactions 18 19 import android.content.Context 20 import android.os.Bundle 21 import android.view.LayoutInflater 22 import android.view.View 23 import android.view.ViewGroup 24 import androidx.appcompat.app.AppCompatActivity 25 import androidx.compose.foundation.basicMarquee 26 import androidx.compose.foundation.layout.Column 27 import androidx.compose.foundation.layout.Row 28 import androidx.compose.foundation.layout.Spacer 29 import androidx.compose.foundation.layout.fillMaxWidth 30 import androidx.compose.foundation.layout.padding 31 import androidx.compose.foundation.layout.size 32 import androidx.compose.foundation.rememberScrollState 33 import androidx.compose.foundation.verticalScroll 34 import androidx.compose.material.icons.Icons 35 import androidx.compose.material.icons.filled.ContentCopy 36 import androidx.compose.material3.ButtonDefaults 37 import androidx.compose.material3.Icon 38 import androidx.compose.material3.MaterialTheme 39 import androidx.compose.material3.Text 40 import androidx.compose.material3.TextButton 41 import androidx.compose.runtime.Composable 42 import androidx.compose.runtime.getValue 43 import androidx.compose.ui.Alignment 44 import androidx.compose.ui.Alignment.Companion.CenterHorizontally 45 import androidx.compose.ui.Modifier 46 import androidx.compose.ui.graphics.Color 47 import androidx.compose.ui.platform.ComposeView 48 import androidx.compose.ui.platform.LocalContext 49 import androidx.compose.ui.res.colorResource 50 import androidx.compose.ui.res.stringResource 51 import androidx.compose.ui.text.style.TextAlign 52 import androidx.compose.ui.unit.dp 53 import androidx.compose.ui.unit.sp 54 import androidx.lifecycle.Lifecycle 55 import androidx.lifecycle.lifecycleScope 56 import androidx.lifecycle.repeatOnLifecycle 57 import kotlinx.coroutines.launch 58 import net.taler.common.Amount 59 import net.taler.common.CurrencySpecification 60 import net.taler.common.copyToClipBoard 61 import net.taler.common.toAbsoluteTime 62 import net.taler.wallet.BottomInsetsSpacer 63 import net.taler.wallet.R 64 import net.taler.wallet.compose.TalerSurface 65 import net.taler.wallet.compose.collectAsStateLifecycleAware 66 import net.taler.wallet.launchInAppBrowser 67 import net.taler.wallet.peer.TransactionPeerPullCreditComposable 68 import net.taler.wallet.peer.TransactionPeerPullDebitComposable 69 import net.taler.wallet.peer.TransactionPeerPushCreditComposable 70 import net.taler.wallet.peer.TransactionPeerPushDebitComposable 71 72 class TransactionPeerFragment : TransactionDetailFragment() { 73 74 override fun onCreateView( 75 inflater: LayoutInflater, 76 container: ViewGroup?, 77 savedInstanceState: Bundle?, 78 ): View = ComposeView(requireContext()).apply { 79 setContent { 80 TalerSurface { 81 val t by transactionManager.selectedTransaction.collectAsStateLifecycleAware() 82 t?.let { tx -> 83 TransactionPeerComposable( 84 tx, devMode, 85 exchangeManager.getSpecForCurrency(tx.amountRaw.currency, tx.scopes), 86 onConfirmKyc = { onConfirmKyc(it) }, 87 ) { 88 onTransitionButtonClicked(tx, it) 89 } 90 } 91 } 92 } 93 } 94 95 override fun onCreate(savedInstanceState: Bundle?) { 96 super.onCreate(savedInstanceState) 97 lifecycleScope.launch { 98 repeatOnLifecycle(Lifecycle.State.STARTED) { 99 transactionManager.selectedTransaction.collect { tx -> 100 val actionBar = (requireActivity() as? AppCompatActivity) 101 ?.supportActionBar 102 ?: return@collect 103 actionBar.title = tx?.getTitle(requireContext()) 104 } 105 } 106 } 107 } 108 109 fun onConfirmKyc(url: String) { 110 launchInAppBrowser(requireContext(), url) 111 } 112 } 113 114 @Composable 115 fun TransactionPeerComposable( 116 t: Transaction, 117 devMode: Boolean, 118 spec: CurrencySpecification?, 119 onConfirmKyc: (url: String) -> Unit, 120 onTransition: (t: TransactionAction) -> Unit, 121 ) { 122 val scrollState = rememberScrollState() 123 Column( 124 modifier = Modifier 125 .fillMaxWidth() 126 .verticalScroll(scrollState), 127 horizontalAlignment = CenterHorizontally, 128 ) { 129 val context = LocalContext.current 130 131 TransactionStateComposable(state = t.txState) 132 133 Text( 134 modifier = Modifier.padding(16.dp), 135 text = t.timestamp.ms.toAbsoluteTime(context).toString(), 136 style = MaterialTheme.typography.bodyLarge, 137 ) 138 139 when (t) { 140 is TransactionPeerPullCredit -> TransactionPeerPullCreditComposable(t, spec, onConfirmKyc) 141 is TransactionPeerPushCredit -> TransactionPeerPushCreditComposable(t, spec, onConfirmKyc) 142 is TransactionPeerPullDebit -> TransactionPeerPullDebitComposable(t, spec) 143 is TransactionPeerPushDebit -> TransactionPeerPushDebitComposable(t, spec) 144 else -> error("unexpected transaction: ${t::class.simpleName}") 145 } 146 147 TransitionsComposable(t, devMode, onTransition) 148 149 if (devMode && t.error != null) { 150 ErrorTransactionButton(error = t.error!!) 151 } 152 153 BottomInsetsSpacer() 154 } 155 } 156 157 @Composable 158 fun TransactionAmountComposable( 159 label: String, 160 amount: Amount, 161 amountType: AmountType, 162 context: Context? = null, 163 copy: Boolean = false, 164 ) { 165 Text( 166 modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp), 167 text = label, 168 style = MaterialTheme.typography.bodyMedium, 169 ) 170 Row( 171 verticalAlignment = Alignment.CenterVertically, 172 ) { 173 Text( 174 modifier = Modifier 175 .padding( 176 top = 8.dp, 177 start = 16.dp, 178 end = 16.dp, 179 bottom = 16.dp, 180 ).weight(1f), 181 text = amount.toString(negative = amountType == AmountType.Negative), 182 textAlign = TextAlign.Center, 183 fontSize = 24.sp, 184 color = when (amountType) { 185 AmountType.Positive -> colorResource(R.color.green) 186 AmountType.Negative -> MaterialTheme.colorScheme.error 187 AmountType.Neutral -> Color.Unspecified 188 }, 189 ) 190 191 if (copy) { 192 TextButton( 193 onClick = { context?.let { 194 copyToClipBoard(context, label, amount.toString(showSymbol = false)) 195 } }, 196 ) { 197 Icon( 198 Icons.Default.ContentCopy, 199 contentDescription = null, 200 modifier = Modifier.size(ButtonDefaults.IconSize), 201 ) 202 Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 203 Text(stringResource(R.string.copy)) 204 } 205 } 206 } 207 } 208 209 @Composable 210 fun TransactionInfoComposable( 211 label: String, 212 info: String, 213 marquee: Boolean = false, 214 trailing: (@Composable () -> Unit)? = null, 215 ) { 216 Text( 217 modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp), 218 text = label, 219 style = MaterialTheme.typography.bodyMedium, 220 ) 221 222 Row( 223 modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 16.dp), 224 verticalAlignment = Alignment.CenterVertically, 225 ) { 226 Text( 227 modifier = if (marquee) Modifier.basicMarquee() else Modifier, 228 text = info, 229 fontSize = 24.sp, 230 ) 231 232 trailing?.let { it() } 233 } 234 }