summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-01-02 13:39:41 -0600
committerIván Ávalos <avalos@disroot.org>2024-01-08 14:45:46 -0600
commitf52100135f5784fe81b0377a0034faab17d543df (patch)
treef082d56ac5fd6d1a1ffd659e8540b35766b02a24 /wallet/src/main/java/net/taler
parent325388cf5b726482acb7b1cd3054eaa968c6f3d3 (diff)
downloadtaler-android-f52100135f5784fe81b0377a0034faab17d543df.tar.gz
taler-android-f52100135f5784fe81b0377a0034faab17d543df.tar.bz2
taler-android-f52100135f5784fe81b0377a0034faab17d543df.zip
[wallet] Refactor p2p payments to show tx details when ready
Diffstat (limited to 'wallet/src/main/java/net/taler')
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt (renamed from wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt)128
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt46
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt150
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt (renamed from wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt)75
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt45
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt150
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt12
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt5
10 files changed, 250 insertions, 371 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
index 92bc72e..8efa64c 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
@@ -16,12 +16,16 @@
package net.taler.wallet.peer
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
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.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
@@ -33,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -42,8 +47,11 @@ 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 kotlinx.serialization.json.JsonPrimitive
import net.taler.common.Amount
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode
+import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
import net.taler.wallet.exchanges.ExchangeItem
import net.taler.wallet.transactions.AmountType
@@ -52,6 +60,38 @@ import net.taler.wallet.transactions.TransactionInfoComposable
import kotlin.random.Random
@Composable
+fun OutgoingPullComposable(
+ amount: Amount,
+ state: OutgoingState,
+ onCreateInvoice: (amount: Amount, subject: String, hours: Long, exchange: ExchangeItem) -> Unit,
+ onClose: () -> Unit,
+) {
+ when(state) {
+ is OutgoingChecking, is OutgoingCreating, is OutgoingResponse -> PeerCreatingComposable()
+ is OutgoingIntro, is OutgoingChecked -> OutgoingPullIntroComposable(
+ amount = amount,
+ state = state,
+ onCreateInvoice = onCreateInvoice,
+ )
+ is OutgoingError -> PeerErrorComposable(state, onClose)
+ }
+}
+
+@Composable
+fun PeerCreatingComposable() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .padding(32.dp)
+ .align(Center),
+ )
+ }
+}
+
+@Composable
fun OutgoingPullIntroComposable(
amount: Amount,
state: OutgoingState,
@@ -67,6 +107,7 @@ fun OutgoingPullIntroComposable(
) {
var subject by rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
+
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
@@ -87,9 +128,11 @@ fun OutgoingPullIntroComposable(
)
}
)
+
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
+
Text(
modifier = Modifier
.fillMaxWidth()
@@ -98,11 +141,13 @@ fun OutgoingPullIntroComposable(
text = stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT),
textAlign = TextAlign.End,
)
+
TransactionAmountComposable(
label = stringResource(id = R.string.amount_chosen),
amount = amount,
amountType = AmountType.Positive,
)
+
if (state is OutgoingChecked) {
val fee = state.amountRaw - state.amountEffective
if (!fee.isZero()) TransactionAmountComposable(
@@ -111,16 +156,19 @@ fun OutgoingPullIntroComposable(
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),
)
+
Text(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
text = stringResource(R.string.send_peer_expiration_period),
style = MaterialTheme.typography.bodyMedium,
)
+
var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
var hours by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY.hours) }
ExpirationComposable(
@@ -129,6 +177,7 @@ fun OutgoingPullIntroComposable(
hours = hours,
onOptionChange = { option = it }
) { hours = it }
+
Button(
modifier = Modifier.padding(16.dp),
enabled = subject.isNotBlank() && state is OutgoingChecked,
@@ -146,27 +195,86 @@ fun OutgoingPullIntroComposable(
}
}
+@Composable
+fun PeerErrorComposable(state: OutgoingError, onClose: () -> Unit) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ Text(
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodyLarge,
+ text = state.info.userFacingMsg,
+ )
+
+ Button(
+ modifier = Modifier.padding(16.dp),
+ onClick = onClose,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.error,
+ contentColor = MaterialTheme.colorScheme.onError,
+ ),
+ ) {
+ Text(text = stringResource(R.string.close))
+ }
+ }
+}
+
@Preview
@Composable
-fun PreviewReceiveFundsCheckingIntro() {
+fun PeerPullComposableCreatingPreview() {
Surface {
- OutgoingPullIntroComposable(
- Amount.fromString("TESTKUDOS", "42.23"),
- if (Random.nextBoolean()) OutgoingIntro else OutgoingChecking,
- ) { _, _, _, _ -> }
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingCreating,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
}
}
@Preview
@Composable
-fun PreviewReceiveFundsCheckedIntro() {
+fun PeerPullComposableCheckingPreview() {
+ Surface {
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = if (Random.nextBoolean()) OutgoingIntro else OutgoingChecking,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PeerPullComposableCheckedPreview() {
Surface {
val amountRaw = Amount.fromString("TESTKUDOS", "42.42")
val amountEffective = Amount.fromString("TESTKUDOS", "42.23")
val exchangeItem = ExchangeItem("https://example.org", "TESTKUDOS", emptyList())
- OutgoingPullIntroComposable(
- Amount.fromString("TESTKUDOS", "42.23"),
- OutgoingChecked(amountRaw, amountEffective, exchangeItem)
- ) { _, _, _, _ -> }
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingChecked(amountRaw, amountEffective, exchangeItem),
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
}
}
+
+@Preview
+@Composable
+fun PeerPullComposableErrorPreview() {
+ Surface {
+ val json = mapOf("foo" to JsonPrimitive("bar"))
+ val state = OutgoingError(TalerErrorInfo(TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message", json))
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = state,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
+ }
+} \ No newline at end of file
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 7b1eee8..0205ae0 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
@@ -23,8 +23,11 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import androidx.navigation.findNavController
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.findNavController
+import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -35,8 +38,8 @@ import net.taler.wallet.showError
class OutgoingPullFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
- private val exchangeManager get() = model.exchangeManager
private val peerManager get() = model.peerManager
+ private val transactionManager get() = model.transactionManager
override fun onCreateView(
inflater: LayoutInflater,
@@ -49,20 +52,15 @@ class OutgoingPullFragment : Fragment() {
return ComposeView(requireContext()).apply {
setContent {
TalerSurface {
- when (val state = peerManager.pullState.collectAsStateLifecycleAware().value) {
- is OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- OutgoingPullIntroComposable(
- amount = amount,
- state = state,
- onCreateInvoice = this@OutgoingPullFragment::onCreateInvoice,
- )
+ val state = peerManager.pullState.collectAsStateLifecycleAware().value
+ OutgoingPullComposable(
+ amount = amount,
+ state = state,
+ onCreateInvoice = this@OutgoingPullFragment::onCreateInvoice,
+ onClose = {
+ findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
}
- OutgoingCreating, is OutgoingResponse, is OutgoingError -> {
- OutgoingPullResultComposable(state) {
- findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
- }
- }
- }
+ )
}
}
}
@@ -70,10 +68,20 @@ class OutgoingPullFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- lifecycleScope.launchWhenStarted {
- peerManager.pullState.collect {
- if (it is OutgoingError && model.devMode.value == true) {
- showError(it.info)
+ lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ peerManager.pullState.collect {
+ if (it is OutgoingResponse) {
+ if (transactionManager.selectTransaction(it.transactionId)) {
+ findNavController().navigate(R.id.action_nav_peer_pull_to_nav_transactions_detail_peer)
+ } else {
+ findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
+ }
+ }
+
+ if (it is OutgoingError && model.devMode.value == true) {
+ showError(it.info)
+ }
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
deleted file mode 100644
index de62cda..0000000
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.peer
-
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment.Companion.CenterHorizontally
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import kotlinx.serialization.json.JsonPrimitive
-import net.taler.common.QrCodeManager
-import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
-import net.taler.wallet.backend.TalerErrorInfo
-import net.taler.wallet.compose.QrCodeUriComposable
-import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.compose.getQrCodeSize
-
-@Composable
-fun OutgoingPullResultComposable(state: OutgoingState, onClose: () -> Unit) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(scrollState),
- ) {
- Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
- style = MaterialTheme.typography.titleLarge,
- text = stringResource(id = R.string.receive_peer_invoice_instruction),
- )
- when (state) {
- OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- error("Result composable with ${state::class.simpleName}")
- }
- is OutgoingCreating -> PeerPullCreatingComposable()
- is OutgoingResponse -> PeerPullResponseComposable(state)
- is OutgoingError -> PeerPullErrorComposable(state)
- }
- Button(modifier = Modifier
- .padding(16.dp)
- .align(CenterHorizontally),
- onClick = onClose) {
- Text(text = stringResource(R.string.close))
- }
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPullCreatingComposable() {
- val qrCodeSize = getQrCodeSize()
- CircularProgressIndicator(
- modifier = Modifier
- .padding(32.dp)
- .size(qrCodeSize)
- .align(CenterHorizontally),
- )
-}
-
-@Composable
-private fun ColumnScope.PeerPullResponseComposable(state: OutgoingResponse) {
- QrCodeUriComposable(
- talerUri = state.talerUri,
- clipBoardLabel = "Invoice",
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
- )
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPullErrorComposable(state: OutgoingError) {
- Text(
- modifier = Modifier
- .align(CenterHorizontally)
- .padding(16.dp),
- color = MaterialTheme.colorScheme.error,
- style = MaterialTheme.typography.bodyLarge,
- text = state.info.userFacingMsg,
- )
-}
-
-@Preview
-@Composable
-fun PeerPullCreatingPreview() {
- Surface {
- OutgoingPullResultComposable(OutgoingCreating) {}
- }
-}
-
-@Preview(uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPullResponsePreview() {
- TalerSurface {
- val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
- OutgoingPullResultComposable(response) {}
- }
-}
-
-@Preview(widthDp = 720, uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPullResponseLandscapePreview() {
- TalerSurface {
- val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
- OutgoingPullResultComposable(response) {}
- }
-}
-
-@Preview
-@Composable
-fun PeerPullErrorPreview() {
- Surface {
- val json = mapOf("foo" to JsonPrimitive("bar"))
- val response = OutgoingError(TalerErrorInfo(WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message", json))
- OutgoingPullResultComposable(response) {}
- }
-}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
index 98391be..9d972bf 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
@@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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
@@ -44,11 +43,32 @@ 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 kotlinx.serialization.json.JsonPrimitive
import net.taler.common.Amount
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode
+import net.taler.wallet.backend.TalerErrorInfo
import kotlin.random.Random
@Composable
+fun OutgoingPushComposable(
+ state: OutgoingState,
+ amount: Amount,
+ onSend: (amount: Amount, summary: String, hours: Long) -> Unit,
+ onClose: () -> Unit,
+) {
+ when(state) {
+ is OutgoingChecking, is OutgoingCreating, is OutgoingResponse -> PeerCreatingComposable()
+ is OutgoingIntro, is OutgoingChecked -> OutgoingPushIntroComposable(
+ amount = amount,
+ state = state,
+ onSend = onSend,
+ )
+ is OutgoingError -> PeerErrorComposable(state, onClose)
+ }
+}
+
+@Composable
fun OutgoingPushIntroComposable(
state: OutgoingState,
amount: Amount,
@@ -68,6 +88,7 @@ fun OutgoingPushIntroComposable(
softWrap = false,
style = MaterialTheme.typography.titleLarge,
)
+
if (state is OutgoingChecked) {
val fee = state.amountEffective - state.amountRaw
Text(
@@ -100,9 +121,11 @@ fun OutgoingPushIntroComposable(
)
}
)
+
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
+
Text(
modifier = Modifier
.fillMaxWidth()
@@ -111,11 +134,13 @@ fun OutgoingPushIntroComposable(
text = stringResource(R.string.char_count, subject.length, MAX_LENGTH_SUBJECT),
textAlign = TextAlign.End,
)
+
Text(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
text = stringResource(R.string.send_peer_expiration_period),
style = MaterialTheme.typography.bodyMedium,
)
+
var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
var hours by rememberSaveable { mutableLongStateOf(DEFAULT_EXPIRY.hours) }
ExpirationComposable(
@@ -124,10 +149,12 @@ fun OutgoingPushIntroComposable(
hours = hours,
onOptionChange = { option = it }
) { hours = it }
+
Text(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
text = stringResource(R.string.send_peer_warning),
)
+
Button(
enabled = state is OutgoingChecked && subject.isNotBlank(),
onClick = { onSend(amount, subject, hours) },
@@ -139,20 +166,58 @@ fun OutgoingPushIntroComposable(
@Preview
@Composable
-fun PeerPushIntroComposableCheckingPreview() {
+fun PeerPushComposableCreatingPreview() {
+ Surface {
+ OutgoingPushComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingCreating,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PeerPushComposableCheckingPreview() {
Surface {
val state = if (Random.nextBoolean()) OutgoingIntro else OutgoingChecking
- OutgoingPushIntroComposable(state, Amount.fromString("TESTKUDOS", "42.23")) { _, _, _ -> }
+ OutgoingPushComposable(
+ state = state,
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
}
}
@Preview
@Composable
-fun PeerPushIntroComposableCheckedPreview() {
+fun PeerPushComposableCheckedPreview() {
Surface {
val amountEffective = Amount.fromString("TESTKUDOS", "42.42")
val amountRaw = Amount.fromString("TESTKUDOS", "42.23")
val state = OutgoingChecked(amountRaw, amountEffective)
- OutgoingPushIntroComposable(state, amountEffective) { _, _, _ -> }
+ OutgoingPushComposable(
+ state = state,
+ amount = amountEffective,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
}
}
+
+@Preview
+@Composable
+fun PeerPushComposableErrorPreview() {
+ Surface {
+ val json = mapOf("foo" to JsonPrimitive("bar"))
+ val state = OutgoingError(TalerErrorInfo(TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message", json))
+ OutgoingPushComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = state,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
+ }
+} \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
index c586a1d..8cd45b0 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
@@ -24,9 +24,12 @@ import androidx.activity.OnBackPressedCallback
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
+import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -37,6 +40,7 @@ import net.taler.wallet.showError
class OutgoingPushFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
private val peerManager get() = model.peerManager
+ private val transactionManager get() = model.transactionManager
// hacky way to change back action until we have navigation for compose
private val backPressedCallback = object : OnBackPressedCallback(false) {
@@ -61,22 +65,15 @@ class OutgoingPushFragment : Fragment() {
return ComposeView(requireContext()).apply {
setContent {
TalerSurface {
- when (val state = peerManager.pushState.collectAsStateLifecycleAware().value) {
- is OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- backPressedCallback.isEnabled = false
- OutgoingPushIntroComposable(
- state = state,
- amount = amount,
- onSend = this@OutgoingPushFragment::onSend,
- )
+ val state = peerManager.pushState.collectAsStateLifecycleAware().value
+ OutgoingPushComposable(
+ amount = amount,
+ state = state,
+ onSend = this@OutgoingPushFragment::onSend,
+ onClose = {
+ findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
}
- OutgoingCreating, is OutgoingResponse, is OutgoingError -> {
- backPressedCallback.isEnabled = true
- OutgoingPushResultComposable(state) {
- findNavController().navigate(R.id.action_nav_peer_push_to_nav_main)
- }
- }
- }
+ )
}
}
}
@@ -84,10 +81,20 @@ class OutgoingPushFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- lifecycleScope.launchWhenStarted {
- peerManager.pushState.collect {
- if (it is OutgoingError && model.devMode.value == true) {
- showError(it.info)
+ lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ peerManager.pushState.collect {
+ if (it is OutgoingResponse) {
+ if (transactionManager.selectTransaction(it.transactionId)) {
+ findNavController().navigate(R.id.action_nav_peer_push_to_nav_transactions_detail_peer)
+ } else {
+ findNavController().navigate(R.id.action_nav_peer_push_to_nav_main)
+ }
+ }
+
+ if (it is OutgoingError && model.devMode.value == true) {
+ showError(it.info)
+ }
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
deleted file mode 100644
index 0a4ee70..0000000
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.peer
-
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment.Companion.CenterHorizontally
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import kotlinx.serialization.json.JsonPrimitive
-import net.taler.common.QrCodeManager
-import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
-import net.taler.wallet.backend.TalerErrorInfo
-import net.taler.wallet.compose.QrCodeUriComposable
-import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.compose.getQrCodeSize
-
-@Composable
-fun OutgoingPushResultComposable(state: OutgoingState, onClose: () -> Unit) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(scrollState),
- ) {
- Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
- style = MaterialTheme.typography.titleLarge,
- text = stringResource(id = R.string.send_peer_payment_instruction),
- )
- when (state) {
- OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- error("Result composable with ${state::class.simpleName}")
- }
- is OutgoingCreating -> PeerPushCreatingComposable()
- is OutgoingResponse -> PeerPushResponseComposable(state)
- is OutgoingError -> PeerPushErrorComposable(state)
- }
- Button(modifier = Modifier
- .padding(16.dp)
- .align(CenterHorizontally),
- onClick = onClose) {
- Text(text = stringResource(R.string.close))
- }
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPushCreatingComposable() {
- val qrCodeSize = getQrCodeSize()
- CircularProgressIndicator(
- modifier = Modifier
- .padding(32.dp)
- .size(qrCodeSize)
- .align(CenterHorizontally),
- )
-}
-
-@Composable
-private fun ColumnScope.PeerPushResponseComposable(state: OutgoingResponse) {
- QrCodeUriComposable(
- talerUri = state.talerUri,
- clipBoardLabel = "Invoice",
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
- )
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPushErrorComposable(state: OutgoingError) {
- Text(
- modifier = Modifier
- .align(CenterHorizontally)
- .padding(16.dp),
- color = MaterialTheme.colorScheme.error,
- style = MaterialTheme.typography.bodyLarge,
- text = state.info.userFacingMsg,
- )
-}
-
-@Preview
-@Composable
-fun PeerPushCreatingPreview() {
- Surface {
- OutgoingPushResultComposable(OutgoingCreating) {}
- }
-}
-
-@Preview(uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPushResponsePreview() {
- TalerSurface {
- val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
- OutgoingPushResultComposable(response) {}
- }
-}
-
-@Preview(widthDp = 720, uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPushResponseLandscapePreview() {
- TalerSurface {
- val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
- OutgoingPushResultComposable(response) {}
- }
-}
-
-@Preview
-@Composable
-fun PeerPushErrorPreview() {
- Surface {
- val json = mapOf("foo" to JsonPrimitive("bar"))
- val response = OutgoingError(TalerErrorInfo(WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message", json))
- OutgoingPushResultComposable(response) {}
- }
-}
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 e53dd40..05da294 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
@@ -16,7 +16,6 @@
package net.taler.wallet.peer
-import android.graphics.Bitmap
import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.wallet.backend.TalerErrorInfo
@@ -32,8 +31,7 @@ data class OutgoingChecked(
) : OutgoingState()
object OutgoingCreating : OutgoingState()
data class OutgoingResponse(
- val talerUri: String,
- val qrCode: Bitmap,
+ val transactionId: String,
) : OutgoingState()
data class OutgoingError(
@@ -49,10 +47,7 @@ data class CheckPeerPullCreditResponse(
@Serializable
data class InitiatePeerPullPaymentResponse(
- /**
- * Taler URI for the other party to make the payment that was requested.
- */
- val talerUri: String,
+ val transactionId: String,
)
@Serializable
@@ -64,8 +59,5 @@ data class CheckPeerPushDebitResponse(
@Serializable
data class InitiatePeerPushDebitResponse(
val exchangeBaseUrl: String,
- @Deprecated("Will be removed in future version")
- val talerUri: String,
- // TODO bring the user to that transaction and only show QR when in Pending/Ready state
val transactionId: String,
)
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 6e65e0b..5bd2b0b 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
@@ -25,7 +25,6 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
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
@@ -95,8 +94,7 @@ class PeerManager(
put("purse_expiration", JSONObject(Json.encodeToString(expiry)))
})
}.onSuccess {
- val qrCode = QrCodeManager.makeQrCode(it.talerUri)
- _outgoingPullState.value = OutgoingResponse(it.talerUri, qrCode)
+ _outgoingPullState.value = OutgoingResponse(it.transactionId)
}.onError { error ->
Log.e(TAG, "got initiatePeerPullCredit error result $error")
_outgoingPullState.value = OutgoingError(error)
@@ -138,9 +136,7 @@ class PeerManager(
put("purse_expiration", JSONObject(Json.encodeToString(expiry)))
})
}.onSuccess { response ->
- // TODO bring the user to that transaction and only show QR when in Pending/Ready state
- val qrCode = QrCodeManager.makeQrCode(response.talerUri)
- _outgoingPushState.value = OutgoingResponse(response.talerUri, qrCode)
+ _outgoingPushState.value = OutgoingResponse(response.transactionId)
}.onError { error ->
Log.e(TAG, "got initiatePeerPushDebit error result $error")
_outgoingPushState.value = OutgoingError(error)
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
index 2587ea9..4c79e5b 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
@@ -69,7 +69,7 @@ fun ColumnScope.TransactionPeerPushDebitComposable(t: TransactionPeerPushDebit)
label = stringResource(id = R.string.send_peer_purpose),
info = t.info.summary ?: "",
)
- if (t.txState == TransactionState(Pending, Ready)) {
+ if (t.txState == TransactionState(Pending, Ready) && t.talerUri != null) {
QrCodeUriComposable(
talerUri = t.talerUri,
clipBoardLabel = "Push payment",
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 681fadb..de47f68 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -254,14 +254,17 @@ sealed class AccountRestriction {
// for a description of the posix-egrep syntax. Applications
// may support regexes with additional features, but exchanges
// must not use such regexes.
+ @SerialName("payto_regex")
val paytoRegex: String,
// Hint for a human to understand the restriction
// (that is hopefully easier to comprehend than the regex itself).
+ @SerialName("human_hint")
val humanHint: String,
// Map from IETF BCP 47 language tags to localized
// human hints.
+ @SerialName("human_hint_i18n")
val humanHintI18n: Map<String, String>? = null,
): AccountRestriction()
}
@@ -483,7 +486,7 @@ class TransactionPeerPushDebit(
override val amountRaw: Amount,
override val amountEffective: Amount,
val info: PeerInfoShort,
- val talerUri: String,
+ val talerUri: String? = null,
// val completed: Boolean, definitely
) : Transaction() {
override val icon = R.drawable.ic_cash_usd_outline