feat: add nfc step 2 - receive with invoice

This commit is contained in:
2026-06-05 15:43:10 +02:00
parent cc50ff08df
commit 82c048f6c0
13 changed files with 588 additions and 316 deletions
@@ -1,8 +1,11 @@
package com.bitcointxoko.gudariwallet
import android.Manifest
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
@@ -25,6 +28,7 @@ import com.bitcointxoko.gudariwallet.ui.OnboardingScreen
import com.bitcointxoko.gudariwallet.ui.WalletScreen
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.WalletViewModelFactory
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
import com.bitcointxoko.gudariwallet.ui.theme.GudariWalletTheme
import com.bitcointxoko.gudariwallet.util.NotificationHelper
import com.bitcointxoko.gudariwallet.util.IntentUtils
@@ -40,13 +44,30 @@ class MainActivity : AppCompatActivity() {
WalletViewModelFactory(application)
}
// ── NFC ──────────────────────────────────────────────────────────────────
val nfcVm: NfcViewModel by viewModels()
private var nfcAdapter: NfcAdapter? = null
/** Pending intent used by foreground dispatch — routes tag intents back here. */
private val nfcPendingIntent: PendingIntent by lazy {
PendingIntent.getActivity(
this, 0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE
)
}
// ── Existing state ───────────────────────────────────────────────────────
// Holds a paymentHash that arrived via notification tap.
// WalletScreen observes this and navigates to the detail screen.
// MutableState so Compose recomposes when it changes.
private val pendingPaymentHash = mutableStateOf<String?>(null)
// Holds a payment URI that arrived via deep link / intent (BTCPay, external apps).
private val pendingPaymentUri = mutableStateOf<String?>(null)
private val pendingPaymentUri = mutableStateOf<String?>(null)
private val requestNotificationPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* informational */ }
@@ -73,10 +94,14 @@ class MainActivity : AppCompatActivity() {
NotificationHelper.createChannels(applicationContext)
// Read hash from the intent that launched the activity (cold start from notification)
pendingPaymentHash.value = intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH)
pendingPaymentHash.value =
intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH)
// Read payment URI from the intent that launched the activity (cold start from deep link)
pendingPaymentUri.value = IntentUtils.extractPaymentUri(intent)
// Initialise NFC adapter (null on devices without NFC — handled gracefully)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
enableEdgeToEdge()
setContent {
@@ -94,6 +119,7 @@ class MainActivity : AppCompatActivity() {
}
WalletScreen(
vm = vm,
nfcVm = nfcVm,
pendingPaymentHash = pendingPaymentHash,
pendingPaymentUri = pendingPaymentUri
)
@@ -105,26 +131,55 @@ class MainActivity : AppCompatActivity() {
}
}
}
}
// Called when the activity is already running and a notification is tapped
// ── NFC foreground dispatch ───────────────────────────────────────────────
override fun onResume() {
super.onResume()
// Give this activity first pick of all NFC tag intents while foregrounded.
// Passing null for techLists and intentFilters means we intercept every tag type.
nfcAdapter?.enableForegroundDispatch(
this,
nfcPendingIntent,
null, // intercept all intent filters
null // intercept all tech lists
)
}
override fun onPause() {
super.onPause()
nfcAdapter?.disableForegroundDispatch(this)
}
// ── Intent routing ────────────────────────────────────────────────────────
// Called when the activity is already running and a notification is tapped,
// a deep link arrives, or an NFC tag is discovered while foregrounded.
// (FLAG_ACTIVITY_SINGLE_TOP prevents a new instance being created)
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
// Notification tap → navigate to payment detail
val hash = intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH)
if (hash != null) {
pendingPaymentHash.value = hash
}
// Deep link / external app URI
val uri = IntentUtils.extractPaymentUri(intent)
if (uri != null) {
pendingPaymentUri.value = uri
}
// NFC tag discovered → hand off to NfcViewModel for reading
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null) {
nfcVm.onTagDiscovered(tag)
}
}
// ── Permission / battery helpers (unchanged) ──────────────────────────────
private fun requestNotificationPermissionIfNeeded() {
@@ -154,9 +209,10 @@ class MainActivity : AppCompatActivity() {
}
private fun openBatterySettings() {
val appDetailsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = "package:$packageName".toUri()
}
val appDetailsIntent =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = "package:$packageName".toUri()
}
if (tryStartActivity(appDetailsIntent)) return
tryStartActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
}