gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 03/03: [wallet] show fees for peer pull credit


From: gnunet
Subject: [taler-taler-android] 03/03: [wallet] show fees for peer pull credit
Date: Wed, 22 Feb 2023 18:32:52 +0100

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit 77cd01bf1a23fc218d265a97925624f066c3236b
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Feb 22 14:29:26 2023 -0300

    [wallet] show fees for peer pull credit
---
 .../main/java/net/taler/wallet/MainViewModel.kt    |  2 +-
 .../java/net/taler/wallet/ReceiveFundsFragment.kt  |  2 +
 .../net/taler/wallet/exchanges/ExchangeManager.kt  | 14 +++-
 .../net/taler/wallet/peer/OutgoingPullFragment.kt  | 25 ++++---
 .../wallet/peer/OutgoingPullIntroComposable.kt     | 79 +++++++++++++---------
 .../wallet/peer/OutgoingPushIntroComposable.kt     |  6 +-
 .../java/net/taler/wallet/peer/OutgoingState.kt    |  9 +++
 .../main/java/net/taler/wallet/peer/PeerManager.kt | 31 +++++++++
 8 files changed, 117 insertions(+), 51 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 255c28b..ed12533 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -99,7 +99,7 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     val transactionManager: TransactionManager = TransactionManager(api, 
viewModelScope)
     val refundManager = RefundManager(api, viewModelScope)
     val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope)
-    val peerManager: PeerManager = PeerManager(api, viewModelScope)
+    val peerManager: PeerManager = PeerManager(api, exchangeManager, 
viewModelScope)
     val settingsManager: SettingsManager = 
SettingsManager(app.applicationContext, viewModelScope)
     val accountManager: AccountManager = AccountManager(api, viewModelScope)
     val depositManager: DepositManager = DepositManager(api, viewModelScope)
diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
index 4fbb09b..0e362ac 100644
--- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt
@@ -62,6 +62,7 @@ import net.taler.wallet.exchanges.ExchangeItem
 class ReceiveFundsFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val exchangeManager get() = model.exchangeManager
+    private val peerManager get() = model.peerManager
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
@@ -107,6 +108,7 @@ class ReceiveFundsFragment : Fragment() {
 
     private fun onPeerPull(amount: Amount) {
         val bundle = bundleOf("amount" to amount.toJSONString())
+        peerManager.checkPeerPullCredit(amount)
         
findNavController().navigate(R.id.action_receiveFunds_to_nav_peer_pull, bundle)
     }
 }
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 5a4c6c2..4a57068 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -17,6 +17,7 @@
 package net.taler.wallet.exchanges
 
 import android.util.Log
