Merge pull request #605 from dmnyc/feat/tx-history-sheet
feat(wallet): transaction history as swipe-up bottom sheet
This commit is contained in:
@@ -55,6 +55,7 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -115,11 +116,13 @@ import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -143,6 +146,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -204,6 +208,7 @@ fun WalletScreen(
|
||||
// app bar to match iOS — leaves more headroom for the centered logo
|
||||
// + title layout.
|
||||
val hideAppBar = currentPage is WalletPage.Home ||
|
||||
currentPage is WalletPage.Transactions ||
|
||||
currentPage is WalletPage.ModeSelection ||
|
||||
currentPage is WalletPage.NwcSetup ||
|
||||
currentPage is WalletPage.SparkSetup ||
|
||||
@@ -482,17 +487,36 @@ fun WalletScreen(
|
||||
)
|
||||
}
|
||||
is WalletPage.Transactions -> {
|
||||
// Observe profile refresh key so UI recomposes when new profiles arrive
|
||||
// Home stays visible behind the transactions bottom sheet.
|
||||
val profileKey = viewModel.profileRefreshKey.collectAsState().value
|
||||
TransactionHistoryContent(
|
||||
transactions = viewModel.transactions.collectAsState().value,
|
||||
error = viewModel.transactionsError.collectAsState().value,
|
||||
isLoading = viewModel.isLoading.collectAsState().value,
|
||||
isLoadingMore = viewModel.isLoadingMore.collectAsState().value,
|
||||
hasMore = viewModel.hasMoreTransactions.collectAsState().value,
|
||||
onLoadMore = { viewModel.loadMoreTransactions() },
|
||||
profileLookup = { viewModel.getProfileData(it) },
|
||||
profileRefreshKey = profileKey,
|
||||
WalletHomeContent(
|
||||
balanceMsats = balanceMsats,
|
||||
walletMode = viewModel.walletMode.collectAsState().value,
|
||||
balanceUnit = viewModel.balanceUnit.collectAsState().value,
|
||||
showSettingsAlert = viewModel.walletMode.collectAsState().value == WalletMode.SPARK
|
||||
&& !viewModel.seedBackupAcked.collectAsState().value
|
||||
&& !viewModel.isDefaultWallet.collectAsState().value,
|
||||
seedBackupAcked = viewModel.seedBackupAcked.collectAsState().value,
|
||||
backupMissing = viewModel.backupMissing.collectAsState().value,
|
||||
isDefaultWallet = viewModel.isDefaultWallet.collectAsState().value,
|
||||
onSend = { viewModel.navigateTo(WalletPage.SendInput) },
|
||||
onReceive = { viewModel.navigateTo(WalletPage.ReceiveAmount) },
|
||||
onTransactions = {},
|
||||
onRefresh = { viewModel.refreshBalance() },
|
||||
onSettings = { viewModel.navigateTo(WalletPage.Settings) },
|
||||
onBackupToRelay = {
|
||||
viewModel.resetBackupStatus()
|
||||
viewModel.navigateTo(WalletPage.BackupToRelay)
|
||||
},
|
||||
onViewSeed = { viewModel.showMnemonicBackup() },
|
||||
lightningAddress = viewModel.lightningAddress.collectAsState().value,
|
||||
onSetupAddress = {
|
||||
viewModel.resetAddressSetupState()
|
||||
viewModel.navigateTo(WalletPage.LightningAddressSetup)
|
||||
},
|
||||
recentTransactions = viewModel.transactions.collectAsState().value,
|
||||
profileLookup = remember(profileKey) { { viewModel.getProfileData(it) } },
|
||||
nwcNodeAlias = viewModel.nwcNodeAlias.collectAsState().value,
|
||||
pubkey = viewModel.keyRepo.getPubkeyHex(),
|
||||
modifier = Modifier.padding(padding)
|
||||
)
|
||||
@@ -612,6 +636,28 @@ fun WalletScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Transactions full-screen bottom sheet — swipe down to dismiss
|
||||
if (currentPage is WalletPage.Transactions) {
|
||||
val txSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val txProfileKey = viewModel.profileRefreshKey.collectAsState().value
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { viewModel.navigateBack() },
|
||||
sheetState = txSheetState,
|
||||
) {
|
||||
TransactionHistoryContent(
|
||||
transactions = viewModel.transactions.collectAsState().value,
|
||||
error = viewModel.transactionsError.collectAsState().value,
|
||||
isLoading = viewModel.isLoading.collectAsState().value,
|
||||
isLoadingMore = viewModel.isLoadingMore.collectAsState().value,
|
||||
hasMore = viewModel.hasMoreTransactions.collectAsState().value,
|
||||
onLoadMore = { viewModel.loadMoreTransactions() },
|
||||
profileLookup = { viewModel.getProfileData(it) },
|
||||
profileRefreshKey = txProfileKey,
|
||||
pubkey = viewModel.keyRepo.getPubkeyHex(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1472,7 +1518,24 @@ private fun WalletHomeContent(
|
||||
|
||||
// ── Recent transactions inline footer ──────────────────────
|
||||
if (recentTransactions.isNotEmpty()) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.pointerInput(onTransactions) {
|
||||
var totalDrag = 0f
|
||||
detectVerticalDragGestures(
|
||||
onDragStart = { totalDrag = 0f },
|
||||
onDragEnd = { totalDrag = 0f },
|
||||
onDragCancel = { totalDrag = 0f }
|
||||
) { change, delta ->
|
||||
totalDrag += delta
|
||||
if (totalDrag < -40f) {
|
||||
totalDrag = 0f
|
||||
change.consume()
|
||||
onTransactions()
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
Reference in New Issue
Block a user