Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 46 additions & 3 deletions bdk_demo/lib/providers/wallet_providers.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import 'package:bdk_demo/models/wallet_record.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../models/wallet_record.dart';
import 'package:bdk_dart/bdk.dart';
import 'settings_providers.dart';

typedef WalletDisposer = void Function(Wallet wallet);

final walletDisposerProvider = Provider<WalletDisposer>(
(ref) =>
(wallet) => wallet.dispose(),
);

final activeWalletRecordProvider =
NotifierProvider<ActiveWalletRecordNotifier, WalletRecord?>(
ActiveWalletRecordNotifier.new,
Expand All @@ -16,6 +23,43 @@ class ActiveWalletRecordNotifier extends Notifier<WalletRecord?> {
void clear() => state = null;
}

final activeWalletProvider = NotifierProvider<ActiveWalletNotifier, Wallet?>(
ActiveWalletNotifier.new,
);

class ActiveWalletNotifier extends Notifier<Wallet?> {
late WalletDisposer _walletDisposer;
Wallet? _currentWallet;

void _disposeWallet(Wallet? wallet) {
if (wallet == null) return;
_walletDisposer(wallet);
}

@override
Wallet? build() {
_walletDisposer = ref.read(walletDisposerProvider);
_currentWallet = null;
ref.onDispose(() => _disposeWallet(_currentWallet));
return null;
}

void set(Wallet wallet) {
if (identical(_currentWallet, wallet)) {
return;
}
_disposeWallet(_currentWallet);
_currentWallet = wallet;
state = wallet;
}

void clear() {
_disposeWallet(_currentWallet);
_currentWallet = null;
state = null;
}
}

final walletRecordsProvider =
NotifierProvider<WalletRecordsNotifier, List<WalletRecord>>(
WalletRecordsNotifier.new,
Expand Down Expand Up @@ -48,5 +92,4 @@ class WalletRecordsNotifier extends Notifier<List<WalletRecord>> {
}
}

// TODO: Add activeWalletProvider.
// TODO: Add balanceProvider, syncStateProvider.
3 changes: 1 addition & 2 deletions bdk_demo/lib/services/storage_service.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'dart:convert';
import 'package:bdk_demo/models/wallet_record.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../models/wallet_record.dart';

abstract final class _PrefKeys {
static const introDone = 'intro_done';
static const darkTheme = 'dark_theme';
Expand Down
10 changes: 10 additions & 0 deletions bdk_demo/lib/services/wallet_network_mapper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:bdk_dart/bdk.dart';
import 'package:bdk_demo/models/wallet_record.dart';

extension WalletNetworkX on WalletNetwork {
Network toBdkNetwork() => switch (this) {
WalletNetwork.signet => Network.signet,
WalletNetwork.testnet => Network.testnet,
WalletNetwork.regtest => Network.regtest,
};
}
12 changes: 12 additions & 0 deletions bdk_demo/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Test Layout

Tests are grouped by app layer to keep the suite easy to navigate as features grow.

- `test/presentation/`: widget and UI behavior tests
- `test/providers/`: Riverpod notifier/provider state tests
- `test/services/`: service and mapping/unit logic tests
- `test/integration/` (future): multi-service or app-flow integration tests

Naming convention:
- Use `*_test.dart` suffix.
- Prefer behavior-focused names (example: `app_shell_test.dart`).
91 changes: 91 additions & 0 deletions bdk_demo/test/providers/active_wallet_notifier_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'package:bdk_dart/bdk.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bdk_demo/providers/wallet_providers.dart';

const _testExtendedPrivKey =
'tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B';

Wallet _createTestWallet() {
final descriptor = Descriptor(
descriptor: 'wpkh($_testExtendedPrivKey/84h/1h/0h/0/*)',
network: Network.testnet,
);
final changeDescriptor = Descriptor(
descriptor: 'wpkh($_testExtendedPrivKey/84h/1h/0h/1/*)',
network: Network.testnet,
);
return Wallet(
descriptor: descriptor,
changeDescriptor: changeDescriptor,
network: Network.testnet,
persister: Persister.newInMemory(),
lookahead: 25,
);
}

void main() {
group('ActiveWalletNotifier', () {
test('set() with same wallet twice keeps state and avoids disposal', () {
var disposeCalls = 0;
final container = ProviderContainer(
overrides: [
walletDisposerProvider.overrideWithValue((wallet) {
disposeCalls += 1;
wallet.dispose();
}),
],
);
addTearDown(container.dispose);

final notifier = container.read(activeWalletProvider.notifier);
final wallet = _createTestWallet();

notifier.set(wallet);
notifier.set(wallet);

expect(disposeCalls, 0);
expect(identical(container.read(activeWalletProvider), wallet), isTrue);
});

test('disposes current wallet when provider is disposed', () {
var disposeCalls = 0;
final container = ProviderContainer(
overrides: [
walletDisposerProvider.overrideWithValue((wallet) {
disposeCalls += 1;
wallet.dispose();
}),
],
);

final notifier = container.read(activeWalletProvider.notifier);
notifier.set(_createTestWallet());

container.dispose();

expect(disposeCalls, 1);
});

test('clear() disposes wallet and sets state to null', () {
var disposeCalls = 0;
final container = ProviderContainer(
overrides: [
walletDisposerProvider.overrideWithValue((wallet) {
disposeCalls += 1;
wallet.dispose();
}),
],
);
addTearDown(container.dispose);

final notifier = container.read(activeWalletProvider.notifier);
notifier.set(_createTestWallet());

notifier.clear();

expect(disposeCalls, 1);
expect(container.read(activeWalletProvider), isNull);
});
});
}
20 changes: 20 additions & 0 deletions bdk_demo/test/services/wallet_network_mapper_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:bdk_dart/bdk.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bdk_demo/models/wallet_record.dart';
import 'package:bdk_demo/services/wallet_network_mapper.dart';

void main() {
group('WalletNetworkX.toBdkNetwork', () {
test('maps signet correctly', () {
expect(WalletNetwork.signet.toBdkNetwork(), Network.signet);
});

test('maps testnet correctly', () {
expect(WalletNetwork.testnet.toBdkNetwork(), Network.testnet);
});

test('maps regtest correctly', () {
expect(WalletNetwork.regtest.toBdkNetwork(), Network.regtest);
});
});
}
Loading