extract master node info into separate widgets and improve look and feel
What changed, and why it matters
This commit is a routine user-interface refactor. It moves the masternode details display into a new reusable widget and updates the visual styling. There is no security-relevant change.
No security action required. This is a cosmetic refactor; review and merge as normal.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff extracts an inline _MasternodeInfoDialog from masternodes_home_view.dart into a new MasternodeInfoWidget in sub_widgets/masternode_info_widget.dart, adds a pretty() helper on MasternodeInfo in firo_wallet.dart, and switches the dialog to use the shared SDialog component. The only functional change is a minor formatting tweak to an error message (removing the word ‘node’). No cryptographic, network, authentication, or data-handling logic is modified.
Changed components
lib/pages/masternodes/masternodes_home_view.dartlib/pages/masternodes/sub_widgets/masternode_info_widget.dartlib/wallets/wallet/impl/firo_wallet.dartInspect captured patch +111 / −129
diff --git a/lib/pages/masternodes/masternodes_home_view.dart b/lib/pages/masternodes/masternodes_home_view.dart
index 3179972..013aa56 100644
--- a/lib/pages/masternodes/masternodes_home_view.dart
+++ b/lib/pages/masternodes/masternodes_home_view.dart
@@ -14,6 +14,7 @@ import '../../widgets/desktop/desktop_scaffold.dart';
import '../../widgets/desktop/primary_button.dart';
import '../../widgets/dialogs/s_dialog.dart';
import 'create_masternode_view.dart';
+import 'sub_widgets/masternode_info_widget.dart';
class MasternodesHomeView extends ConsumerStatefulWidget {
const MasternodesHomeView({super.key, required this.walletId});
@@ -502,134 +503,8 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
showDialog<void>(
context: context,
barrierDismissible: true,
- builder: (context) => _MasternodeInfoDialog(node: node),
- );
- }
-}
-
-class _MasternodeInfoDialog extends StatelessWidget {
- const _MasternodeInfoDialog({required this.node});
-
- final MasternodeInfo node;
-
- @override
- Widget build(BuildContext context) {
- final stack = Theme.of(context).extension<StackColors>()!;
- final status = node.revocationReason == 0 ? 'Active' : 'Revoked';
-
- return AlertDialog(
- backgroundColor: stack.popupBG,
- title: const Text('Masternode Information'),
- content: SizedBox(
- width: 500,
- child: SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- _buildInfoRow(context, 'ProTx Hash', node.proTxHash),
- _buildInfoRow(
- context,
- 'IP:Port',
- '${node.serviceAddr}:${node.servicePort}',
- ),
- _buildInfoRow(context, 'Status', status),
- _buildInfoRow(
- context,
- 'Registered Height',
- node.registeredHeight.toString(),
- ),
- _buildInfoRow(
- context,
- 'Last Paid Height',
- node.lastPaidHeight.toString(),
- ),
- _buildInfoRow(context, 'Payout Address', node.payoutAddress),
- _buildInfoRow(context, 'Owner Address', node.ownerAddress),
- _buildInfoRow(context, 'Voting Address', node.votingAddress),
- _buildInfoRow(
- context,
- 'Operator Public Key',
- node.pubKeyOperator,
- ),
- _buildInfoRow(
- context,
- 'Operator Reward',
- '${node.operatorReward / 100} %',
- ),
- _buildInfoRow(context, 'Collateral Hash', node.collateralHash),
- _buildInfoRow(
- context,
- 'Collateral Index',
- node.collateralIndex.toString(),
- ),
- _buildInfoRow(
- context,
- 'Collateral Address',
- node.collateralAddress,
- ),
- _buildInfoRow(
- context,
- 'Pose Penalty',
- node.posePenalty.toString(),
- ),
- _buildInfoRow(
- context,
- 'Pose Revived Height',
- node.poseRevivedHeight.toString(),
- ),
- _buildInfoRow(
- context,
- 'Pose Ban Height',
- node.poseBanHeight.toString(),
- ),
- _buildInfoRow(
- context,
- 'Revocation Reason',
- node.revocationReason.toString(),
- ),
- ],
- ),
- ),
- ),
- actions: [
- FilledButton(
- onPressed: () => Navigator.of(context).pop(),
- style: FilledButton.styleFrom(
- backgroundColor: stack.buttonBackPrimary,
- foregroundColor: stack.buttonTextPrimary,
- ),
- child: const Text('Close'),
- ),
- ],
- );
- }
-
- Widget _buildInfoRow(BuildContext context, String label, String value) {
- return Padding(
- padding: const EdgeInsets.only(bottom: 12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- label,
- style: STextStyles.w600_14(context).copyWith(
- color: Theme.of(context).extension<StackColors>()!.textSubtitle1,
- ),
- ),
- const SizedBox(height: 4),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(value, style: STextStyles.w500_12(context)),
- ),
- ],
+ builder: (context) => SDialog(
+ child: SizedBox(width: 600, child: MasternodeInfoWidget(info: node)),
),
);
}
diff --git a/lib/pages/masternodes/sub_widgets/masternode_info_widget.dart b/lib/pages/masternodes/sub_widgets/masternode_info_widget.dart
new file mode 100644
index 0000000..c25b696
--- /dev/null
+++ b/lib/pages/masternodes/sub_widgets/masternode_info_widget.dart
@@ -0,0 +1,85 @@
+import 'package:flutter/material.dart';
+
+import '../../../themes/stack_colors.dart';
+import '../../../utilities/text_styles.dart';
+import '../../../utilities/util.dart';
+import '../../../wallets/wallet/impl/firo_wallet.dart';
+import '../../../widgets/conditional_parent.dart';
+import '../../../widgets/desktop/desktop_dialog_close_button.dart';
+import '../../../widgets/detail_item.dart';
+import '../../../widgets/rounded_white_container.dart';
+
+class MasternodeInfoWidget extends StatelessWidget {
+ const MasternodeInfoWidget({super.key, required this.info});
+
+ final MasternodeInfo info;
+
+ @override
+ Widget build(BuildContext context) {
+ final map = info.pretty();
+ final keys = map.keys.toList(growable: false);
+
+ return ConditionalParent(
+ condition: Util.isDesktop,
+ builder: (child) => Column(
+ crossAxisAlignment: .stretch,
+ mainAxisSize: .min,
+ children: [
+ // not really the place for this in terms of structure but running
+ // out of time...
+ Row(
+ mainAxisAlignment: .spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "Masternode details",
+ style: STextStyles.desktopH3(context),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.only(left: 32, bottom: 32, right: 32),
+ child: RoundedWhiteContainer(
+ padding: .zero,
+
+ // using listview kind of breaks
+ borderColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.backgroundAppBar,
+ child: child,
+ ),
+ ),
+ ),
+ ],
+ ),
+ child: Column(
+ mainAxisSize: .min,
+ children: [
+ for (int i = 0; i < keys.length; i++)
+ Builder(
+ builder: (context) {
+ final title = keys[i];
+ final detail = map[title]!;
+
+ return Column(
+ mainAxisSize: .min,
+ children: [
+ if (i > 0) const DetailDivider(),
+ DetailItem(
+ title: title,
+ detail: detail,
+ horizontal: detail.length < 22,
+ ),
+ ],
+ );
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index ef93f38..ef689a4 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -67,6 +67,28 @@ class MasternodeInfo {
required this.payoutAddress,
required this.pubKeyOperator,
});
+
+ Map<String, String> pretty() {
+ return {
+ "ProTx Hash": proTxHash,
+ "IP:Port": "$serviceAddr:$servicePort",
+ "Status": revocationReason == 0 ? "Active" : "Revoked",
+ "Registered Height": registeredHeight.toString(),
+ "Last Paid Height": lastPaidHeight.toString(),
+ "Payout Address": payoutAddress,
+ "Owner Address": ownerAddress,
+ "Voting Address": votingAddress,
+ "Operator Public Key": pubKeyOperator,
+ "Operator Reward": "$operatorReward %",
+ "Collateral Hash": collateralHash,
+ "Collateral Index": collateralIndex.toString(),
+ "Collateral Address": collateralAddress,
+ "Pose Penalty": posePenalty.toString(),
+ "Pose Revived Height": poseRevivedHeight.toString(),
+ "Pose Ban Height": poseBanHeight.toString(),
+ "Revocation Reason": revocationReason.toString(),
+ };
+ }
}
final kMasterNodeValue = Decimal.fromInt(1000); // full value (not sats)
@@ -925,7 +947,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
fractionDigits: cryptoCurrency.fractionDigits,
)) {
throw Exception(
- 'Not enough funds to register a masternode. You must have at least 1000 FIRO in your public balance.',
+ 'Not enough funds to register a master You must have at least 1000 FIRO in your public balance.',
);
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.