feat: add sending by nfc
This commit is contained in:
@@ -296,6 +296,7 @@ fun WalletScreen(
|
|||||||
composable(TabItem.Send.route) {
|
composable(TabItem.Send.route) {
|
||||||
SendScreen(
|
SendScreen(
|
||||||
vm = vm,
|
vm = vm,
|
||||||
|
nfcVm = nfcVm,
|
||||||
onViewDetails = { checkingId ->
|
onViewDetails = { checkingId ->
|
||||||
historyVm.refresh()
|
historyVm.refresh()
|
||||||
navController.navigate("payment_detail/$checkingId") {
|
navController.navigate("payment_detail/$checkingId") {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ContentPaste
|
import androidx.compose.material.icons.filled.ContentPaste
|
||||||
|
import androidx.compose.material.icons.filled.Nfc
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -21,8 +22,10 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bitcointxoko.gudariwallet.ui.BalanceState
|
import com.bitcointxoko.gudariwallet.ui.BalanceState
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.NfcOfferState
|
||||||
import com.bitcointxoko.gudariwallet.ui.SendState
|
import com.bitcointxoko.gudariwallet.ui.SendState
|
||||||
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
|
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
|
||||||
import com.bitcointxoko.gudariwallet.util.SendInputDetector
|
import com.bitcointxoko.gudariwallet.util.SendInputDetector
|
||||||
import com.bitcointxoko.gudariwallet.util.feePpm
|
import com.bitcointxoko.gudariwallet.util.feePpm
|
||||||
import com.bitcointxoko.gudariwallet.util.formatFiat
|
import com.bitcointxoko.gudariwallet.util.formatFiat
|
||||||
@@ -30,21 +33,38 @@ import com.bitcointxoko.gudariwallet.util.satsToFiat
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SendScreen(vm: WalletViewModel, onViewDetails: (paymentHash: String) -> Unit = {}) {
|
fun SendScreen(
|
||||||
val sendState: SendState by vm.sendState.collectAsStateWithLifecycle()
|
vm : WalletViewModel,
|
||||||
var input by remember { mutableStateOf("") }
|
nfcVm : NfcViewModel,
|
||||||
val context = LocalContext.current
|
onViewDetails: (paymentHash: String) -> Unit = {}
|
||||||
val activity = context as FragmentActivity
|
) {
|
||||||
val clipboard = LocalClipboard.current
|
val sendState : SendState by vm.sendState.collectAsStateWithLifecycle()
|
||||||
val scope = rememberCoroutineScope()
|
var input by remember { mutableStateOf("") }
|
||||||
val balanceState by vm.balanceState.collectAsStateWithLifecycle()
|
val context = LocalContext.current
|
||||||
val fiatRate = (balanceState as? BalanceState.Success)?.let { s ->
|
val activity = context as FragmentActivity
|
||||||
|
val clipboard = LocalClipboard.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val balanceState by vm.balanceState.collectAsStateWithLifecycle()
|
||||||
|
val fiatRate = (balanceState as? BalanceState.Success)?.let { s ->
|
||||||
s.fiatAmount?.let { if (s.sats > 0) s.sats.toDouble() / it else null }
|
s.fiatAmount?.let { if (s.sats > 0) s.sats.toDouble() / it else null }
|
||||||
}
|
}
|
||||||
val fiatCurrency = (balanceState as? BalanceState.Success)?.fiatCurrency
|
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() } }
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -55,13 +75,31 @@ fun SendScreen(vm: WalletViewModel, onViewDetails: (paymentHash: String) -> Unit
|
|||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(32.dp))
|
Spacer(Modifier.height(32.dp))
|
||||||
|
|
||||||
// ── Header row (matches ReceiveIdleContent) ──────────────────────────
|
// ── Header row ────────────────────────────────────────────────────────
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
Text("Send", style = MaterialTheme.typography.headlineSmall)
|
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) {
|
if (sendState !is SendState.PaymentSent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user