taler-android

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

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 }