feat: send from contact details
This commit is contained in:
@@ -547,9 +547,20 @@ fun WalletScreen(
|
|||||||
)
|
)
|
||||||
ContactDetailScreen(
|
ContactDetailScreen(
|
||||||
viewModel = detailVm,
|
viewModel = detailVm,
|
||||||
onBack = { navController.popBackStack() }
|
onBack = { navController.popBackStack() },
|
||||||
|
onPayAddress = { address ->
|
||||||
|
vm.prefillSend(address)
|
||||||
|
navController.navigate(TabItem.Send.route) {
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = false // don't restore stale SendScreen state
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Clipboard banner — floats over content, slides in from top ────
|
// ── Clipboard banner — floats over content, slides in from top ────
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.bitcointxoko.gudariwallet.ui.receive.ReceiveViewModel
|
|||||||
import com.bitcointxoko.gudariwallet.ui.send.SendEvent
|
import com.bitcointxoko.gudariwallet.ui.send.SendEvent
|
||||||
import com.bitcointxoko.gudariwallet.ui.send.SendStrings
|
import com.bitcointxoko.gudariwallet.ui.send.SendStrings
|
||||||
import com.bitcointxoko.gudariwallet.ui.send.SendViewModel
|
import com.bitcointxoko.gudariwallet.ui.send.SendViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
@@ -116,6 +117,16 @@ class WalletViewModel(
|
|||||||
|
|
||||||
val sendEvents: SharedFlow<SendEvent> = sendVm.events
|
val sendEvents: SharedFlow<SendEvent> = sendVm.events
|
||||||
|
|
||||||
|
// ── Contact tap-to-pay prefill ────────────────────────────────────────────
|
||||||
|
private val _pendingSend = MutableStateFlow<String?>(null)
|
||||||
|
val pendingSend: StateFlow<String?> get() = _pendingSend
|
||||||
|
|
||||||
|
/** Called by ContactDetailScreen before navigating to the Send tab. */
|
||||||
|
fun prefillSend(address: String) { _pendingSend.value = address }
|
||||||
|
|
||||||
|
/** Called by SendScreen once it has consumed the prefilled address. */
|
||||||
|
fun consumePendingSend() { _pendingSend.value = null }
|
||||||
|
|
||||||
fun handleWithdrawScanned(raw: LnurlScanResponse, lnurl: String) {
|
fun handleWithdrawScanned(raw: LnurlScanResponse, lnurl: String) {
|
||||||
receiveVm.handleWithdrawResponse(raw, lnurl)
|
receiveVm.handleWithdrawResponse(raw, lnurl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.contacts
|
package com.bitcointxoko.gudariwallet.ui.contacts
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -37,7 +38,8 @@ import com.bitcointxoko.gudariwallet.ui.common.DetailSection
|
|||||||
@Composable
|
@Composable
|
||||||
fun ContactDetailScreen(
|
fun ContactDetailScreen(
|
||||||
viewModel: ContactDetailViewModel,
|
viewModel: ContactDetailViewModel,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit,
|
||||||
|
onPayAddress: (address: String) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val strings = LocalAppStrings.current
|
val strings = LocalAppStrings.current
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
@@ -127,6 +129,7 @@ fun ContactDetailScreen(
|
|||||||
isEditing = isEditing,
|
isEditing = isEditing,
|
||||||
onRemoveAddress = { addressId -> pendingAddressDelete = addressId },
|
onRemoveAddress = { addressId -> pendingAddressDelete = addressId },
|
||||||
onSetDefault = { viewModel.setDefaultAddress(it) },
|
onSetDefault = { viewModel.setDefaultAddress(it) },
|
||||||
|
onPayAddress = onPayAddress,
|
||||||
onEditAlias = { viewModel.openEditAliasSheet() },
|
onEditAlias = { viewModel.openEditAliasSheet() },
|
||||||
onDeleteContact = { viewModel.requestDelete() },
|
onDeleteContact = { viewModel.requestDelete() },
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
@@ -209,6 +212,7 @@ private fun ContactDetailContent(
|
|||||||
isEditing: Boolean,
|
isEditing: Boolean,
|
||||||
onRemoveAddress: (addressId: String) -> Unit,
|
onRemoveAddress: (addressId: String) -> Unit,
|
||||||
onSetDefault: (PaymentAddressEntity) -> Unit,
|
onSetDefault: (PaymentAddressEntity) -> Unit,
|
||||||
|
onPayAddress: (address: String) -> Unit,
|
||||||
onEditAlias: () -> Unit,
|
onEditAlias: () -> Unit,
|
||||||
onDeleteContact: () -> Unit,
|
onDeleteContact: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@@ -276,6 +280,7 @@ private fun ContactDetailContent(
|
|||||||
AddressRow(
|
AddressRow(
|
||||||
address = addr,
|
address = addr,
|
||||||
isEditing = isEditing,
|
isEditing = isEditing,
|
||||||
|
onPay = { onPayAddress(addr.address) },
|
||||||
onRemove = { onRemoveAddress(addr.id) },
|
onRemove = { onRemoveAddress(addr.id) },
|
||||||
onSetDefault = { onSetDefault(addr) }
|
onSetDefault = { onSetDefault(addr) }
|
||||||
)
|
)
|
||||||
@@ -349,6 +354,7 @@ private fun ContactDetailContent(
|
|||||||
private fun AddressRow(
|
private fun AddressRow(
|
||||||
address: PaymentAddressEntity,
|
address: PaymentAddressEntity,
|
||||||
isEditing: Boolean,
|
isEditing: Boolean,
|
||||||
|
onPay: () -> Unit,
|
||||||
onRemove: () -> Unit,
|
onRemove: () -> Unit,
|
||||||
onSetDefault: () -> Unit,
|
onSetDefault: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -365,6 +371,10 @@ private fun AddressRow(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
enabled = !isEditing,
|
||||||
|
onClickLabel = "Pay with this address"
|
||||||
|
) { onPay() }
|
||||||
.padding(vertical = 12.dp),
|
.padding(vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -59,6 +59,17 @@ fun SendScreen(
|
|||||||
}
|
}
|
||||||
val fiatCurrency = (balanceState as? BalanceState.Success)?.fiatCurrency
|
val fiatCurrency = (balanceState as? BalanceState.Success)?.fiatCurrency
|
||||||
|
|
||||||
|
// ── Consume address prefilled from Contacts tap-to-pay ───────────────────
|
||||||
|
val pendingSend by vm.pendingSend.collectAsStateWithLifecycle()
|
||||||
|
LaunchedEffect(pendingSend) {
|
||||||
|
val address = pendingSend
|
||||||
|
if (!address.isNullOrBlank() && sendState is SendState.Idle) {
|
||||||
|
input = SendInputDetector.normalize(address)
|
||||||
|
vm.scan(address, sendStrings)
|
||||||
|
vm.consumePendingSend()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Collect NFC offer at top level — never inside a conditional ───────────
|
// ── Collect NFC offer at top level — never inside a conditional ───────────
|
||||||
val nfcOffer by nfcVm.nfcOfferState.collectAsStateWithLifecycle()
|
val nfcOffer by nfcVm.nfcOfferState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user