taler-android

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

TransactionLossFragment.kt (6231B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2024 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.os.Bundle
     20 import android.view.LayoutInflater
     21 import android.view.View
     22 import android.view.ViewGroup
     23 import androidx.compose.foundation.layout.Column
     24 import androidx.compose.foundation.layout.fillMaxWidth
     25 import androidx.compose.foundation.layout.padding
     26 import androidx.compose.foundation.rememberScrollState
     27 import androidx.compose.foundation.verticalScroll
     28 import androidx.compose.material3.MaterialTheme
     29 import androidx.compose.material3.Surface
     30 import androidx.compose.material3.Text
     31 import androidx.compose.runtime.Composable
     32 import androidx.compose.runtime.getValue
     33 import androidx.compose.runtime.remember
     34 import androidx.compose.ui.Alignment
     35 import androidx.compose.ui.Modifier
     36 import androidx.compose.ui.platform.ComposeView
     37 import androidx.compose.ui.platform.LocalContext
     38 import androidx.compose.ui.res.stringResource
     39 import androidx.compose.ui.tooling.preview.Preview
     40 import androidx.compose.ui.unit.dp
     41 import net.taler.common.Amount
     42 import net.taler.common.CurrencySpecification
     43 import net.taler.common.Timestamp
     44 import net.taler.common.toAbsoluteTime
     45 import net.taler.wallet.BottomInsetsSpacer
     46 import net.taler.wallet.R
     47 import net.taler.wallet.backend.TalerErrorCode
     48 import net.taler.wallet.backend.TalerErrorInfo
     49 import net.taler.wallet.balances.ScopeInfo
     50 import net.taler.wallet.compose.TalerSurface
     51 import net.taler.wallet.compose.collectAsStateLifecycleAware
     52 import net.taler.wallet.transactions.LossEventType.DenomExpired
     53 import net.taler.wallet.transactions.LossEventType.DenomUnoffered
     54 import net.taler.wallet.transactions.LossEventType.DenomVanished
     55 import net.taler.wallet.transactions.TransactionAction.Abort
     56 import net.taler.wallet.transactions.TransactionAction.Retry
     57 import net.taler.wallet.transactions.TransactionAction.Suspend
     58 import net.taler.wallet.transactions.TransactionMajorState.Pending
     59 
     60 class TransactionLossFragment: TransactionDetailFragment() {
     61 
     62     override fun onCreateView(
     63         inflater: LayoutInflater,
     64         container: ViewGroup?,
     65         savedInstanceState: Bundle?
     66     ): View = ComposeView(requireContext()).apply {
     67         setContent {
     68             val t by transactionManager.selectedTransaction.collectAsStateLifecycleAware()
     69 
     70             TalerSurface {
     71                 (t as? TransactionDenomLoss)?.let { tx ->
     72                     val spec = remember(tx.amountRaw.currency, tx.scopes) {
     73                         exchangeManager.getSpecForCurrency(tx.amountRaw.currency, tx.scopes)
     74                     }
     75                     TransitionLossComposable(tx, devMode, spec) {
     76                         onTransitionButtonClicked(tx, it)
     77                     }
     78                 }
     79             }
     80         }
     81     }
     82 }
     83 
     84 @Composable
     85 fun TransitionLossComposable(
     86     t: TransactionDenomLoss,
     87     devMode: Boolean,
     88     spec: CurrencySpecification?,
     89     onTransition: (t: TransactionAction) -> Unit,
     90 ) {
     91     val scrollState = rememberScrollState()
     92     val context = LocalContext.current
     93 
     94     Column(
     95         modifier = Modifier
     96             .fillMaxWidth()
     97             .verticalScroll(scrollState),
     98         horizontalAlignment = Alignment.CenterHorizontally,
     99     ) {
    100         TransactionStateComposable(state = t.txState)
    101 
    102         Text(
    103             modifier = Modifier.padding(16.dp),
    104             text = t.timestamp.ms.toAbsoluteTime(context).toString(),
    105             style = MaterialTheme.typography.bodyLarge,
    106         )
    107 
    108         TransactionAmountComposable(
    109             label = stringResource(id = R.string.amount_lost),
    110             amount = t.amountEffective.withSpec(spec),
    111             amountType = AmountType.Negative,
    112         )
    113 
    114         TransactionInfoComposable(
    115             label = stringResource(id = R.string.loss_reason),
    116             info = stringResource(
    117                 when(t.lossEventType) {
    118                     DenomExpired -> R.string.loss_reason_expired
    119                     DenomVanished -> R.string.loss_reason_vanished
    120                     DenomUnoffered -> R.string.loss_reason_unoffered
    121                 }
    122             )
    123         )
    124 
    125         TransitionsComposable(t, devMode, onTransition)
    126 
    127         if (devMode && t.error != null) {
    128             ErrorTransactionButton(error = t.error)
    129         }
    130 
    131         BottomInsetsSpacer()
    132     }
    133 }
    134 
    135 fun previewLossTransaction(lossEventType: LossEventType) =
    136     TransactionDenomLoss(
    137         transactionId = "transactionId",
    138         timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60 * 1000),
    139         txState = TransactionState(Pending),
    140         txActions = listOf(Retry, Suspend, Abort),
    141         amountRaw = Amount.fromString("TESTKUDOS", "0.3"),
    142         amountEffective = Amount.fromString("TESTKUDOS", "0.3"),
    143         error = TalerErrorInfo(code = TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED),
    144         lossEventType = lossEventType,
    145         scopes = listOf(ScopeInfo.Exchange(
    146             currency = "TESTKUDOS",
    147             url = "exchange.test.taler.net",
    148         ))
    149     )
    150 
    151 @Composable
    152 @Preview
    153 fun TransitionLossComposableExpiredPreview() {
    154     val t = previewLossTransaction(DenomExpired)
    155     Surface {
    156         TransitionLossComposable(t, true, null) {}
    157     }
    158 }
    159 
    160 @Composable
    161 @Preview
    162 fun TransitionLossComposableVanishedPreview() {
    163     val t = previewLossTransaction(DenomVanished)
    164     Surface {
    165         TransitionLossComposable(t, true, null) {}
    166     }
    167 }
    168 
    169 @Composable
    170 @Preview
    171 fun TransactionLossComposableUnofferedPreview() {
    172     val t = previewLossTransaction(DenomUnoffered)
    173     Surface {
    174         TransitionLossComposable(t, true, null) {}
    175     }
    176 }