Skip to content

Commit fdc2c3f

Browse files
committed
feat: coin control row tap-to-toggle and per-address options menu
Tapping an address/output row now toggles its selection instead of opening the details view. A vertical ellipsis options button is added to the right of each row to reach the address options. Keeps the filtering, WillPopScope migration, and the UTXOConfirmedStatus deduplication from the prior work. closes #406
1 parent 51db6c7 commit fdc2c3f

5 files changed

Lines changed: 118 additions & 98 deletions

File tree

lib/pages/coin_control/coin_control_view.dart

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import '../../utilities/assets.dart';
2626
import '../../utilities/constants.dart';
2727
import '../../utilities/text_styles.dart';
2828
import '../../wallets/isar/providers/wallet_info_provider.dart';
29-
import '../../wallets/wallet/impl/namecoin_wallet.dart';
30-
import '../../wallets/wallet/wallet.dart';
3129
import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
3230
import '../../widgets/animated_widgets/rotate_icon.dart';
3331
import '../../widgets/app_bar_field.dart';
@@ -42,6 +40,7 @@ import '../../widgets/rounded_container.dart';
4240
import '../../widgets/rounded_white_container.dart';
4341
import '../../widgets/toggle.dart';
4442
import 'utxo_card.dart';
43+
import 'utxo_confirmed_status.dart';
4544
import 'utxo_details_view.dart';
4645

4746
enum CoinControlViewType { manage, use }
@@ -87,18 +86,6 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
8786
await coinControlInterface.updateBalance();
8887
}
8988