+import androidx.annotation.WorkerThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
@@ -81,13 +82,20 @@ class ExchangeManager(
     }
 
     fun findExchangeForCurrency(currency: String): Flow<ExchangeItem?> = flow {
-        val response = api.request("listExchanges", 
ExchangeListResponse.serializer())
+        emit(findExchange(currency))
+    }
+
+    @WorkerThread
+    suspend fun findExchange(currency: String): ExchangeItem? {
         var exchange: ExchangeItem? = null
-        response.onSuccess { exchangeListResponse ->
+        api.request(
+            operation = "listExchanges",
+            serializer = ExchangeListResponse.serializer()
+        ).onSuccess { exchangeListResponse ->
             // just pick the first for now
             exchange = exchangeListResponse.exchanges.find { it.currency == 
currency }
         }
-        emit(exchange)
+        return exchange
     }
 
 }
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
index 5dc1af7..cccae0f 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
@@ -44,22 +44,21 @@ class OutgoingPullFragment : Fragment() {
         val amount = arguments?.getString("amount")?.let {
             Amount.fromJSONString(it)
         } ?: error("no amount passed")
-        val exchangeFlow = 
exchangeManager.findExchangeForCurrency(amount.currency)
         return ComposeView(requireContext()).apply {
             setContent {
                 TalerSurface {
-                    val state = 
peerManager.pullState.collectAsStateLifecycleAware()
-                    if (state.value is OutgoingIntro) {
-                        val exchangeState =
-                            exchangeFlow.collectAsStateLifecycleAware(initial 
= null)
-                        OutgoingPullIntroComposable(
-                            amount = amount,
-                            exchangeState = exchangeState,
-                            onCreateInvoice = 
this@OutgoingPullFragment::onCreateInvoice,
-                        )
-                    } else {
-                        OutgoingPullResultComposable(state.value) {
-                            findNavController().popBackStack()
+                    when (val state = 
peerManager.pullState.collectAsStateLifecycleAware().value) {
+                        is OutgoingIntro, OutgoingChecking, is OutgoingChecked 
-> {
+                            OutgoingPullIntroComposable(
+                                amount = amount,
+                                state = state,
+                                onCreateInvoice = 
this@OutgoingPullFragment::onCreateInvoice,
+                            )
+                        }
+                        OutgoingCreating, is OutgoingResponse, is 
OutgoingError -> {
+                            OutgoingPullResultComposable(state) {
+                                findNavController().popBackStack()
+                            }
                         }
                     }
                 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
index 6d74ba6..a7cd2a8 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
@@ -16,21 +16,19 @@
 
 package net.taler.wallet.peer
 
-import android.annotation.SuppressLint
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Button
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -41,34 +39,36 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
 import net.taler.common.Amount
 import net.taler.wallet.R
 import net.taler.wallet.cleanExchange
 import net.taler.wallet.exchanges.ExchangeItem
+import net.taler.wallet.transactions.AmountType
+import net.taler.wallet.transactions.TransactionAmountComposable
+import net.taler.wallet.transactions.TransactionInfoComposable
+import kotlin.random.Random
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun OutgoingPullIntroComposable(
     amount: Amount,
-    exchangeState: State<ExchangeItem?>,
+    state: OutgoingState,
     onCreateInvoice: (amount: Amount, subject: String, exchange: ExchangeItem) 
-> Unit,
 ) {
     val scrollState = rememberScrollState()
     Column(
         modifier = Modifier
             .fillMaxWidth()
+            .padding(16.dp)
             .verticalScroll(scrollState),
         horizontalAlignment = CenterHorizontally,
     ) {
         var subject by rememberSaveable { mutableStateOf("") }
         val focusRequester = remember { FocusRequester() }
-        val exchangeItem = exchangeState.value
         OutlinedTextField(
             modifier = Modifier
                 .fillMaxWidth()
@@ -101,30 +101,33 @@ fun OutgoingPullIntroComposable(
             text = stringResource(R.string.char_count, subject.length, 
MAX_LENGTH_SUBJECT),
             textAlign = TextAlign.End,
         )
-        Text(
-            modifier = Modifier.padding(horizontal = 16.dp),
-            text = stringResource(id = R.string.amount_chosen),
+        TransactionAmountComposable(
+            label = stringResource(id = R.string.amount_chosen),
+            amount = amount,
+            amountType = AmountType.Positive,
         )
-        Text(
-            modifier = Modifier.padding(16.dp),
-            fontSize = 24.sp,
-            color = colorResource(R.color.green),
-            text = amount.toString(),
-        )
-        Text(
-            modifier = Modifier.padding(horizontal = 16.dp),
-            text = stringResource(R.string.withdraw_exchange),
-        )
-        Text(
-            modifier = Modifier.padding(16.dp),
-            fontSize = 24.sp,
-            text = if (exchangeItem == null) "" else 
cleanExchange(exchangeItem.exchangeBaseUrl),
+        if (state is OutgoingChecked) {
+            val fee = state.amountRaw - state.amountEffective
+            if (!fee.isZero()) TransactionAmountComposable(
+                label = stringResource(id = R.string.withdraw_fees),
+                amount = fee,
+                amountType = AmountType.Negative,
+            )
+        }
+        val exchangeItem = (state as? OutgoingChecked)?.exchangeItem
+        TransactionInfoComposable(
+            label = stringResource(id = R.string.withdraw_exchange),
+            info = if (exchangeItem == null) "" else 
cleanExchange(exchangeItem.exchangeBaseUrl),
         )
         Button(
             modifier = Modifier.padding(16.dp),
-            enabled = subject.isNotBlank() && exchangeItem != null,
+            enabled = subject.isNotBlank() && state is OutgoingChecked,
             onClick = {
-                onCreateInvoice(amount, subject, exchangeItem ?: 
error("clickable without exchange"))
+                onCreateInvoice(
+                    amount,
+                    subject,
+                    exchangeItem ?: error("clickable without exchange")
+                )
             },
         ) {
             Text(text = stringResource(R.string.receive_peer_create_button))
@@ -134,11 +137,25 @@ fun OutgoingPullIntroComposable(
 
 @Preview
 @Composable
-fun PreviewReceiveFundsIntro() {
+fun PreviewReceiveFundsCheckingIntro() {
+    Surface {
+        OutgoingPullIntroComposable(
+            Amount.fromDouble("TESTKUDOS", 42.23),
+            if (Random.nextBoolean()) OutgoingIntro else OutgoingChecking,
+        ) { _, _, _ -> }
+    }
+}
+
+@Preview
+@Composable
+fun PreviewReceiveFundsCheckedIntro() {
     Surface {
-        @SuppressLint("UnrememberedMutableState")
-        val exchangeFlow =
-            mutableStateOf(ExchangeItem("https://example.org";, "TESTKUDOS", 
emptyList()))
-        OutgoingPullIntroComposable(Amount.fromDouble("TESTKUDOS", 42.23), 
exchangeFlow) { _, _, _ -> }
+        val amountRaw = Amount.fromDouble("TESTKUDOS", 42.42)
+        val amountEffective = Amount.fromDouble("TESTKUDOS", 42.23)
+        val exchangeItem = ExchangeItem("https://example.org";, "TESTKUDOS", 
emptyList())
+        OutgoingPullIntroComposable(
+            Amount.fromDouble("TESTKUDOS", 42.23),
+            OutgoingChecked(amountRaw, amountEffective, exchangeItem)
+        ) { _, _, _ -> }
     }
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
index 7d109c7..33e8390 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
@@ -127,9 +127,9 @@ fun PeerPushIntroComposableCheckingPreview() {
 @Composable
 fun PeerPushIntroComposableCheckedPreview() {
     Surface {
-        val amountEffective = Amount.fromDouble("TESTKUDOS", 42.23)
-        val amountRaw = Amount.fromDouble("TESTKUDOS", 42.42)
-        val state = OutgoingChecked(amountEffective, amountRaw)
+        val amountEffective = Amount.fromDouble("TESTKUDOS", 42.42)
+        val amountRaw = Amount.fromDouble("TESTKUDOS", 42.23)
+        val state = OutgoingChecked(amountRaw, amountEffective)
         OutgoingPushIntroComposable(state, amountEffective) { _, _ -> }
     }
 }
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt 
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
index b0a31d2..5673417 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
@@ -20,6 +20,7 @@ import android.graphics.Bitmap
 import kotlinx.serialization.Serializable
 import net.taler.common.Amount
 import net.taler.wallet.backend.TalerErrorInfo
+import net.taler.wallet.exchanges.ExchangeItem
 
 sealed class OutgoingState
 object OutgoingIntro : OutgoingState()
@@ -27,6 +28,7 @@ object OutgoingChecking : OutgoingState()
 data class OutgoingChecked(
     val amountRaw: Amount,
     val amountEffective: Amount,
+    val exchangeItem: ExchangeItem? = null,
 ) : OutgoingState()
 object OutgoingCreating : OutgoingState()
 data class OutgoingResponse(
@@ -38,6 +40,13 @@ data class OutgoingError(
     val info: TalerErrorInfo,
 ) : OutgoingState()
 
+@Serializable
+data class CheckPeerPullCreditResponse(
+    val exchangeBaseUrl: String,
+    val amountRaw: Amount,
+    val amountEffective: Amount,
+)
+
 @Serializable
 data class InitiatePeerPullPaymentResponse(
     /**
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt 
b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
index 7875c6f..f031d44 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
@@ -28,8 +28,11 @@ import net.taler.common.Amount
 import net.taler.common.QrCodeManager
 import net.taler.common.Timestamp
 import net.taler.wallet.TAG
+import net.taler.wallet.backend.TalerErrorCode.UNKNOWN
+import net.taler.wallet.backend.TalerErrorInfo
 import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.exchanges.ExchangeItem
+import net.taler.wallet.exchanges.ExchangeManager
 import org.json.JSONObject
 import java.util.concurrent.TimeUnit.DAYS
 
@@ -37,6 +40,7 @@ const val MAX_LENGTH_SUBJECT = 100
 
 class PeerManager(
     private val api: WalletBackendApi,
+    private val exchangeManager: ExchangeManager,
     private val scope: CoroutineScope,
 ) {
 
@@ -52,6 +56,32 @@ class PeerManager(
     private val _incomingPushState = 
MutableStateFlow<IncomingState>(IncomingChecking)
     val incomingPushState: StateFlow<IncomingState> = _incomingPushState
 
+    fun checkPeerPullCredit(amount: Amount) {
+        _outgoingPullState.value = OutgoingChecking
+        scope.launch(Dispatchers.IO) {
+            val exchangeItem = exchangeManager.findExchange(amount.currency)
+            if (exchangeItem == null) {
+                _outgoingPullState.value = OutgoingError(
+                    TalerErrorInfo(UNKNOWN, "No exchange found for 
${amount.currency}")
+                )
+                return@launch
+            }
+            api.request("checkPeerPullCredit", 
CheckPeerPullCreditResponse.serializer()) {
+                put("exchangeBaseUrl", exchangeItem.exchangeBaseUrl)
+                put("amount", amount.toJSONString())
+            }.onSuccess {
+                _outgoingPullState.value = OutgoingChecked(
+                    amountRaw = it.amountRaw,
+                    amountEffective = it.amountEffective,
+                    exchangeItem = exchangeItem,
+                )
+            }.onError { error ->
+                Log.e(TAG, "got checkPeerPullCredit error result $error")
+                _outgoingPullState.value = OutgoingError(error)
+            }
+        }
+    }
+
     fun initiatePeerPullCredit(amount: Amount, summary: String, exchange: 
ExchangeItem) {
         _outgoingPullState.value = OutgoingCreating
         scope.launch(Dispatchers.IO) {
@@ -86,6 +116,7 @@ class PeerManager(
                 _outgoingPushState.value = OutgoingChecked(
                     amountRaw = response.amountRaw,
                     amountEffective = response.amountEffective,
+                    // FIXME add exchangeItem once available in API
                 )
             }.onError { error ->
                 Log.e(TAG, "got checkPeerPushDebit error result $error")

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]