diff --git a/lib/pages/coin_control/coin_control_view.dart b/lib/pages/coin_control/coin_control_view.dart new file mode 100644 index 000000000..4237e0283 --- /dev/null +++ b/lib/pages/coin_control/coin_control_view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/coin_control/utxo_card.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/toggle.dart'; + +class CoinControlView extends ConsumerStatefulWidget { + const CoinControlView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const routeName = "/coinControl"; + + final String walletId; + + @override + ConsumerState createState() => _CoinControlViewState(); +} + +class _CoinControlViewState extends ConsumerState { + bool _showAvailable = false; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final ids = MainDB.instance + .getUTXOs(widget.walletId) + .filter() + .isBlockedEqualTo(_showAvailable) + .idProperty() + .findAllSync(); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Coin control", + style: STextStyles.navBarTitle(context), + ), + titleSpacing: 0, + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Column( + children: [ + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + child: Text( + "This option allows you to control, freeze, and utilize outputs at your discretion. Tap the output circle to select.", + style: STextStyles.subtitle(context), + ), + ), + const SizedBox( + height: 10, + ), + SizedBox( + height: 48, + child: Toggle( + key: UniqueKey(), + onColor: Theme.of(context).extension()!.popupBG, + onText: "Available outputs", + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + offText: "Frozen outputs", + isOn: _showAvailable, + onValueChanged: (value) { + setState(() { + _showAvailable = value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + const SizedBox( + height: 10, + ), + Expanded( + child: ListView.separated( + itemCount: ids.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(ids[index]) + .findFirstSync()!; + + return UtxoCard( + key: Key("${utxo.walletId}_${utxo.id}"), + walletId: widget.walletId, + utxo: utxo, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/coin_control/utxo_card.dart b/lib/pages/coin_control/utxo_card.dart new file mode 100644 index 000000000..83778f883 --- /dev/null +++ b/lib/pages/coin_control/utxo_card.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class UtxoCard extends ConsumerStatefulWidget { + const UtxoCard({ + Key? key, + required this.utxo, + required this.walletId, + this.selectable = false, + }) : super(key: key); + + final String walletId; + final UTXO utxo; + final bool selectable; + + @override + ConsumerState createState() => _UtxoCardState(); +} + +class _UtxoCardState extends ConsumerState { + late final UTXO utxo; + + bool _selected = false; + + @override + void initState() { + utxo = widget.utxo; + super.initState(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + + final addr = MainDB.instance.isar.transactions + .where() + .txidWalletIdEqualTo( + utxo.txid, + widget.walletId, + ) + .findFirstSync() + ?.address + .value + ?.value; + + String? label; + if (addr != null) { + label = MainDB.instance.isar.addressLabels + .where() + .addressStringWalletIdEqualTo(addr, widget.walletId) + .findFirstSync() + ?.value; + } + + return RoundedWhiteContainer( + child: Row( + children: [ + SvgPicture.asset( + _selected + ? Assets.svg.coinControl.selected + : utxo.isBlocked + ? Assets.svg.coinControl.blocked + : Assets.svg.coinControl.unBlocked, + width: 32, + height: 32, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${Format.satoshisToAmount( + utxo.value, + coin: coin, + ).toStringAsFixed(coin.decimals)} ${coin.ticker}", + style: STextStyles.w600_14(context), + ), + const SizedBox( + height: 2, + ), + Row( + children: [ + Flexible( + child: Text( + label ?? addr ?? utxo.txid, + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart index 7f1c9c757..0bbee3163 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/providers/global/paynym_api_provider.dart'; @@ -112,10 +113,10 @@ class _WalletNavigationBarState extends ConsumerState { if (mounted) { Navigator.of(context).pop(); - // Navigator.of(context).pushNamed( - // PaynymHomeView.routeName, - // arguments: widget.walletId, - // ); + Navigator.of(context).pushNamed( + CoinControlView.routeName, + arguments: widget.walletId, + ); } }, child: Container( diff --git a/lib/route_generator.dart b/lib/route_generator.dart index a9ce742ec..6b14b97a9 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_ import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart'; import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart'; import 'package:stackwallet/pages/buy_view/buy_view.dart'; +import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; @@ -130,8 +131,6 @@ import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:tuple/tuple.dart'; -import 'models/isar/models/blockchain_data/transaction.dart'; - class RouteGenerator { static const bool useMaterialPageRoute = true; @@ -214,6 +213,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case CoinControlView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CoinControlView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case PaynymHomeView.routeName: if (args is String) { return getRoute( diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 0484fe0e8..b8b1fed13 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -14,6 +14,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' import 'package:stackwallet/services/coins/particl/particl_wallet.dart' as particl; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; +import 'package:stackwallet/utilities/constants.dart'; enum Coin { bitcoin, @@ -232,6 +233,8 @@ extension CoinExt on Coin { return nmc.MINIMUM_CONFIRMATIONS; } } + + int get decimals => Constants.decimalPlacesForCoin(this); } Coin coinFromPrettyName(String name) {