diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index 20b3278da..f495475db 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -19,6 +19,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; import 'package:stackwallet/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; +import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -32,7 +33,8 @@ enum _WalletOptions { deleteWallet, changeRepresentative, showXpub, - lelantusCoins; + lelantusCoins, + sparkCoins; String get prettyName { switch (this) { @@ -46,6 +48,8 @@ enum _WalletOptions { return "Show xPub"; case _WalletOptions.lelantusCoins: return "Lelantus Coins"; + case _WalletOptions.sparkCoins: + return "Spark Coins"; } } } @@ -89,6 +93,9 @@ class WalletOptionsButton extends StatelessWidget { onFiroShowLelantusCoins: () async { Navigator.of(context).pop(_WalletOptions.lelantusCoins); }, + onFiroShowSparkCoins: () async { + Navigator.of(context).pop(_WalletOptions.sparkCoins); + }, walletId: walletId, ); }, @@ -191,6 +198,15 @@ class WalletOptionsButton extends StatelessWidget { ), ); break; + + case _WalletOptions.sparkCoins: + unawaited( + Navigator.of(context).pushNamed( + SparkCoinsView.routeName, + arguments: walletId, + ), + ); + break; } } }, @@ -224,6 +240,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { required this.onShowXpubPressed, required this.onChangeRepPressed, required this.onFiroShowLelantusCoins, + required this.onFiroShowSparkCoins, required this.walletId, }) : super(key: key); @@ -232,6 +249,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final VoidCallback onShowXpubPressed; final VoidCallback onChangeRepPressed; final VoidCallback onFiroShowLelantusCoins; + final VoidCallback onFiroShowSparkCoins; final String walletId; @override @@ -374,6 +392,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget { ), ), ), + if (firoDebug) + const SizedBox( + height: 8, + ), + if (firoDebug) + TransparentButton( + onPressed: onFiroShowSparkCoins, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.eye, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 14), + Expanded( + child: Text( + _WalletOptions.sparkCoins.prettyName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ], + ), + ), + ), if (xpubEnabled) const SizedBox( height: 8, diff --git a/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart new file mode 100644 index 000000000..73ef56912 --- /dev/null +++ b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart @@ -0,0 +1,236 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/wallets/isar/models/spark_coin.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class SparkCoinsView extends ConsumerStatefulWidget { + const SparkCoinsView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/sparkCoinsView"; + + final String walletId; + + @override + ConsumerState createState() => _SparkCoinsViewState(); +} + +class _SparkCoinsViewState extends ConsumerState { + List _coins = []; + + Stream>? sparkCoinsCollectionWatcher; + + void _onSparkCoinsCollectionWatcherEvent(List coins) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _coins = coins; + }); + } + }); + } + + @override + void initState() { + sparkCoinsCollectionWatcher = ref + .read(mainDBProvider) + .isar + .sparkCoins + .where() + .walletIdEqualToAnyLTagHash(widget.walletId) + .sortByHeightDesc() + .watch(fireImmediately: true); + sparkCoinsCollectionWatcher! + .listen((data) => _onSparkCoinsCollectionWatcherEvent(data)); + + super.initState(); + } + + @override + void dispose() { + sparkCoinsCollectionWatcher = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + "Spark Coins", + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], + ), + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + flex: 9, + child: Text( + "TXID", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + ), + Expanded( + flex: 3, + child: Text( + "Value (sats)", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: Text( + "Height", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: Text( + "Type", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: Text( + "Used", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.right, + ), + ), + ], + ), + ), + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: _coins.length, + separatorBuilder: (_, __) => Container( + height: 1, + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 9, + child: SelectableText( + _coins[index].txHash, + style: STextStyles.itemSubtitle12(context), + ), + ), + Expanded( + flex: 3, + child: SelectableText( + _coins[index].value.toString(), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: SelectableText( + _coins[index].height.toString(), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: SelectableText( + _coins[index].type.name, + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + Expanded( + flex: 2, + child: SelectableText( + _coins[index].isUsed.toString(), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 289a8bb37..11c8f6237 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -175,6 +175,7 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/nodes_ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -1858,6 +1859,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case SparkCoinsView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SparkCoinsView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopCoinControlView.routeName: if (args is String) { return getRoute( diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 3895a6e37..c6da88803 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -715,34 +715,29 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface { txb.setLockTime(await chainHeight); txb.setVersion(1); - // Create a mint script. - final mintScript = bscript.compile([ - 0xd1, // OP_SPARKMINT. - Uint8List(0), - ]); - - // Add inputs. - for (final utxo in txData.utxos!) { - txb.addInput( - utxo.txid, - utxo.vout, - 0xffffffff, - mintScript, - ); - } + final signingData = await fetchBuildTxData(txData.utxos!.toList()); // Create the serial context. // // "...serial_context is a byte array, which should be unique for each // transaction, and for that we serialize and put all inputs into // serial_context vector." - List serialContext = []; - for (final utxo in txData.utxos!) { - serialContext.addAll( - bscript.compile([ - utxo.txid, - utxo.vout, - ]), + final serialContext = LibSpark.serializeMintContext( + inputs: signingData + .map((e) => ( + e.utxo.txid, + e.utxo.vout, + )) + .toList(), + ); + + // Add inputs. + for (final sd in signingData) { + txb.addInput( + sd.utxo.txid, + sd.utxo.vout, + null, + sd.output, ); } @@ -756,7 +751,7 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface { )) .toList(), serialContext: Uint8List.fromList(serialContext), - // generate: true // TODO is this needed? + generate: true, ); // Add mint output(s).