90-
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
91-
if (wallet is NamecoinWallet) {
92-
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
93-
} else {
94-
return utxo.isConfirmed(
95-
currentChainHeight,
96-
wallet.cryptoCurrency.minConfirms,
97-
wallet.cryptoCurrency.minCoinbaseConfirms,
98-
);
99-
}
100-
}
101-
10289
@override
10390
void initState() {
10491
if (widget.selectedUTXOs != null) {
@@ -123,21 +110,21 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
123110
Widget build(BuildContext context) {
124111
debugPrint("BUILD: $runtimeType");
125112

126-
final minConfirms =
127-
ref
128-
.watch(pWallets)
129-
.getWallet(widget.walletId)
130-
.cryptoCurrency
131-
.minConfirms;
132-
133113
final coin = ref.watch(pWalletCoin(widget.walletId));
134114
final currentHeight = ref.watch(pWalletChainHeight(widget.walletId));
135115

116+
final CCFilter _filter =
117+
_isSearching
118+
? CCFilter.all
119+
: _showBlocked
120+
? CCFilter.frozen
121+
: CCFilter.available;
122+
136123
if (_sort == CCSortDescriptor.address && !_isSearching) {
137124
_list = null;
138125
_map = MainDB.instance.queryUTXOsGroupedByAddressSync(
139126
walletId: widget.walletId,
140-
filter: CCFilter.all,
127+
filter: _filter,
141128
sort: _sort,
142129
searchTerm: "",
143130
cryptoCurrency: coin,
@@ -146,25 +133,21 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
146133
_map = null;
147134
_list = MainDB.instance.queryUTXOsSync(
148135
walletId: widget.walletId,
149-
filter:
150-
_isSearching
151-
? CCFilter.all
152-
: _showBlocked
153-
? CCFilter.frozen
154-
: CCFilter.available,
136+
filter: _filter,
155137
sort: _sort,
156138
searchTerm: _isSearching ? searchController.text : "",
157139
cryptoCurrency: coin,
158140
);
159141
}
160142

161-
return WillPopScope(
162-
onWillPop: () async {
143+
return PopScope(
144+
canPop: false,
145+
onPopInvokedWithResult: (didPop, _) {
146+
if (didPop) return;
163147
unawaited(_refreshBalance());
164148
Navigator.of(context).pop(
165149
widget.type == CoinControlViewType.use ? _selectedAvailable : null,
166150
);
167-
return false;
168151
},
169152
child: Background(
170153
child: Scaffold(
@@ -291,8 +274,8 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
291274
RoundedWhiteContainer(
292275
child: Text(
293276
"This option allows you to control, freeze, and utilize "
294-
"outputs at your discretion. Tap the output circle to "
295-
"select.",
277+
"outputs at your discretion. Tap an output to select it, "
278+
"or use the options button for more actions.",
296279
style: STextStyles.w500_14(context).copyWith(
297280
color:
298281
Theme.of(
@@ -302,7 +285,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
302285
),
303286
),
304287
if (!_isSearching) const SizedBox(height: 10),
305-
if (!(_isSearching || _map != null))
288+
if (!_isSearching)
306289
SizedBox(
307290
height: 48,
308291
child: Toggle(
@@ -360,8 +343,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
360343
CoinControlViewType.manage ||
361344
(widget.type == CoinControlViewType.use &&
362345
!utxo.isBlocked &&
363-
_isConfirmed(
364-
utxo,
346+
utxo.isConfirmedStatus(
365347
currentHeight,
366348
ref.watch(
367349
pWallets.select(
@@ -384,7 +366,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
384366
}
385367
setState(() {});
386368
},
387-
onPressed: () async {
369+
onOptionsPressed: () async {
388370
final result = await Navigator.of(
389371
context,
390372
).pushNamed(
@@ -434,8 +416,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
434416
(widget.type ==
435417
CoinControlViewType.use &&
436418
!_showBlocked &&
437-
_isConfirmed(
438-
utxo,
419+
utxo.isConfirmedStatus(
439420
currentHeight,
440421
ref.watch(
441422
pWallets.select(
@@ -458,7 +439,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
458439
}
459440
setState(() {});
460441
},
461-
onPressed: () async {
442+
onOptionsPressed: () async {
462443
final result = await Navigator.of(
463444
context,
464445
).pushNamed(
@@ -590,8 +571,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
590571
CoinControlViewType
591572
.use &&
592573
!utxo.isBlocked &&
593-
_isConfirmed(
594-
utxo,
574+
utxo.isConfirmedStatus(
595575
currentHeight,
596576
ref.watch(
597577
pWallets.select(
@@ -621,7 +601,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
621601
}
622602
setState(() {});
623603
},
624-
onPressed: () async {
604+
onOptionsPressed: () async {
625605
final result =
626606
await Navigator.of(
627607
context,

lib/pages/coin_control/utxo_card.dart

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010

1111
import 'package:flutter/material.dart';
1212
import 'package:flutter_riverpod/flutter_riverpod.dart';
13+
import 'package:flutter_svg/flutter_svg.dart';
1314

1415
import '../../db/isar/main_db.dart';
1516
import '../../models/isar/models/isar_models.dart';
1617
import '../../providers/global/wallets_provider.dart';
1718
import '../../themes/stack_colors.dart';
1819
import '../../utilities/amount/amount.dart';
1920
import '../../utilities/amount/amount_formatter.dart';
21+
import '../../utilities/assets.dart';
2022
import '../../utilities/constants.dart';
2123
import '../../utilities/text_styles.dart';
2224
import '../../wallets/isar/providers/wallet_info_provider.dart';
23-
import '../../wallets/wallet/impl/namecoin_wallet.dart';
24-
import '../../wallets/wallet/wallet.dart';
2525
import '../../widgets/conditional_parent.dart';
26+
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
2627
import '../../widgets/icon_widgets/utxo_status_icon.dart';
2728
import '../../widgets/rounded_container.dart';
29+
import 'utxo_confirmed_status.dart';
2830

2931
class UtxoCard extends ConsumerStatefulWidget {
3032
const UtxoCard({
@@ -34,14 +36,14 @@ class UtxoCard extends ConsumerStatefulWidget {
3436
required this.onSelectedChanged,
3537
required this.initialSelectedState,
3638
required this.canSelect,
37-
this.onPressed,
39+
this.onOptionsPressed,
3840
});
3941

4042
final String walletId;
4143
final UTXO utxo;
4244
final void Function(bool) onSelectedChanged;
4345
final bool initialSelectedState;
44-
final VoidCallback? onPressed;
46+
final VoidCallback? onOptionsPressed;
4547
final bool canSelect;
4648

4749
@override
@@ -54,18 +56,6 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
5456

5557
late bool _selected;
5658

57-
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
58-
if (wallet is NamecoinWallet) {
59-
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
60-
} else {
61-
return utxo.isConfirmed(
62-
currentChainHeight,
63-
wallet.cryptoCurrency.minConfirms,
64-
wallet.cryptoCurrency.minCoinbaseConfirms,
65-
);
66-
}
67-
}
68-
6959
@override
7060
void initState() {
7161
_selected = widget.initialSelectedState;
@@ -75,6 +65,12 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
7565
super.initState();
7666
}
7767

68+
void _toggleSelected() {
69+
_selected = !_selected;
70+
widget.onSelectedChanged(_selected);
71+
setState(() {});
72+
}
73+
7874
@override
7975
Widget build(BuildContext context) {
8076
debugPrint("BUILD: $runtimeType");
@@ -83,7 +79,7 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
8379
final currentHeight = ref.watch(pWalletChainHeight(widget.walletId));
8480

8581
return ConditionalParent(
86-
condition: widget.onPressed != null,
82+
condition: widget.canSelect,
8783
builder: (child) => MaterialButton(
8884
padding: const EdgeInsets.all(0),
8985
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
@@ -97,13 +93,11 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
9793
borderRadius:
9894
BorderRadius.circular(Constants.size.circularBorderRadius),
9995
),
100-
onPressed: widget.onPressed,
96+
onPressed: _toggleSelected,
10197
child: child,
10298
),
10399
child: RoundedContainer(
104-
color: widget.onPressed == null
105-
? Theme.of(context).extension<StackColors>()!.popupBG
106-
: Colors.transparent,
100+
color: Theme.of(context).extension<StackColors>()!.popupBG,
107101
child: StreamBuilder<UTXO?>(
108102
stream: stream,
109103
builder: (context, snapshot) {
@@ -115,17 +109,12 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
115109
ConditionalParent(
116110
condition: widget.canSelect,
117111
builder: (child) => GestureDetector(
118-
onTap: () {
119-
_selected = !_selected;
120-
widget.onSelectedChanged(_selected);
121-
setState(() {});
122-
},
112+
onTap: _toggleSelected,
123113
child: child,
124114
),
125115
child: UTXOStatusIcon(
126116
blocked: utxo.isBlocked,
127-
status: _isConfirmed(
128-
utxo,
117+
status: utxo.isConfirmedStatus(
129118
currentHeight,
130119
ref.watch(
131120
pWallets.select(
@@ -182,6 +171,25 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
182171
],
183172
),
184173
),
174+
if (widget.onOptionsPressed != null)
175+
const SizedBox(
176+
width: 10,
177+
),
178+
if (widget.onOptionsPressed != null)
179+
AppBarIconButton(
180+
size: 36,
181+
shadows: const [],
182+
color: Theme.of(context).extension<StackColors>()!.popupBG,
183+
icon: SvgPicture.asset(
184+
Assets.svg.verticalEllipsis,
185+
color: Theme.of(context)
186+
.extension<StackColors>()!
187+
.textSubtitle1,
188+
width: 20,
189+
height: 20,
190+
),
191+
onPressed: widget.onOptionsPressed,
192+
),
185193
],
186194
);
187195
},
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* This file is part of Stack Wallet.
3+
*
4+
* Copyright (c) 2023 Cypher Stack
5+
* All Rights Reserved.
6+
* The code is distributed under GPLv3 license, see LICENSE file for details.
7+
* Generated by Cypher Stack on 2023-05-26
8+
*
9+
*/
10+
11+
import '../../models/isar/models/isar_models.dart';
12+
import '../../wallets/wallet/impl/namecoin_wallet.dart';
13+
import '../../wallets/wallet/wallet.dart';
14+
15+
extension UTXOConfirmedStatus on UTXO {
16+
bool isConfirmedStatus(int currentChainHeight, Wallet wallet) {
17+
if (wallet is NamecoinWallet) {
18+
return wallet.checkUtxoConfirmed(this, currentChainHeight);
19+
} else {
20+
return isConfirmed(
21+
currentChainHeight,
22+
wallet.cryptoCurrency.minConfirms,
23+
wallet.cryptoCurrency.minCoinbaseConfirms,
24+
);
25+
}
26+
}
27+
}

lib/pages/coin_control/utxo_details_view.dart

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import '../../utilities/amount/amount_formatter.dart';
2323
import '../../utilities/text_styles.dart';
2424
import '../../utilities/util.dart';
2525
import '../../wallets/isar/providers/wallet_info_provider.dart';
26-
import '../../wallets/wallet/impl/namecoin_wallet.dart';
27-
import '../../wallets/wallet/wallet.dart';
2826
import '../../widgets/background.dart';
2927
import '../../widgets/conditional_parent.dart';
3028
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
@@ -36,6 +34,7 @@ import '../../widgets/desktop/secondary_button.dart';
3634
import '../../widgets/icon_widgets/utxo_status_icon.dart';
3735
import '../../widgets/rounded_container.dart';
3836
import '../wallet_view/transaction_views/transaction_details_view.dart' as tdv;
37+
import 'utxo_confirmed_status.dart';
3938

4039
class UtxoDetailsView extends ConsumerStatefulWidget {
4140
const UtxoDetailsView({
@@ -69,18 +68,6 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
6968
await MainDB.instance.putUTXO(utxo!.copyWith(isBlocked: !utxo!.isBlocked));
7069
}
7170

72-
bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) {
73-
if (wallet is NamecoinWallet) {
74-
return wallet.checkUtxoConfirmed(utxo, currentChainHeight);
75-
} else {
76-
return utxo.isConfirmed(
77-
currentChainHeight,
78-
wallet.cryptoCurrency.minConfirms,
79-
wallet.cryptoCurrency.minCoinbaseConfirms,
80-
);
81-
}
82-
}
83-
8471
@override
8572
void initState() {
8673
utxo =
@@ -110,8 +97,7 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
11097
final coin = ref.watch(pWalletCoin(widget.walletId));
11198
final currentHeight = ref.watch(pWalletChainHeight(widget.walletId));
11299

113-
final confirmed = _isConfirmed(
114-
utxo!,
100+
final confirmed = utxo!.isConfirmedStatus(
115101
currentHeight,
116102
ref.watch(pWallets.select((s) => s.getWallet(widget.walletId))),
117103
);

0 commit comments

Comments
 (0)