Skip to content

Commit 7ba1014

Browse files
Merge pull request #1293 from levoncrypto/fix-create-masternode-desktop-dialog
Fix create masternode desktop dialog
2 parents f6a7cae + 2a61446 commit 7ba1014

12 files changed

Lines changed: 1101 additions & 467 deletions

File tree

lib/pages/masternodes/create_masternode_view.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ class CreateMasternodeView extends ConsumerStatefulWidget {
1414
const CreateMasternodeView({
1515
super.key,
1616
required this.firoWalletId,
17+
required this.collateralTxid,
18+
required this.collateralVout,
19+
required this.collateralAddress,
1720
this.popTxidOnSuccess = true,
1821
});
1922

2023
static const routeName = "/createMasternodeView";
2124

2225
final String firoWalletId;
26+
final String collateralTxid;
27+
final int collateralVout;
28+
final String collateralAddress;
2329
final bool popTxidOnSuccess;
2430

2531
@override
@@ -107,6 +113,9 @@ class _CreateMasternodeDialogState extends ConsumerState<CreateMasternodeView> {
107113
),
108114
child: RegisterMasternodeForm(
109115
firoWalletId: widget.firoWalletId,
116+
collateralTxid: widget.collateralTxid,
117+
collateralVout: widget.collateralVout,
118+
collateralAddress: widget.collateralAddress,
110119
onRegistrationSuccess: (txid) {
111120
if (widget.popTxidOnSuccess && mounted) {
112121
Navigator.of(context, rootNavigator: Util.isDesktop).pop(txid);

lib/pages/masternodes/masternodes_home_view.dart

Lines changed: 210 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/material.dart';
24
import 'package:flutter_riverpod/flutter_riverpod.dart';
35
import 'package:flutter_svg/svg.dart';
4-
56
import '../../providers/global/wallets_provider.dart';
67
import '../../themes/stack_colors.dart';
8+
import '../../utilities/amount/amount.dart';
79
import '../../utilities/assets.dart';
810
import '../../utilities/logger.dart';
911
import '../../utilities/text_styles.dart';
1012
import '../../utilities/util.dart';
13+
import '../../models/isar/models/blockchain_data/utxo.dart';
14+
import '../../wallets/isar/models/wallet_info.dart';
1115
import '../../wallets/wallet/impl/firo_wallet.dart';
1216
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
1317
import '../../widgets/desktop/desktop_app_bar.dart';
@@ -34,15 +38,206 @@ class MasternodesHomeView extends ConsumerStatefulWidget {
3438

3539
class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
3640
late Future<List<MasternodeInfo>> _masternodesFuture;
41+
bool _hasPromptedForCollateral = false;
42+
bool _isCheckingForCollateral = false;
43+
44+
Set<String> _dismissedCollateral(FiroWallet wallet) {
45+
final raw =
46+
wallet.info.otherData[WalletInfoKeys.firoMasternodeCollateralDismissed];
47+
if (raw is! List) {
48+
return {};
49+
}
50+
return raw.whereType<String>().toSet();
51+
}
3752

38-
Future<void> _showDesktopCreateMasternodeDialog() async {
39-
final txid = await showDialog<Object>(
40-
context: context,
41-
barrierDismissible: true,
42-
builder: (context) =>
43-
SDialog(child: CreateMasternodeView(firoWalletId: widget.walletId)),
53+
Future<void> _persistDismissedCollateral(
54+
FiroWallet wallet,
55+
String txid,
56+
int vout,
57+
) async {
58+
final set = _dismissedCollateral(wallet);
59+
set.add("$txid:$vout");
60+
await wallet.info.updateOtherData(
61+
newEntries: {
62+
WalletInfoKeys.firoMasternodeCollateralDismissed: set.toList(),
63+
},
64+
isar: wallet.mainDB.isar,
4465
);
45-
_handleSuccessTxid(txid);
66+
}
67+
68+
Future<({String txid, int vout, String address})?>
69+
_findCollateralUtxo() async {
70+
final wallet = ref.read(pWallets).getWallet(widget.walletId) as FiroWallet;
71+
final List<UTXO> utxos =
72+
await (wallet.mainDB.getUTXOs(widget.walletId) as dynamic).findAll()
73+
as List<UTXO>;
74+
final currentChainHeight = await wallet.chainHeight;
75+
final masternodeRaw = Amount.fromDecimal(
76+
kMasterNodeValue,
77+
fractionDigits: wallet.cryptoCurrency.fractionDigits,
78+
).raw.toInt();
79+
80+
for (final utxo in utxos) {
81+
if (utxo.value == masternodeRaw &&
82+
!utxo.isBlocked &&
83+
utxo.used != true &&
84+
utxo.isConfirmed(
85+
currentChainHeight,
86+
wallet.cryptoCurrency.minConfirms,
87+
wallet.cryptoCurrency.minCoinbaseConfirms,
88+
) &&
89+
utxo.address != null) {
90+
return (txid: utxo.txid, vout: utxo.vout, address: utxo.address!);
91+
}
92+
}
93+
return null;
94+
}
95+
96+
Future<void> _createMasternode() async {
97+
final collateral = await _findCollateralUtxo();
98+
if (!mounted) {
99+
return;
100+
}
101+
102+
if (collateral == null) {
103+
await showDialog<void>(
104+
context: context,
105+
builder: (_) => StackOkDialog(
106+
title: "No collateral found",
107+
message:
108+
"A masternode needs one confirmed, unblocked transparent "
109+
"UTXO of exactly 1000 FIRO.\n\n"
110+
"Total balance above 1000 FIRO is not enough if no single "
111+
"1000 output exists. Also ensure fee is not subtracted from "
112+
"the recipient amount when sending to yourself.",
113+
desktopPopRootNavigator: Util.isDesktop,
114+
maxWidth: Util.isDesktop ? 400 : null,
115+
),
116+
);
117+
return;
118+
}
119+
120+
if (Util.isDesktop) {
121+
final txid = await showDialog<Object>(
122+
context: context,
123+
barrierDismissible: true,
124+
builder: (context) => SDialog(
125+
child: CreateMasternodeView(
126+
firoWalletId: widget.walletId,
127+
collateralTxid: collateral.txid,
128+
collateralVout: collateral.vout,
129+
collateralAddress: collateral.address,
130+
),
131+
),
132+
);
133+
_handleSuccessTxid(txid);
134+
} else {
135+
final txid = await Navigator.of(context).pushNamed(
136+
CreateMasternodeView.routeName,
137+
arguments: {
138+
'walletId': widget.walletId,
139+
'collateralTxid': collateral.txid,
140+
'collateralVout': collateral.vout,
141+
'collateralAddress': collateral.address,
142+
},
143+
);
144+
_handleSuccessTxid(txid);
145+
}
146+
}
147+
148+
Future<void> _maybePromptForExistingCollateral() async {
149+
if (_hasPromptedForCollateral || _isCheckingForCollateral || !mounted) {
150+
return;
151+
}
152+
_isCheckingForCollateral = true;
153+
154+
try {
155+
final collateral = await _findCollateralUtxo();
156+
if (collateral == null || !mounted) {
157+
return;
158+
}
159+
160+
final wallet =
161+
ref.read(pWallets).getWallet(widget.walletId) as FiroWallet;
162+
final dismissed = _dismissedCollateral(wallet);
163+
final collateralKey = "${collateral.txid}:${collateral.vout}";
164+
if (dismissed.contains(collateralKey)) {
165+
return;
166+
}
167+
168+
_hasPromptedForCollateral = true;
169+
170+
final wantsMN = await showDialog<bool>(
171+
context: context,
172+
barrierDismissible: true,
173+
builder: (ctx) => StackDialog(
174+
title: "Register Masternode?",
175+
message:
176+
"A 1000 FIRO collateral UTXO was found in your wallet. "
177+
"Would you like to register a masternode now?",
178+
leftButton: TextButton(
179+
style: Theme.of(
180+
ctx,
181+
).extension<StackColors>()!.getSecondaryEnabledButtonStyle(ctx),
182+
child: Text(
183+
"Later",
184+
style: STextStyles.button(ctx).copyWith(
185+
color: Theme.of(ctx).extension<StackColors>()!.accentColorDark,
186+
),
187+
),
188+
onPressed: () => Navigator.of(ctx).pop(false),
189+
),
190+
rightButton: TextButton(
191+
style: Theme.of(
192+
ctx,
193+
).extension<StackColors>()!.getPrimaryEnabledButtonStyle(ctx),
194+
child: Text("Register", style: STextStyles.button(ctx)),
195+
onPressed: () => Navigator.of(ctx).pop(true),
196+
),
197+
),
198+
);
199+
200+
if (wantsMN == false || wantsMN == null) {
201+
await _persistDismissedCollateral(
202+
wallet,
203+
collateral.txid,
204+
collateral.vout,
205+
);
206+
}
207+
208+
if (wantsMN != true || !mounted) {
209+
return;
210+
}
211+
212+
if (Util.isDesktop) {
213+
final txid = await showDialog<Object>(
214+
context: context,
215+
barrierDismissible: true,
216+
builder: (context) => SDialog(
217+
child: CreateMasternodeView(
218+
firoWalletId: widget.walletId,
219+
collateralTxid: collateral.txid,
220+
collateralVout: collateral.vout,
221+
collateralAddress: collateral.address,
222+
),
223+
),
224+
);
225+
_handleSuccessTxid(txid);
226+
} else {
227+
final txid = await Navigator.of(context).pushNamed(
228+
CreateMasternodeView.routeName,
229+
arguments: {
230+
'walletId': widget.walletId,
231+
'collateralTxid': collateral.txid,
232+
'collateralVout': collateral.vout,
233+
'collateralAddress': collateral.address,
234+
},
235+
);
236+
_handleSuccessTxid(txid);
237+
}
238+
} finally {
239+
_isCheckingForCollateral = false;
240+
}
46241
}
47242

48243
void _handleSuccessTxid(Object? txid) {
@@ -75,10 +270,13 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
75270
void initState() {
76271
super.initState();
77272

78-
// TODO polling and update on successful registration
79273
_masternodesFuture =
80274
(ref.read(pWallets).getWallet(widget.walletId) as FiroWallet)
81275
.getMyMasternodes();
276+
277+
WidgetsBinding.instance.addPostFrameCallback((_) {
278+
unawaited(_maybePromptForExistingCollateral());
279+
});
82280
}
83281

84282
@override
@@ -143,7 +341,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
143341
.srcIn,
144342
),
145343
),
146-
onPressed: _showDesktopCreateMasternodeDialog,
344+
onPressed: _createMasternode,
147345
),
148346
),
149347
)
@@ -184,13 +382,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
184382
width: 20,
185383
height: 20,
186384
),
187-
onPressed: () async {
188-
final txid = await Navigator.of(context).pushNamed(
189-
CreateMasternodeView.routeName,
190-
arguments: widget.walletId,
191-
);
192-
_handleSuccessTxid(txid);
193-
},
385+
onPressed: _createMasternode,
194386
),
195387
),
196388
),
@@ -229,17 +421,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
229421
label: "Create Your First Masternode",
230422
horizontalContentPadding: 16,
231423
buttonHeight: Util.isDesktop ? .l : null,
232-
onPressed: () async {
233-
if (Util.isDesktop) {
234-
await _showDesktopCreateMasternodeDialog();
235-
} else {
236-
final txid = await Navigator.of(context).pushNamed(
237-
CreateMasternodeView.routeName,
238-
arguments: widget.walletId,
239-
);
240-
_handleSuccessTxid(txid);
241-
}
242-
},
424+
onPressed: _createMasternode,
243425
),
244426
],
245427
),

0 commit comments

Comments
 (0)