aboutsummaryrefslogtreecommitdiff
path: root/wallet/src
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src')
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainActivity.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerIncomingState.kt50
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt86
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerOutgoingState.kt47
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerPullFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerPullIntroComposable.kt (renamed from wallet/src/main/java/net/taler/wallet/peer/PeerPullComposable.kt)0
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerPullPaymentComposable.kt223
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerPullResultComposable.kt22
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerPushResultComposable.kt22
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PullPaymentFragment.kt68
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt77
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt3
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt4
-rw-r--r--wallet/src/main/res/navigation/nav_graph.xml14
-rw-r--r--wallet/src/main/res/values/strings.xml4
16 files changed, 562 insertions, 68 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index ea604c4..2797a69 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -253,6 +253,10 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
model.showProgressBar.value = true
model.refundManager.refund(u).observe(this, Observer(::onRefundResponse))
}
+ action.startsWith("pay-pull/") -> {
+ nav.navigate(R.id.action_global_prompt_pull_payment)
+ model.peerManager.checkPeerPullPayment(u)
+ }
else -> {
showError(R.string.error_unsupported_uri, "From: $from\nURI: $u")
}
diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
index c67b345..27f2c96 100644
--- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt
@@ -28,7 +28,7 @@ import androidx.navigation.findNavController
import com.google.android.material.composethemeadapter.MdcTheme
import net.taler.common.Amount
import net.taler.wallet.compose.collectAsStateLifecycleAware
-import net.taler.wallet.peer.PeerPaymentIntro
+import net.taler.wallet.peer.PeerOutgoingIntro
import net.taler.wallet.peer.PeerPushIntroComposable
import net.taler.wallet.peer.PeerPushResultComposable
@@ -45,7 +45,7 @@ class SendFundsFragment : Fragment() {
MdcTheme {
Surface {
val state = peerManager.pushState.collectAsStateLifecycleAware()
- if (state.value is PeerPaymentIntro) {
+ if (state.value is PeerOutgoingIntro) {
val currency = transactionManager.selectedCurrency
?: error("No currency selected")
PeerPushIntroComposable(currency, this@SendFundsFragment::onSend)
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerIncomingState.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerIncomingState.kt
new file mode 100644
index 0000000..c021c2f
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerIncomingState.kt
@@ -0,0 +1,50 @@
+/*
+ * 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 kotlinx.serialization.Serializable
+import net.taler.common.Amount
+import net.taler.wallet.backend.TalerErrorInfo
+
+sealed class PeerIncomingState
+object PeerIncomingChecking : PeerIncomingState()
+open class PeerIncomingTerms(
+ val amount: Amount,
+ val contractTerms: PeerContractTerms,
+ val id: String,
+) : PeerIncomingState()
+
+class PeerIncomingAccepting(s: PeerIncomingTerms) :
+ PeerIncomingTerms(s.amount, s.contractTerms, s.id)
+
+object PeerIncomingAccepted : PeerIncomingState()
+data class PeerIncomingError(
+ val info: TalerErrorInfo,
+) : PeerIncomingState()
+
+@Serializable
+data class PeerContractTerms(
+ val summary: String,
+ val amount: Amount,
+)
+
+@Serializable
+data class CheckPeerPullPaymentResponse(
+ val amount: Amount,
+ val contractTerms: PeerContractTerms,
+ val peerPullPaymentIncomingId: 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 898dcfd..5bfd030 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
@@ -16,18 +16,15 @@
package net.taler.wallet.peer
-import android.graphics.Bitmap
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
-import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.common.QrCodeManager
import net.taler.wallet.TAG
-import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.exchanges.ExchangeItem
import org.json.JSONObject
@@ -37,14 +34,17 @@ class PeerManager(
private val scope: CoroutineScope,
) {
- private val _pullState = MutableStateFlow<PeerPaymentState>(PeerPaymentIntro)
- val pullState: StateFlow<PeerPaymentState> = _pullState
+ private val _pullState = MutableStateFlow<PeerOutgoingState>(PeerOutgoingIntro)
+ val pullState: StateFlow<PeerOutgoingState> = _pullState
- private val _pushState = MutableStateFlow<PeerPaymentState>(PeerPaymentIntro)
- val pushState: StateFlow<PeerPaymentState> = _pushState
+ private val _pushState = MutableStateFlow<PeerOutgoingState>(PeerOutgoingIntro)
+ val pushState: StateFlow<PeerOutgoingState> = _pushState
+
+ private val _paymentState = MutableStateFlow<PeerIncomingState>(PeerIncomingChecking)
+ val paymentState: StateFlow<PeerIncomingState> = _paymentState
fun initiatePullPayment(amount: Amount, exchange: ExchangeItem) {
- _pullState.value = PeerPaymentCreating
+ _pullState.value = PeerOutgoingCreating
scope.launch(Dispatchers.IO) {
api.request("initiatePeerPullPayment", InitiatePeerPullPaymentResponse.serializer()) {
put("exchangeBaseUrl", exchange.exchangeBaseUrl)
@@ -54,20 +54,20 @@ class PeerManager(
})
}.onSuccess {
val qrCode = QrCodeManager.makeQrCode(it.talerUri)
- _pullState.value = PeerPaymentResponse(it.talerUri, qrCode)
+ _pullState.value = PeerOutgoingResponse(it.talerUri, qrCode)
}.onError { error ->
Log.e(TAG, "got initiatePeerPullPayment error result $error")
- _pullState.value = PeerPaymentError(error)
+ _pullState.value = PeerOutgoingError(error)
}
}
}
fun resetPullPayment() {
- _pullState.value = PeerPaymentIntro
+ _pullState.value = PeerOutgoingIntro
}
fun initiatePeerPushPayment(amount: Amount, summary: String) {
- _pushState.value = PeerPaymentCreating
+ _pushState.value = PeerOutgoingCreating
scope.launch(Dispatchers.IO) {
api.request("initiatePeerPushPayment", InitiatePeerPushPaymentResponse.serializer()) {
put("amount", amount.toJSONString())
@@ -76,42 +76,48 @@ class PeerManager(
})
}.onSuccess { response ->
val qrCode = QrCodeManager.makeQrCode(response.talerUri)
- _pushState.value = PeerPaymentResponse(response.talerUri, qrCode)
+ _pushState.value = PeerOutgoingResponse(response.talerUri, qrCode)
}.onError { error ->
Log.e(TAG, "got initiatePeerPushPayment error result $error")
- _pushState.value = PeerPaymentError(error)
+ _pushState.value = PeerOutgoingError(error)
}
}
}
fun resetPushPayment() {
- _pushState.value = PeerPaymentIntro
+ _pushState.value = PeerOutgoingIntro
}
-}
-
-sealed class PeerPaymentState
-object PeerPaymentIntro : PeerPaymentState()
-object PeerPaymentCreating : PeerPaymentState()
-data class PeerPaymentResponse(
- val talerUri: String,
- val qrCode: Bitmap,
-) : PeerPaymentState()
-
-data class PeerPaymentError(
- val info: TalerErrorInfo,
-) : PeerPaymentState()
+ fun checkPeerPullPayment(talerUri: String) {
+ _paymentState.value = PeerIncomingChecking
+ scope.launch(Dispatchers.IO) {
+ api.request("checkPeerPullPayment", CheckPeerPullPaymentResponse.serializer()) {
+ put("talerUri", talerUri)
+ }.onSuccess { response ->
+ _paymentState.value = PeerIncomingTerms(
+ amount = response.amount,
+ contractTerms = response.contractTerms,
+ id = response.peerPullPaymentIncomingId,
+ )
+ }.onError { error ->
+ Log.e(TAG, "got checkPeerPushPayment error result $error")
+ _paymentState.value = PeerIncomingError(error)
+ }
+ }
+ }
-@Serializable
-data class InitiatePeerPullPaymentResponse(
- /**
- * Taler URI for the other party to make the payment that was requested.
- */
- val talerUri: String,
-)
+ fun acceptPeerPullPayment(terms: PeerIncomingTerms) {
+ _paymentState.value = PeerIncomingAccepting(terms)
+ scope.launch(Dispatchers.IO) {
+ api.request<Unit>("acceptPeerPullPayment") {
+ put("peerPullPaymentIncomingId", terms.id)
+ }.onSuccess {
+ _paymentState.value = PeerIncomingAccepted
+ }.onError { error ->
+ Log.e(TAG, "got checkPeerPushPayment error result $error")
+ _paymentState.value = PeerIncomingError(error)
+ }
+ }
+ }
-@Serializable
-data class InitiatePeerPushPaymentResponse(
- val exchangeBaseUrl: String,
- val talerUri: String,
-)
+}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerOutgoingState.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerOutgoingState.kt
new file mode 100644
index 0000000..0b6b2a8
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerOutgoingState.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.graphics.Bitmap
+import kotlinx.serialization.Serializable
+import net.taler.wallet.backend.TalerErrorInfo
+
+sealed class PeerOutgoingState
+object PeerOutgoingIntro : PeerOutgoingState()
+object PeerOutgoingCreating : PeerOutgoingState()
+data class PeerOutgoingResponse(
+ val talerUri: String,
+ val qrCode: Bitmap,
+) : PeerOutgoingState()
+
+data class PeerOutgoingError(
+ val info: TalerErrorInfo,
+) : PeerOutgoingState()
+
+@Serializable
+data class InitiatePeerPullPaymentResponse(
+ /**
+ * Taler URI for the other party to make the payment that was requested.
+ */
+ val talerUri: String,
+)
+
+@Serializable
+data class InitiatePeerPushPaymentResponse(
+ val exchangeBaseUrl: String,
+ val talerUri: String,
+)
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerPullFragment.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerPullFragment.kt
index d38ae34..be79e9d 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerPullFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerPullFragment.kt
@@ -51,7 +51,7 @@ class PeerPullFragment : Fragment() {
MdcTheme {
Surface {
val state = peerManager.pullState.collectAsStateLifecycleAware()
- if (state.value is PeerPaymentIntro) {
+ if (state.value is PeerOutgoingIntro) {
val exchangeState =
exchangeFlow.collectAsStateLifecycleAware(initial = null)
PeerPullIntroComposable(
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerPullComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerPullIntroComposable.kt
index 02f2c7c..02f2c7c 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerPullComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerPullIntroComposable.kt
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerPullPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerPullPaymentComposable.kt
new file mode 100644
index 0000000..fff74ea
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerPullPaymentComposable.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.annotation.SuppressLint
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Card
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
+import androidx.compose.ui.Alignment.Companion.End
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import net.taler.common.Amount
+import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorInfo
+
+@Composable
+fun PeerPullPaymentComposable(
+ state: State<PeerIncomingState>,
+ onAccept: (PeerIncomingTerms) -> Unit,
+) {
+ val scrollState = rememberScrollState()
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState),
+ ) {
+ Text(
+ modifier = Modifier
+ .padding(16.dp)
+ .align(CenterHorizontally),
+ text = stringResource(id = R.string.pay_peer_intro))
+ when (val s = state.value) {
+ PeerIncomingChecking -> PeerPullCheckingComposable()
+ is PeerIncomingTerms -> PeerPullTermsComposable(s, onAccept)
+ is PeerIncomingAccepting -> PeerPullTermsComposable(s, onAccept)
+ PeerIncomingAccepted -> {
+ // we navigate away, don't show anything
+ }
+ is PeerIncomingError -> PeerPullErrorComposable(s)
+ }
+ }
+}
+
+@Composable
+fun ColumnScope.PeerPullCheckingComposable() {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .align(CenterHorizontally)
+ .fillMaxSize(0.75f),
+ )
+}
+
+@Composable
+fun ColumnScope.PeerPullTermsComposable(
+ terms: PeerIncomingTerms,
+ onAccept: (PeerIncomingTerms) -> Unit,
+) {
+ Text(
+ modifier = Modifier
+ .padding(16.dp)
+ .align(CenterHorizontally),
+ text = terms.contractTerms.summary,
+ style = MaterialTheme.typography.h5,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ Card(modifier = Modifier.fillMaxWidth()) {
+ Column(
+ modifier = Modifier.padding(8.dp)
+ ) {
+ Row(
+ modifier = Modifier.align(End),
+ ) {
+ Text(
+ text = stringResource(id = R.string.payment_label_amount_total),
+ style = MaterialTheme.typography.body1,
+ )
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ text = terms.contractTerms.amount.toString(),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ val fee =
+ Amount.zero(terms.amount.currency) // terms.amount - terms.contractTerms.amount
+ if (!fee.isZero()) {
+ Text(
+ modifier = Modifier.align(End),
+ text = stringResource(id = R.string.payment_fee, fee),
+ style = MaterialTheme.typography.body1,
+ )
+ }
+ if (terms is PeerIncomingAccepting) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .padding(end = 64.dp)
+ .align(End),
+ )
+ } else {
+ Button(
+ modifier = Modifier
+ .align(End)
+ .padding(top = 8.dp),
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = colorResource(R.color.green),
+ contentColor = Color.White,
+ ),
+ onClick = { onAccept(terms) },
+ ) {
+ Text(
+ text = stringResource(id = R.string.payment_button_confirm),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ColumnScope.PeerPullErrorComposable(s: PeerIncomingError) {
+ Text(
+ modifier = Modifier
+ .align(CenterHorizontally)
+ .padding(horizontal = 32.dp),
+ text = s.info.userFacingMsg,
+ style = MaterialTheme.typography.h5,
+ color = colorResource(id = R.color.red),
+ )
+}
+
+@Preview
+@Composable
+fun PeerPullCheckingPreview() {
+ Surface {
+ @SuppressLint("UnrememberedMutableState")
+ val s = mutableStateOf(PeerIncomingChecking)
+ PeerPullPaymentComposable(s) {}
+ }
+}
+
+@Preview
+@Composable
+fun PeerPullTermsPreview() {
+ Surface {
+ val terms = PeerIncomingTerms(
+ amount = Amount.fromDouble("TESTKUDOS", 42.23),
+ contractTerms = PeerContractTerms(
+ summary = "This is a long test summary that can be more than one line long for sure",
+ amount = Amount.fromDouble("TESTKUDOS", 23.42),
+ ),
+ id = "ID123",
+ )
+
+ @SuppressLint("UnrememberedMutableState")
+ val s = mutableStateOf(terms)
+ PeerPullPaymentComposable(s) {}
+ }
+}
+
+@Preview
+@Composable
+fun PeerPullAcceptingPreview() {
+ Surface {
+ val terms = PeerIncomingTerms(
+ amount = Amount.fromDouble("TESTKUDOS", 42.23),
+ contractTerms = PeerContractTerms(
+ summary = "This is a long test summary that can be more than one line long for sure",
+ amount = Amount.fromDouble("TESTKUDOS", 23.42),
+ ),
+ id = "ID123",
+ )
+
+ @SuppressLint("UnrememberedMutableState")
+ val s = mutableStateOf(PeerIncomingAccepting(terms))
+ PeerPullPaymentComposable(s) {}
+ }
+}
+
+@Preview
+@Composable
+fun PeerPullPayErrorPreview() {
+ Surface {
+ @SuppressLint("UnrememberedMutableState")
+ val s = mutableStateOf(PeerIncomingError(TalerErrorInfo(42, "hint", "msg")))
+ PeerPullPaymentComposable(s) {}
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerPullResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerPullResultComposable.kt
index 0b9b546..d37ca4b 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerPullResultComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerPullResultComposable.kt
@@ -55,7 +55,7 @@ import net.taler.wallet.compose.getQrCodeSize
import org.json.JSONObject
@Composable
-fun PeerPullResultComposable(state: PeerPaymentState, onClose: () -> Unit) {
+fun PeerPullResultComposable(state: PeerOutgoingState, onClose: () -> Unit) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
@@ -68,10 +68,10 @@ fun PeerPullResultComposable(state: PeerPaymentState, onClose: () -> Unit) {
text = stringResource(id = R.string.receive_peer_invoice_instruction),
)
when (state) {
- PeerPaymentIntro -> error("Result composable with PullPaymentIntro")
- is PeerPaymentCreating -> PeerPullCreatingComposable()
- is PeerPaymentResponse -> PeerPullResponseComposable(state)
- is PeerPaymentError -> PeerPullErrorComposable(state)
+ PeerOutgoingIntro -> error("Result composable with PullPaymentIntro")
+ is PeerOutgoingCreating -> PeerPullCreatingComposable()
+ is PeerOutgoingResponse -> PeerPullResponseComposable(state)
+ is PeerOutgoingError -> PeerPullErrorComposable(state)
}
Button(modifier = Modifier
.padding(16.dp)
@@ -94,7 +94,7 @@ private fun ColumnScope.PeerPullCreatingComposable() {
}
@Composable
-private fun ColumnScope.PeerPullResponseComposable(state: PeerPaymentResponse) {
+private fun ColumnScope.PeerPullResponseComposable(state: PeerOutgoingResponse) {
val qrCodeSize = getQrCodeSize()
Image(
modifier = Modifier
@@ -135,7 +135,7 @@ private fun ColumnScope.PeerPullResponseComposable(state: PeerPaymentResponse) {
}
@Composable
-private fun ColumnScope.PeerPullErrorComposable(state: PeerPaymentError) {
+private fun ColumnScope.PeerPullErrorComposable(state: PeerOutgoingError) {
Text(
modifier = Modifier
.align(CenterHorizontally)
@@ -150,7 +150,7 @@ private fun ColumnScope.PeerPullErrorComposable(state: PeerPaymentError) {
@Composable
fun PeerPullCreatingPreview() {
Surface {
- PeerPullResultComposable(PeerPaymentCreating) {}
+ PeerPullResultComposable(PeerOutgoingCreating) {}
}
}
@@ -159,7 +159,7 @@ fun PeerPullCreatingPreview() {
fun PeerPullResponsePreview() {
Surface {
val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = PeerPaymentResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
+ val response = PeerOutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
PeerPullResultComposable(response) {}
}
}
@@ -169,7 +169,7 @@ fun PeerPullResponsePreview() {
fun PeerPullResponseLandscapePreview() {
Surface {
val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = PeerPaymentResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
+ val response = PeerOutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
PeerPullResultComposable(response) {}
}
}
@@ -179,7 +179,7 @@ fun PeerPullResponseLandscapePreview() {
fun PeerPullErrorPreview() {
Surface {
val json = JSONObject().apply { put("foo", "bar") }
- val response = PeerPaymentError(TalerErrorInfo(42, "hint", "message", json))
+ val response = PeerOutgoingError(TalerErrorInfo(42, "hint", "message", json))
PeerPullResultComposable(response) {}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerPushResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerPushResultComposable.kt
index f3d1a79..b33fc4f 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerPushResultComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerPushResultComposable.kt
@@ -55,7 +55,7 @@ import net.taler.wallet.compose.getQrCodeSize
import org.json.JSONObject
@Composable
-fun PeerPushResultComposable(state: PeerPaymentState, onClose: () -> Unit) {
+fun PeerPushResultComposable(state: PeerOutgoingState, onClose: () -> Unit) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
@@ -68,10 +68,10 @@ fun PeerPushResultComposable(state: PeerPaymentState, onClose: () -> Unit) {
text = stringResource(id = R.string.send_peer_payment_instruction),
)
when (state) {
- PeerPaymentIntro -> error("Result composable with PullPaymentIntro")
- is PeerPaymentCreating -> PeerPushCreatingComposable()
- is PeerPaymentResponse -> PeerPushResponseComposable(state)
- is PeerPaymentError -> PeerPushErrorComposable(state)
+ PeerOutgoingIntro -> error("Result composable with PullPaymentIntro")
+ is PeerOutgoingCreating -> PeerPushCreatingComposable()
+ is PeerOutgoingResponse -> PeerPushResponseComposable(state)
+ is PeerOutgoingError -> PeerPushErrorComposable(state)
}
Button(modifier = Modifier
.padding(16.dp)
@@ -94,7 +94,7 @@ private fun ColumnScope.PeerPushCreatingComposable() {
}
@Composable
-private fun ColumnScope.PeerPushResponseComposable(state: PeerPaymentResponse) {
+private fun ColumnScope.PeerPushResponseComposable(state: PeerOutgoingResponse) {
val qrCodeSize = getQrCodeSize()
Image(
modifier = Modifier
@@ -135,7 +135,7 @@ private fun ColumnScope.PeerPushResponseComposable(state: PeerPaymentResponse) {
}
@Composable
-private fun ColumnScope.PeerPushErrorComposable(state: PeerPaymentError) {
+private fun ColumnScope.PeerPushErrorComposable(state: PeerOutgoingError) {
Text(
modifier = Modifier
.align(CenterHorizontally)
@@ -150,7 +150,7 @@ private fun ColumnScope.PeerPushErrorComposable(state: PeerPaymentError) {
@Composable
fun PeerPushCreatingPreview() {
Surface {
- PeerPushResultComposable(PeerPaymentCreating) {}
+ PeerPushResultComposable(PeerOutgoingCreating) {}
}
}
@@ -159,7 +159,7 @@ fun PeerPushCreatingPreview() {
fun PeerPushResponsePreview() {
Surface {
val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = PeerPaymentResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
+ val response = PeerOutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
PeerPushResultComposable(response) {}
}
}
@@ -169,7 +169,7 @@ fun PeerPushResponsePreview() {
fun PeerPushResponseLandscapePreview() {
Surface {
val talerUri = "https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = PeerPaymentResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
+ val response = PeerOutgoingResponse(talerUri, QrCodeManager.makeQrCode(talerUri))
PeerPushResultComposable(response) {}
}
}
@@ -179,7 +179,7 @@ fun PeerPushResponseLandscapePreview() {
fun PeerPushErrorPreview() {
Surface {
val json = JSONObject().apply { put("foo", "bar") }
- val response = PeerPaymentError(TalerErrorInfo(42, "hint", "message", json))
+ val response = PeerOutgoingError(TalerErrorInfo(42, "hint", "message", json))
PeerPushResultComposable(response) {}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PullPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/peer/PullPaymentFragment.kt
new file mode 100644
index 0000000..71b1bcc
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/peer/PullPaymentFragment.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.material.Surface
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.compose.collectAsStateLifecycleAware
+
+class PullPaymentFragment : Fragment() {
+ private val model: MainViewModel by activityViewModels()
+ private val peerManager get() = model.peerManager
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ lifecycleScope.launchWhenResumed {
+ peerManager.paymentState.collect {
+ if (it is PeerIncomingAccepted) {
+ findNavController().navigate(R.id.action_promptPullPayment_to_nav_main)
+ }
+ }
+ }
+ return ComposeView(requireContext()).apply {
+ setContent {
+ MdcTheme {
+ Surface {
+ val state = peerManager.paymentState.collectAsStateLifecycleAware()
+ PeerPullPaymentComposable(state) { terms ->
+ peerManager.acceptPeerPullPayment(terms)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ activity?.setTitle(R.string.pay_peer_title)
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt
new file mode 100644
index 0000000..823126b
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt
@@ -0,0 +1,77 @@
+/*
+ * 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 androidx.compose.material.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+import net.taler.wallet.R
+import net.taler.wallet.transactions.AmountType
+import net.taler.wallet.transactions.PeerInfoShort
+import net.taler.wallet.transactions.TransactionAmountComposable
+import net.taler.wallet.transactions.TransactionInfoComposable
+import net.taler.wallet.transactions.TransactionPeerComposable
+import net.taler.wallet.transactions.TransactionPeerPullDebit
+
+@Composable
+fun TransactionPeerPullDebitComposable(t: TransactionPeerPullDebit) {
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.transaction_paid),
+ amount = t.amountEffective,
+ amountType = AmountType.Negative,
+ )
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.transaction_order_total),
+ amount = t.amountRaw,
+ amountType = AmountType.Neutral,
+ )
+ val fee = t.amountEffective - t.amountRaw
+ if (!fee.isZero()) {
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.withdraw_fees),
+ amount = fee,
+ amountType = AmountType.Negative,
+ )
+ }
+ TransactionInfoComposable(
+ label = stringResource(id = R.string.withdraw_manual_ready_subject),
+ info = t.info.summary ?: "",
+ )
+}
+
+@Preview
+@Composable
+fun TransactionPeerPullDebitPreview() {
+ val t = TransactionPeerPullDebit(
+ transactionId = "transactionId",
+ timestamp = Timestamp(System.currentTimeMillis() - 360 * 60 * 1000),
+ pending = true,
+ exchangeBaseUrl = "https://exchange.example.org/",
+ amountRaw = Amount.fromDouble("TESTKUDOS", 42.1337),
+ amountEffective = Amount.fromDouble("TESTKUDOS", 42.23),
+ info = PeerInfoShort(
+ expiration = Timestamp(System.currentTimeMillis() + 60 * 60 * 1000),
+ summary = "test invoice",
+ ),
+ )
+ Surface {
+ TransactionPeerComposable(t) {}
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt
index f1afb41..9b0c208 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt
@@ -49,6 +49,7 @@ import net.taler.common.Amount
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
import net.taler.wallet.peer.TransactionPeerPullCreditComposable
+import net.taler.wallet.peer.TransactionPeerPullDebitComposable
import net.taler.wallet.peer.TransactionPeerPushDebitComposable
class TransactionPeerFragment : TransactionDetailFragment() {
@@ -89,7 +90,7 @@ fun TransactionPeerComposable(t: Transaction, onDelete: () -> Unit) {
when (t) {
is TransactionPeerPullCredit -> TransactionPeerPullCreditComposable(t)
is TransactionPeerPushCredit -> TODO()
- is TransactionPeerPullDebit -> TODO()
+ is TransactionPeerPullDebit -> TransactionPeerPullDebitComposable(t)
is TransactionPeerPushDebit -> TransactionPeerPushDebitComposable(t)
else -> error("unexpected transaction: ${t::class.simpleName}")
}
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 6f72567..6ef6c88 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -280,9 +280,9 @@ class TransactionPeerPullDebit(
@Transient
override val amountType = AmountType.Negative
override fun getTitle(context: Context): String {
- return context.getString(R.string.transaction_peer_push_debit)
+ return context.getString(R.string.transaction_peer_pull_debit)
}
- override val generalTitleRes = R.string.payment_title
+ override val generalTitleRes = R.string.transaction_peer_pull_debit
}
/**
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
index e3d526e..3170216 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -141,6 +141,16 @@
</fragment>
<fragment
+ android:id="@+id/promptPullPayment"
+ android:name="net.taler.wallet.peer.PullPaymentFragment"
+ android:label="@string/pay_peer_title">
+ <action
+ android:id="@+id/action_promptPullPayment_to_nav_main"
+ app:destination="@id/nav_main"
+ app:popUpTo="@id/nav_main" />
+ </fragment>
+
+ <fragment
android:id="@+id/nav_transactions"
android:name="net.taler.wallet.transactions.TransactionsFragment"
android:label="@string/transactions_title"
@@ -276,6 +286,10 @@
app:destination="@id/promptTip" />
<action
+ android:id="@+id/action_global_prompt_pull_payment"
+ app:destination="@id/promptPullPayment" />
+
+ <action
android:id="@+id/action_global_pending_operations"
app:destination="@id/nav_pending_operations" />
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 96a3453..8601b14 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -98,6 +98,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="transaction_refresh">Coin expiry change fee</string>
<string name="transaction_peer_push_debit">Push payment</string>
<string name="transaction_peer_pull_credit">Invoice</string>
+ <string name="transaction_peer_pull_debit">Invoice paid</string>
<string name="payment_title">Payment</string>
<string name="payment_fee">+%s payment fee</string>
@@ -127,6 +128,9 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="send_peer_warning">Warning: Funds will leave the wallet immediately.</string>
<string name="send_peer_payment_instruction">Let the payee scan this QR code to receive:</string>
+ <string name="pay_peer_title">Pay invoice</string>
+ <string name="pay_peer_intro">Do you want to pay this invoice?</string>
+
<string name="withdraw_initiated">Withdrawal initiated</string>
<string name="withdraw_title">Withdrawal</string>
<string name="withdraw_total">Withdraw</string>