Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
71d07ad
docs: plan real transaction history
j-kon Jun 30, 2026
3ae8a7d
refactor: rename transaction history model
j-kon Jun 30, 2026
45141ad
feat: map wallet transactions for history
j-kon Jun 30, 2026
8b46b1e
feat: load real wallet transaction history
j-kon Jun 30, 2026
7cad683
chore: clean up PR 102, delete docs, fix UTC time, handle no wallet s…
j-kon Jun 30, 2026
db69a39
refactor: extract hasActiveWalletProvider and remove real BDK wallet …
j-kon Jun 30, 2026
af67b6a
test: rename helper parameter to hasActiveWallet in transactions_list…
j-kon Jun 30, 2026
184bbfc
Scope transactions controller and details to active logical wallet ID
j-kon Jul 14, 2026
c1cb1bf
Merge remote-tracking branch 'upstream/main' into feat/bdk-demo-real-…
j-kon Jul 14, 2026
e4c458c
fix(demo): clean up transaction history resources
j-kon Jul 14, 2026
969da23
fix(demo): scope transaction state by wallet ID
j-kon Jul 18, 2026
e9b32f0
feat: automatically load and refresh transaction history
j-kon Jul 21, 2026
1958c9d
feat: auto-reload transactions in background after broadcast and sync…
j-kon Jul 21, 2026
54114a7
fix: stabilize transaction history refresh lifecycle
j-kon Jul 22, 2026
aa1fe5a
ci: trigger fresh build on all platforms
j-kon Jul 22, 2026
270a415
fix: queue pending transaction refreshes and resolve analyzer warning
j-kon Jul 25, 2026
fdba833
fix(demo): keep back stack when navigating home after wallet creation…
Shamsudeen12 Aug 23, 2026
2a2a5f7
feat: show spendable balance as Home hero and add pending activity card
Shamsudeen12 Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 190 additions & 17 deletions bdk_demo/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:bdk_demo/core/constants/app_constants.dart';
import 'package:bdk_demo/core/router/app_router.dart';
import 'package:bdk_demo/core/theme/app_theme.dart';
import 'package:bdk_demo/core/utils/formatters.dart';
import 'package:bdk_demo/features/home/network_endpoint_bottom_sheet.dart';
import 'package:bdk_demo/features/shared/widgets/secondary_app_bar.dart';
import 'package:bdk_demo/features/shared/widgets/wallet_ui_helpers.dart';
import 'package:bdk_demo/features/transactions/models/transaction_history_item.dart';
import 'package:bdk_demo/features/transactions/transactions_controller.dart';
import 'package:bdk_demo/models/currency_unit.dart';
import 'package:bdk_demo/models/wallet_balance_snapshot.dart';
import 'package:bdk_demo/models/wallet_record.dart';
Expand Down Expand Up @@ -58,6 +61,9 @@ class _HomePageState extends ConsumerState<HomePage> {
final syncProgress = ref.watch(syncProgressProvider);
final isOnline = ref.watch(isOnlineProvider);

final guardedSnapshot = _matchingSnapshot(snapshot, record?.id);
final pendingSat = guardedSnapshot?.pendingSat ?? 0;

return Scaffold(
appBar: const SecondaryAppBar(title: 'Home'),
body: SafeArea(
Expand All @@ -78,7 +84,7 @@ class _HomePageState extends ConsumerState<HomePage> {
_WalletHeader(record: record),
const SizedBox(height: 16),
_BalanceCard(
snapshot: _matchingSnapshot(snapshot, record.id),
snapshot: guardedSnapshot,
syncStatus: syncStatus,
currencyUnit: _currencyUnit,
onToggleUnit: () {
Expand All @@ -97,6 +103,14 @@ class _HomePageState extends ConsumerState<HomePage> {
],
const SizedBox(height: 16),
_ActionRow(isOnline: isOnline),
if (pendingSat > 0) ...[
const SizedBox(height: 16),
_PendingActivityCard(
pendingSat: pendingSat,
walletId: record.id,
currencyUnit: _currencyUnit,
),
],
],
),
),
Expand Down Expand Up @@ -150,9 +164,9 @@ class _HomePageState extends ConsumerState<HomePage> {

WalletBalanceSnapshot? _matchingSnapshot(
WalletBalanceSnapshot? snapshot,
String walletId,
String? walletId,
) {
if (snapshot?.walletId != walletId) return null;
if (walletId == null || snapshot?.walletId != walletId) return null;
return snapshot;
}

Expand Down Expand Up @@ -227,17 +241,23 @@ class _BalanceCard extends StatelessWidget {
final CurrencyUnit currencyUnit;
final VoidCallback onToggleUnit;

String? get _subtitle {
final snapshot = this.snapshot;
if (snapshot == null) {
return syncStatus == SyncStatus.syncing
? 'Syncing wallet...'
: 'Balance will update after sync.';
}
if (snapshot.totalSat == snapshot.trustedSpendableSat) return null;
return 'Total incl. pending: '
'${Formatters.formatBalance(snapshot.totalSat, currencyUnit)}';
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final totalSat = snapshot?.totalSat ?? 0;
final trustedSpendableSat = snapshot?.trustedSpendableSat ?? 0;
final hasSnapshot = snapshot != null;
final subtitle = hasSnapshot
? 'Trusted spendable: ${Formatters.formatBalance(trustedSpendableSat, currencyUnit)}'
: syncStatus == SyncStatus.syncing
? 'Syncing wallet...'
: 'Balance will update after sync.';
final subtitle = _subtitle;

return Card(
child: InkWell(
Expand Down Expand Up @@ -269,18 +289,20 @@ class _BalanceCard extends StatelessWidget {
),
const SizedBox(height: 12),
Text(
Formatters.formatBalance(totalSat, currencyUnit),
Formatters.formatBalance(trustedSpendableSat, currencyUnit),
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8),
Text(
subtitle,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(170),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(170),
),
),
),
],
const SizedBox(height: 12),
Text(
'Tap balance to toggle units',
Expand Down Expand Up @@ -600,3 +622,154 @@ class _ActionRow extends StatelessWidget {
);
}
}

class _PendingActivityCard extends ConsumerWidget {
const _PendingActivityCard({
required this.pendingSat,
required this.walletId,
required this.currencyUnit,
});

final int pendingSat;
final String walletId;
final CurrencyUnit currencyUnit;

static const _maxRows = 3;

void _openTransactionDetail(
BuildContext context,
TransactionHistoryItem transaction,
) {
context.pushNamed(
'transactionDetail',
pathParameters: {'txid': transaction.txid},
);
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final state = ref.watch(transactionsControllerProvider(walletId));
final pendingTxs = state.transactions
.where((transaction) => transaction.pending)
.toList(growable: false);
final visibleTxs = pendingTxs.take(_maxRows).toList(growable: false);

return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Pending',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
Text(
Formatters.formatBalance(pendingSat, currencyUnit),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: theme.colorScheme.secondary,
),
),
],
),
const SizedBox(height: 4),
Text(
'Awaiting confirmation',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(170),
),
),
if (visibleTxs.isNotEmpty) ...[
const SizedBox(height: 16),
for (var index = 0; index < visibleTxs.length; index++) ...[
_PendingTransactionRow(
transaction: visibleTxs[index],
onTap: () =>
_openTransactionDetail(context, visibleTxs[index]),
),
if (index < visibleTxs.length - 1) const SizedBox(height: 12),
],
],
if (pendingTxs.length > _maxRows) ...[
const SizedBox(height: 8),
TextButton(
onPressed: () => context.push(AppRoutes.transactionHistory),
child: Text('View all (${pendingTxs.length})'),
),
],
],
),
),
);
}
}

