feat: add sending by nfc

This commit is contained in:
2026-06-05 17:17:19 +02:00
parent 82c048f6c0
commit cfde0755cd
2 changed files with 51 additions and 12 deletions
@@ -296,6 +296,7 @@ fun WalletScreen(
composable(TabItem.Send.route) {
SendScreen(
vm = vm,
nfcVm = nfcVm,
onViewDetails = { checkingId ->
historyVm.refresh()
navController.navigate("payment_detail/$checkingId") {
@@ -7,6 +7,7 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Nfc
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -21,8 +22,10 @@ import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitcointxoko.gudariwallet.ui.BalanceState
import com.bitcointxoko.gudariwallet.ui.NfcOfferState
import com.bitcointxoko.gudariwallet.ui.SendState
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
import com.bitcointxoko.gudariwallet.util.SendInputDetector
import com.bitcointxoko.gudariwallet.util.feePpm
import com.bitcointxoko.gudariwallet.util.formatFiat
@@ -30,7 +33,11 @@ import com.bitcointxoko.gudariwallet.util.satsToFiat
import kotlinx.coroutines.launch
@Composable
fun SendScreen(vm: WalletViewModel, onViewDetails: (paymentHash: String) -> Unit = {}) {
fun SendScreen(
vm : WalletViewModel,
nfcVm : NfcViewModel,
onViewDetails: (paymentHash: String) -> Unit = {}
) {
val sendState : SendState by vm.sendState.collectAsStateWithLifecycle()
var input by remember { mutableStateOf("") }
val context = LocalContext.current
@@ -43,8 +50,21 @@ fun SendScreen(vm: WalletViewModel, onViewDetails: (paymentHash: String) -> Unit
}
val fiatCurrency = (balanceState as? BalanceState.Success)?.fiatCurrency
// ── Collect NFC offer at top level — never inside a conditional ───────────
val nfcOffer by nfcVm.nfcOfferState.collectAsStateWithLifecycle()
DisposableEffect(Unit) { onDispose { vm.resetSendState() } }
// ── Auto-trigger scan when NFC offer arrives ──────────────────────────────
LaunchedEffect(nfcOffer) {
val offer = nfcOffer
if (offer is NfcOfferState.Detected && sendState is SendState.Idle) {
input = offer.raw
vm.scan(offer.raw)
nfcVm.dismissNfcOffer()
}
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -55,13 +75,31 @@ fun SendScreen(vm: WalletViewModel, onViewDetails: (paymentHash: String) -> Unit
) {
Spacer(Modifier.height(32.dp))
// ── Header row (matches ReceiveIdleContent) ──────────────────────────
// ── Header row ────────────────────────────────────────────────────────
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Send", style = MaterialTheme.typography.headlineSmall)
// NFC indicator — only visible in Idle/Error, lights up when offer pending
if (sendState is SendState.Idle || sendState is SendState.Error) {
FilledTonalIconButton(
onClick = { /* passive indicator */ },
modifier = Modifier.size(40.dp)
) {
Icon(
imageVector = Icons.Filled.Nfc,
contentDescription = "Tap NFC device to receive invoice",
tint = if (nfcOffer is NfcOfferState.Detected)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
}
}
if (sendState !is SendState.PaymentSent) {