summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2023-02-22 14:29:26 -0300
committerTorsten Grote <t@grobox.de>2023-02-22 14:29:26 -0300
commit77cd01bf1a23fc218d265a97925624f066c3236b (patch)
treec521353f50d2b42042086cc39692272e6a041024
parent6e99207a7b525536240f32f04f1bb5457f969025 (diff)
downloadtaler-android-77cd01bf1a23fc218d265a97925624f066c3236b.tar.gz
taler-android-77cd01bf1a23fc218d265a97925624f066c3236b.tar.bz2
taler-android-77cd01bf1a23fc218d265a97925624f066c3236b.zip
[wallet] show fees for peer pull credit
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt14
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt25
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt79
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt31
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(
@@ -39,6 +41,13 @@ data class OutgoingError(
) : OutgoingState()
@Serializable
+data class CheckPeerPullCreditResponse(
+ val exchangeBaseUrl: String,
+ val amountRaw: Amount,
+ val amountEffective: Amount,
+)
+
+@Serializable
data class InitiatePeerPullPaymentResponse(
/**
* Taler URI for the other party to make the payment that was requested.
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")