class _PendingTransactionRow extends StatelessWidget {
const _PendingTransactionRow({
required this.transaction,
required this.onTap,
});

final TransactionHistoryItem transaction;
final VoidCallback onTap;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final amount = transaction.netAmount;
final amountLabel =
'${amount >= 0 ? '+' : '-'}${Formatters.formatBalance(amount.abs(), CurrencyUnit.satoshi)}';

return Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: onTap,
child: Ink(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: theme.colorScheme.outlineVariant),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
amountLabel,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w700,
color: theme.colorScheme.secondary,
),
),
const SizedBox(height: 4),
Text(
transaction.shortTxid,
style: AppTheme.monoStyle.copyWith(
fontSize: 13,
color: theme.colorScheme.onSurface,
),
),
],
),
),
const SizedBox(width: 12),
WalletStatusChip(status: transaction.statusLabel),
],
),
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import 'package:bdk_demo/core/utils/formatters.dart';

class DemoTxDetails {
class TransactionHistoryItem {
final String txid;
final int sent;
final int received;
final bool pending;
final int? blockHeight;
final DateTime? confirmationTime;

const DemoTxDetails({
const TransactionHistoryItem({
required this.txid,
required this.sent,
required this.received,
Expand Down
18 changes: 11 additions & 7 deletions bdk_demo/lib/features/transactions/transaction_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import 'package:bdk_demo/core/theme/app_theme.dart';
import 'package:bdk_demo/core/utils/formatters.dart';
import 'package:bdk_demo/features/shared/widgets/secondary_app_bar.dart';
import 'package:bdk_demo/features/shared/widgets/wallet_ui_helpers.dart';
import 'package:bdk_demo/features/transactions/models/demo_tx_details.dart';
import 'package:bdk_demo/features/transactions/models/transaction_history_item.dart';
import 'package:bdk_demo/features/transactions/transactions_controller.dart';
import 'package:bdk_demo/models/currency_unit.dart';
import 'package:bdk_demo/providers/wallet_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Expand All @@ -13,7 +14,7 @@ class TransactionDetailPage extends ConsumerWidget {

const TransactionDetailPage({super.key, required this.txid});

String _formatAmount(DemoTxDetails transaction) {
String _formatAmount(TransactionHistoryItem transaction) {
final amount = transaction.netAmount;
final prefix = amount >= 0 ? '+' : '-';
final value = Formatters.formatBalance(amount.abs(), CurrencyUnit.satoshi);
Expand All @@ -28,7 +29,10 @@ class TransactionDetailPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final transactionAsync = ref.watch(transactionDetailsProvider(txid));
final activeWalletId = ref.watch(activeWalletIdProvider);
final transactionAsync = ref.watch(
transactionDetailsProvider((walletId: activeWalletId, txid: txid)),
);

return Scaffold(
appBar: const SecondaryAppBar(title: 'Transaction Detail'),
Expand All @@ -37,14 +41,14 @@ class TransactionDetailPage extends ConsumerWidget {
loading: () => const WalletStateCard(
icon: Icons.hourglass_bottom,
title: 'Loading transaction',
message: 'Preparing placeholder transaction details...',
message: 'Reading wallet transaction details...',
showSpinner: true,
centered: true,
),
error: (_, __) => WalletStateCard(
icon: Icons.error_outline,
title: 'Transaction unavailable',
message: 'The demo could not load placeholder transaction details.',
message: 'The wallet transaction details could not be loaded.',
accentColor: theme.colorScheme.error,
centered: true,
),
Expand All @@ -54,7 +58,7 @@ class TransactionDetailPage extends ConsumerWidget {
icon: Icons.search_off,
title: 'Transaction not found',
message:
'No placeholder transaction was found for this txid.\n\n$txid',
'No wallet transaction was found for this txid.\n\n$txid',
centered: true,
);
}
Expand Down Expand Up @@ -83,7 +87,7 @@ class TransactionDetailPage extends ConsumerWidget {
),
const SizedBox(height: 8),
Text(
'Standalone transaction detail view for the selected placeholder transaction.',
'Transaction detail for the selected wallet transaction.',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(170),
),
Expand Down
Loading
Loading