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
12 changes: 10 additions & 2 deletions lib/pages/receive_view/receive_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import '../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dar
import '../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../widgets/address_label_editor.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
Expand Down Expand Up @@ -161,8 +162,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {

if (slatepackString == null) return;
if (mounted) {
final wallet =
ref.read(pWallets).getWallet(walletId) as EpiccashWallet;
final wallet = ref.read(pWallets).getWallet(walletId) as EpiccashWallet;

Exception? ex;
final result = await showLoading(
Expand Down Expand Up @@ -792,6 +792,14 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
),
),
const SizedBox(height: 12),
RoundedWhiteContainer(
child: AddressLabelEditor(
walletId: walletId,
address: address,
isDesktop: false,
),
),
const SizedBox(height: 12),
PrimaryButton(
label: "Copy address",
onPressed: () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interfa
import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
import '../../../../widgets/address_label_editor.dart';
import '../../../../widgets/conditional_parent.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_loading_overlay.dart';
Expand Down Expand Up @@ -174,8 +175,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
}

Future<void> _onEpicReceiveSlatePressed() async {
final wallet =
ref.read(pWallets).getWallet(walletId) as EpiccashWallet;
final wallet = ref.read(pWallets).getWallet(walletId) as EpiccashWallet;

Exception? ex;
final result = await showLoading(
Expand Down Expand Up @@ -605,9 +605,11 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
),
),
),
if (!((isMimblewimblecoin || isEpiccash) && ref.watch(pIsSlatepack(widget.walletId))))
if (!((isMimblewimblecoin || isEpiccash) &&
ref.watch(pIsSlatepack(widget.walletId))))
const SizedBox(height: 20),
if (!((isMimblewimblecoin || isEpiccash) && ref.watch(pIsSlatepack(widget.walletId))))
if (!((isMimblewimblecoin || isEpiccash) &&
ref.watch(pIsSlatepack(widget.walletId))))
ConditionalParent(
condition: showMultiType,
builder: (child) => Column(
Expand Down Expand Up @@ -762,6 +764,28 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
),
),

const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(
context,
).extension<StackColors>()!.backgroundAppBar,
width: 1,
),
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: RoundedWhiteContainer(
child: AddressLabelEditor(
walletId: walletId,
address: address,
isDesktop: true,
),
),
),

if (canGen) const SizedBox(height: 20),

if (canGen)
Expand All @@ -778,7 +802,8 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
label: "Generate new address",
),
const SizedBox(height: 20),
if ((isMimblewimblecoin || isEpiccash) && ref.watch(pIsSlatepack(widget.walletId)))
if ((isMimblewimblecoin || isEpiccash) &&
ref.watch(pIsSlatepack(widget.walletId)))
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expand Down Expand Up @@ -909,15 +934,18 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {

// TODO: create transparent button class to account for hover
// Conditional logic for 'Submit' button or QR code
if ((isMimblewimblecoin || isEpiccash) && ref.watch(pIsSlatepack(widget.walletId)))
if ((isMimblewimblecoin || isEpiccash) &&
ref.watch(pIsSlatepack(widget.walletId)))
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
label: "Receive Slatepack",
enabled: _slateToggleFlag,
onPressed: _slateToggleFlag
? (isEpiccash ? _onEpicReceiveSlatePressed : _onReceiveSlatePressed)
? (isEpiccash
? _onEpicReceiveSlatePressed
: _onReceiveSlatePressed)
: null,
),
)
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,
);
119 changes: 119 additions & 0 deletions lib/widgets/address_label_editor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../db/isar/main_db.dart';
import '../models/isar/models/address_label.dart';
import '../notifications/show_flush_bar.dart';
import '../providers/db/main_db_provider.dart';
import '../providers/wallet/address_label_provider.dart';
import '../themes/stack_colors.dart';
import '../utilities/logger.dart';
import '../utilities/text_styles.dart';
import 'custom_buttons/simple_edit_button.dart';

abstract interface class AddressLabelWriter {
Future<void> write(AddressLabelKey key, String value);
}

class _MainDBAddressLabelWriter implements AddressLabelWriter {
const _MainDBAddressLabelWriter(this.db);

final MainDB db;

@override
Future<void> write(AddressLabelKey key, String value) async {
final existing = db.getAddressLabelSync(key.walletId, key.address);
await db.putAddressLabel(
existing?.copyWith(label: value) ??
AddressLabel(
walletId: key.walletId,
addressString: key.address,
value: value,
tags: null,
),
);
}
}

final addressLabelWriterProvider = Provider<AddressLabelWriter>((ref) {
return _MainDBAddressLabelWriter(ref.watch(mainDBProvider));
});

class AddressLabelEditor extends ConsumerStatefulWidget {
const AddressLabelEditor({
super.key,
required this.walletId,
required this.address,
required this.isDesktop,
});

final String walletId;
final String address;
final bool isDesktop;

@override
ConsumerState<AddressLabelEditor> createState() => _AddressLabelEditorState();
}

class _AddressLabelEditorState extends ConsumerState<AddressLabelEditor> {
Future<void> _pendingWrite = Future.value();

// Each write is a read-then-put on the unique (address, wallet) index, so
// writes are chained: two concurrent creates would race and the loser would
// throw. The key and writer are bound when the edit starts rather than when
// the edit view returns, because the receiving address can rotate while it
// is open and this state may be unmounted by then.
void _queueWrite(
AddressLabelKey key,
AddressLabelWriter writer,
String value,
) {
_pendingWrite = _pendingWrite.then((_) async {
try {
await writer.write(key, value);
} catch (error, stackTrace) {
Logging.instance.w(
'Failed to update address label',
error: error,
stackTrace: stackTrace,
);
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: 'Failed to update address label',
context: context,
),
);
}
}
});
}

@override
Widget build(BuildContext context) {
final key = (walletId: widget.walletId, address: widget.address);
final writer = ref.watch(addressLabelWriterProvider);
final value = ref.watch(pAddressLabel(key))?.value ?? '';
final style = widget.isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
)
: STextStyles.itemSubtitle(context);

return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(value.isEmpty ? 'No label' : value, style: style)),
SimpleEditButton(
editValue: value,
editLabel: 'label',
overrideTitle: 'Edit label',
onValueChanged: (newValue) => _queueWrite(key, writer, newValue),
),
],
);
}
}
Loading
Loading