feat: add nfc step 2 - receive with invoice
This commit is contained in:
@@ -58,6 +58,7 @@ import com.bitcointxoko.gudariwallet.ui.history.HistoryScreen
|
||||
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModelFactory
|
||||
import com.bitcointxoko.gudariwallet.ui.history.PaymentDetailScreen
|
||||
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.receive.ReceiveScreen
|
||||
import com.bitcointxoko.gudariwallet.ui.send.SendScreen
|
||||
import com.bitcointxoko.gudariwallet.util.SendInputType
|
||||
@@ -67,7 +68,7 @@ import kotlinx.coroutines.delay
|
||||
|
||||
sealed class TabItem(val route: String, val label: String, val icon: ImageVector) {
|
||||
data object Receive : TabItem("receive", "Receive", Icons.Filled.QrCode)
|
||||
data object Send : TabItem("send", "Send", Icons.AutoMirrored.Filled.Send)
|
||||
data object Send : TabItem("send", "Send", Icons.AutoMirrored.Filled.Send)
|
||||
}
|
||||
|
||||
private const val ROUTE_HOME = "home"
|
||||
@@ -78,6 +79,7 @@ private const val ROUTE_PAYMENT_DETAIL = "payment_detail/{checkingId}"
|
||||
@Composable
|
||||
fun WalletScreen(
|
||||
vm : WalletViewModel,
|
||||
nfcVm : NfcViewModel, // ← new
|
||||
pendingPaymentHash: MutableState<String?>,
|
||||
pendingPaymentUri : MutableState<String?>
|
||||
) {
|
||||
@@ -85,10 +87,10 @@ fun WalletScreen(
|
||||
val context = LocalContext.current
|
||||
val historyFactory = remember {
|
||||
HistoryViewModelFactory(
|
||||
repo = vm.repo,
|
||||
aliasRepo = vm.aliasRepo,
|
||||
paymentCache = vm.paymentCache,
|
||||
fiatCurrency = vm.selectedCurrency,
|
||||
repo = vm.repo,
|
||||
aliasRepo = vm.aliasRepo,
|
||||
paymentCache = vm.paymentCache,
|
||||
fiatCurrency = vm.selectedCurrency,
|
||||
fiatSatsPerUnit = vm.fiatSatsPerUnit
|
||||
)
|
||||
}
|
||||
@@ -143,7 +145,7 @@ fun WalletScreen(
|
||||
LaunchedEffect(intentUri) {
|
||||
if (intentUri != null) {
|
||||
Log.d("WalletScreen", "INTENT [URI] deep link received: $intentUri")
|
||||
pendingPaymentUri.value = null // consume immediately
|
||||
pendingPaymentUri.value = null
|
||||
vm.scan(intentUri)
|
||||
navController.navigate(TabItem.Send.route) {
|
||||
launchSingleTop = true
|
||||
@@ -151,9 +153,9 @@ fun WalletScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// ── Clipboard offer ───────────────────────────────────────────────────────
|
||||
val clipboardOffer by vm.clipboardOfferState.collectAsStateWithLifecycle()
|
||||
|
||||
// Auto-dismiss banner after 8 seconds
|
||||
LaunchedEffect(clipboardOffer) {
|
||||
if (clipboardOffer is ClipboardOfferState.Detected) {
|
||||
delay(8_000)
|
||||
@@ -161,33 +163,13 @@ fun WalletScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// ── NFC reader mode — register on composition, unregister on disposal ────────
|
||||
val nfcAdapter = remember {
|
||||
android.nfc.NfcAdapter.getDefaultAdapter(context)
|
||||
}
|
||||
// ── NFC offer (tag read while app is foregrounded) ────────────────────────
|
||||
val nfcOffer by nfcVm.nfcOfferState.collectAsStateWithLifecycle()
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
val activity = context as MainActivity
|
||||
nfcAdapter?.enableReaderMode(
|
||||
activity,
|
||||
{ tag -> vm.onNfcTagDiscovered(tag) }, // runs on NFC binder thread
|
||||
android.nfc.NfcAdapter.FLAG_READER_NFC_A or
|
||||
android.nfc.NfcAdapter.FLAG_READER_NFC_B or
|
||||
android.nfc.NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK.inv(), // keep NDEF
|
||||
null
|
||||
)
|
||||
onDispose {
|
||||
nfcAdapter?.disableReaderMode(activity)
|
||||
}
|
||||
}
|
||||
|
||||
val nfcOffer by vm.nfcOfferState.collectAsStateWithLifecycle()
|
||||
|
||||
// Auto-dismiss NFC banner after 8 seconds (same as clipboard)
|
||||
LaunchedEffect(nfcOffer) {
|
||||
if (nfcOffer is NfcOfferState.Detected) {
|
||||
delay(8_000)
|
||||
vm.dismissNfcOffer()
|
||||
nfcVm.dismissNfcOffer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +286,8 @@ fun WalletScreen(
|
||||
}
|
||||
composable(TabItem.Receive.route) {
|
||||
ReceiveScreen(
|
||||
vm,
|
||||
vm = vm,
|
||||
nfcVm = nfcVm, // ← new
|
||||
onNavigateToPaymentDetail = { checkingId ->
|
||||
navController.navigate("payment_detail/$checkingId")
|
||||
}
|
||||
@@ -339,9 +322,8 @@ fun WalletScreen(
|
||||
// History — full screen, no bottom chrome, close → home
|
||||
composable(
|
||||
ROUTE_HISTORY,
|
||||
enterTransition = { fadeIn(animationSpec = tween(150)) },
|
||||
popExitTransition = { fadeOut(animationSpec = tween(150)) }
|
||||
|
||||
enterTransition = { fadeIn(animationSpec = tween(150)) },
|
||||
popExitTransition = { fadeOut(animationSpec = tween(150)) }
|
||||
) {
|
||||
HistoryScreen(
|
||||
vm = historyVm,
|
||||
@@ -371,8 +353,8 @@ fun WalletScreen(
|
||||
if (payment != null) {
|
||||
PaymentDetailScreen(
|
||||
payment = payment,
|
||||
vm = historyVm,
|
||||
onBack = { navController.popBackStack() }
|
||||
vm = historyVm,
|
||||
onBack = { navController.popBackStack() }
|
||||
)
|
||||
} else {
|
||||
LaunchedEffect(checkingId) { historyVm.refresh() }
|
||||
@@ -393,13 +375,13 @@ fun WalletScreen(
|
||||
exit = slideOutVertically(targetOffsetY = { -it }),
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.zIndex(1f)
|
||||
.zIndex(2f) // above NFC banner
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
if (clipboardOffer is ClipboardOfferState.Detected) {
|
||||
val offer = clipboardOffer as ClipboardOfferState.Detected
|
||||
val label = when (offer.inputType) {
|
||||
val offer = clipboardOffer as ClipboardOfferState.Detected
|
||||
val label = when (offer.inputType) {
|
||||
is SendInputType.Bolt11 -> "Invoice"
|
||||
is SendInputType.Lnurl -> "LNURL"
|
||||
is SendInputType.LightningAddress -> "Lightning Address"
|
||||
@@ -445,22 +427,23 @@ fun WalletScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
// ── NFC banner — floats below clipboard banner, slides in from top ────────────
|
||||
|
||||
// ── NFC banner — floats over content, slides in from top ──────────
|
||||
// Shown when an NFC tag is tapped while the app is foregrounded.
|
||||
// Mirrors the clipboard banner pattern exactly.
|
||||
AnimatedVisibility(
|
||||
visible = nfcOffer is NfcOfferState.Detected,
|
||||
enter = slideInVertically(initialOffsetY = { -it }),
|
||||
exit = slideOutVertically(targetOffsetY = { -it }),
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.zIndex(1f)
|
||||
.zIndex(1f) // below clipboard banner if both show
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
// push down if clipboard banner is also showing
|
||||
.padding(top = if (clipboardOffer is ClipboardOfferState.Detected) 64.dp else 0.dp)
|
||||
) {
|
||||
if (nfcOffer is NfcOfferState.Detected) {
|
||||
val offer = nfcOffer as NfcOfferState.Detected
|
||||
val label = when (offer.inputType) {
|
||||
val offer = nfcOffer as NfcOfferState.Detected
|
||||
val label = when (offer.inputType) {
|
||||
is SendInputType.Bolt11 -> "Invoice"
|
||||
is SendInputType.Lnurl -> "LNURL"
|
||||
is SendInputType.LightningAddress -> "Lightning Address"
|
||||
@@ -471,15 +454,12 @@ fun WalletScreen(
|
||||
else -> "Tap to pay"
|
||||
}
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer, // distinct from clipboard's primaryContainer
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
vm.dismissNfcOffer()
|
||||
nfcVm.dismissNfcOffer()
|
||||
vm.scan(offer.raw)
|
||||
// scan() already emits navigateToReceive for LNURL-withdraw,
|
||||
// so we only need to navigate to Send for everything else.
|
||||
// The existing navigateToReceive LaunchedEffect handles BoltCard.
|
||||
navController.navigate(TabItem.Send.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
@@ -489,7 +469,7 @@ fun WalletScreen(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Filled.Nfc, contentDescription = null)
|
||||
Icon(Icons.Default.Nfc, contentDescription = null)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
@@ -502,7 +482,7 @@ fun WalletScreen(
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { vm.dismissNfcOffer() }) {
|
||||
IconButton(onClick = { nfcVm.dismissNfcOffer() }) {
|
||||
Icon(Icons.Default.Close, contentDescription = "Dismiss")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user