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
27 changes: 27 additions & 0 deletions lib/pages/coin_control/utxo_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../db/isar/main_db.dart';
import '../../models/isar/models/isar_models.dart';
import '../../providers/global/wallets_provider.dart';
import '../../providers/wallet/address_label_provider.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/amount/amount.dart';
import '../../utilities/amount/amount_formatter.dart';
Expand Down Expand Up @@ -110,6 +111,15 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
if (snapshot.hasData) {
utxo = snapshot.data!;
}
final addressLabel = utxo.address == null
? null
: ref.watch(
pAddressLabel((
walletId: widget.walletId,
address: utxo.address!,
)),
);

return Row(
children: [
ConditionalParent(
Expand Down Expand Up @@ -179,6 +189,23 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
),
],
),
if (addressLabel != null &&
addressLabel.value.isNotEmpty)
Row(
children: [
Flexible(
child: Text(
addressLabel.value,
style:
STextStyles.w500_12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
),
],
),
],
),
),
Expand Down
71 changes: 71 additions & 0 deletions lib/providers/wallet/address_label_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar_community/isar.dart';

import '../../models/isar/models/address_label.dart';
import '../db/main_db_provider.dart';

typedef AddressLabelKey = ({String walletId, String address});

abstract interface class AddressLabelStore {
AddressLabel? find(AddressLabelKey key);

Stream<List<AddressLabel>> watch(AddressLabelKey key);
}

class _MainDBAddressLabelStore implements AddressLabelStore {
const _MainDBAddressLabelStore(this.isar);

final Isar isar;

QueryBuilder<AddressLabel, AddressLabel, QAfterWhereClause> _query(
AddressLabelKey key,
) => isar.addressLabels.where().addressStringWalletIdEqualTo(
key.address,
key.walletId,
);

@override
AddressLabel? find(AddressLabelKey key) => _query(key).findFirstSync();

@override
Stream<List<AddressLabel>> watch(AddressLabelKey key) =>
_query(key).watch(fireImmediately: true);
}

final addressLabelStoreProvider = Provider<AddressLabelStore>((ref) {
return _MainDBAddressLabelStore(ref.watch(mainDBProvider).isar);
});

class _AddressLabelWatcher extends ChangeNotifier {
_AddressLabelWatcher(AddressLabelStore store, AddressLabelKey key)
: _value = store.find(key) {
_subscription = store.watch(key).listen((labels) {
_value = labels.firstOrNull;
notifyListeners();
});
}

late final StreamSubscription<List<AddressLabel>> _subscription;
AddressLabel? _value;

AddressLabel? get value => _value;

@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}

final _addressLabelWatcherProvider = ChangeNotifierProvider.autoDispose
.family<_AddressLabelWatcher, AddressLabelKey>((ref, key) {
return _AddressLabelWatcher(ref.watch(addressLabelStoreProvider), key);
});

final pAddressLabel = Provider.autoDispose
.family<AddressLabel?, AddressLabelKey>(
(ref, key) => ref.watch(_addressLabelWatcherProvider(key)).value,
);
92 changes: 92 additions & 0 deletions test/providers/wallet/address_label_provider_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stackwallet/models/isar/models/address_label.dart';
import 'package:stackwallet/providers/wallet/address_label_provider.dart';

void main() {
late _FakeAddressLabelStore store;

setUp(() => store = _FakeAddressLabelStore());
tearDown(() => store.dispose());

testWidgets('updates a visible label without another data event', (
tester,
) async {
store.value = _label('Original label');

await tester.pumpWidget(
ProviderScope(
overrides: [addressLabelStoreProvider.overrideWithValue(store)],
child: const MaterialApp(home: _LabelText()),
),
);
expect(find.text('Original label'), findsOneWidget);

store.emit(_label('Updated label'));
await tester.pump();
await tester.pump();

expect(find.text('Updated label'), findsOneWidget);
expect(find.text('Original label'), findsNothing);
});

testWidgets('reacts when a label is created and deleted', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [addressLabelStoreProvider.overrideWithValue(store)],
child: const MaterialApp(home: _LabelText()),
),
);
expect(find.text('No label'), findsOneWidget);

store.emit(_label('New label'));
await tester.pump();
await tester.pump();
expect(find.text('New label'), findsOneWidget);

store.emit(null);
await tester.pump();
await tester.pump();
expect(find.text('No label'), findsOneWidget);
});
}

AddressLabel _label(String value) => AddressLabel(
walletId: 'wallet',
addressString: 'address',
value: value,
tags: null,
);

class _FakeAddressLabelStore implements AddressLabelStore {
final _controller = StreamController<List<AddressLabel>>.broadcast();
AddressLabel? value;

@override
AddressLabel? find(AddressLabelKey key) => value;

@override
Stream<List<AddressLabel>> watch(AddressLabelKey key) => _controller.stream;

void emit(AddressLabel? label) {
value = label;
_controller.add(label == null ? [] : [label]);
}

void dispose() => _controller.close();
}

class _LabelText extends ConsumerWidget {
const _LabelText();

@override
Widget build(BuildContext context, WidgetRef ref) {
final label = ref.watch(
pAddressLabel((walletId: 'wallet', address: 'address')),
);
return Text(label?.value ?? 'No label');
}
}
Loading