From af47c67231eeb6cb1d04f2740a50847af55f9610 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 12:58:38 -0600 Subject: [PATCH 001/225] BranchedParent class --- lib/widgets/conditional_parent.dart | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/widgets/conditional_parent.dart b/lib/widgets/conditional_parent.dart index 757c8f992..e8c60884a 100644 --- a/lib/widgets/conditional_parent.dart +++ b/lib/widgets/conditional_parent.dart @@ -21,3 +21,27 @@ class ConditionalParent extends StatelessWidget { } } } + +class BranchedParent extends StatelessWidget { + const BranchedParent({ + Key? key, + required this.condition, + required this.conditionBranchBuilder, + required this.otherBranchBuilder, + required this.children, + }) : super(key: key); + + final bool condition; + final Widget Function(List) conditionBranchBuilder; + final Widget Function(List) otherBranchBuilder; + final List children; + + @override + Widget build(BuildContext context) { + if (condition) { + return conditionBranchBuilder(children); + } else { + return otherBranchBuilder(children); + } + } +} From 2aa8dd2becce6c7dd39845273f6fc008db4f5ead Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 13:12:41 -0600 Subject: [PATCH 002/225] WIP: trade details for desktop --- .../exchange_view/trade_details_view.dart | 1315 ++++++++++------- .../sub_widgets/transactions_list.dart | 87 +- 2 files changed, 872 insertions(+), 530 deletions(-) diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 76c845027..602d588da 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -15,17 +15,23 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.d import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -157,34 +163,113 @@ class _TradeDetailsViewState extends ConsumerState { final sendAmount = Decimal.tryParse(trade.payInAmount) ?? Decimal.parse("-1"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Trade details", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Trade details", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), ), ), - body: Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( + child: Padding( + padding: isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(0), + child: BranchedParent( + condition: isDesktop, + conditionBranchBuilder: (children) => Padding( + padding: const EdgeInsets.only( + right: 20, + ), + child: Padding( + padding: const EdgeInsets.only( + right: 12, + ), + child: RoundedWhiteContainer( + borderColor: isDesktop + ? Theme.of(context).extension()!.background + : null, + padding: const EdgeInsets.all(0), + child: ListView( + primary: false, + shrinkWrap: true, + children: children, + ), + ), + ), + ), + otherBranchBuilder: (children) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: children, + ), + children: [ + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), + child: Container( + decoration: isDesktop + ? BoxDecoration( + color: Theme.of(context) + .extension()! + .background, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), + ), + ) + : null, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + if (isDesktop) + Row( + children: [ + SvgPicture.asset( + _fetchIconAssetForStatus(trade.status), + width: 32, + height: 32, + ), + const SizedBox( + width: 16, + ), + SelectableText( + "Exchange", + style: STextStyles.desktopTextMedium(context), + ), + ], + ), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", @@ -194,7 +279,7 @@ class _TradeDetailsViewState extends ConsumerState { height: 4, ), SelectableText( - "${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch( + "-${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), decimalPlaces: trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}", @@ -202,136 +287,178 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(32), - ), - child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus(trade.status), - width: 32, - height: 32, + if (!isDesktop) + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + ), + child: Center( + child: SvgPicture.asset( + _fetchIconAssetForStatus(trade.status), + width: 32, + height: 32, + ), ), ), - ), ], ), ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Status", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - trade.status, - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .colorForStatus(trade.status), - ), - ), - // ), - // ), - ], - ), - ), - if (!sentFromStack && !hasTx) - const SizedBox( + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( height: 12, ), - if (!sentFromStack && !hasTx) - RoundedContainer( - color: Theme.of(context) - .extension()! - .warningBackground, - child: RichText( - text: TextSpan( + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + trade.status, + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context) + .extension()! + .colorForStatus(trade.status), + ), + ), + // ), + // ), + ], + ), + ), + if (!sentFromStack && !hasTx) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (!sentFromStack && !hasTx) + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Theme.of(context) + .extension()! + .warningBackground, + child: RichText( + text: TextSpan( + text: + "You must send at least ${sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, + )} ${trade.payInCurrency.toUpperCase()}. ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + children: [ + TextSpan( text: - "You must send at least ${sendAmount.toStringAsFixed( + "If you send less than ${sendAmount.toStringAsFixed( trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}. ", - style: STextStyles.label700(context).copyWith( + )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", + style: STextStyles.label(context).copyWith( color: Theme.of(context) .extension()! .warningForeground, ), - children: [ - TextSpan( - text: - "If you send less than ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" - ? 12 - : 8, - )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", - style: STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + ), + ]), + ), + ), + if (sentFromStack) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (sentFromStack) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Sent from", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + widget.walletName!, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 10, + ), + BlueTextButton( + text: "View transaction", + onTap: () { + final Coin coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + if (isDesktop) { + Navigator.of(context).push( + FadePageRoute( + DesktopDialog( + maxHeight: + MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TransactionDetailsView( + coin: coin, + transaction: transactionIfSentFromStack!, + walletId: walletId!, + ), + ), + const RouteSettings( + name: TransactionDetailsView.routeName, ), ), - ]), + ); + } else { + Navigator.of(context).pushNamed( + TransactionDetailsView.routeName, + arguments: Tuple3( + transactionIfSentFromStack!, coin, walletId!), + ); + } + }, ), - ), - if (sentFromStack) - const SizedBox( - height: 12, - ), - if (sentFromStack) - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Sent from", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - widget.walletName!, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 10, - ), - GestureDetector( - onTap: () { - final Coin coin = coinFromTickerCaseInsensitive( - trade.payInCurrency); - - Navigator.of(context).pushNamed( - TransactionDetailsView.routeName, - arguments: Tuple3( - transactionIfSentFromStack!, coin, walletId!), - ); - }, - child: Text( - "View transaction", - style: STextStyles.link2(context), - ), - ), - ], + ], + ), + ), + if (sentFromStack) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, ), - ), - if (sentFromStack) - const SizedBox( - height: 12, - ), - if (sentFromStack) - RoundedWhiteContainer( - child: Column( + if (sentFromStack) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( @@ -347,252 +474,224 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - ), - if (!sentFromStack && !hasTx) - const SizedBox( - height: 12, - ), - if (!sentFromStack && !hasTx) - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + if (isDesktop) + IconCopyButton( + data: trade.payInAddress, + ), + ], + ), + ), + if (!sentFromStack && !hasTx) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (!sentFromStack && !hasTx) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Send ${trade.payInCurrency.toUpperCase()} to this address", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( - onTap: () async { - final address = trade.payInAddress; - await Clipboard.setData( - ClipboardData( - text: address, - ), - ); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: Row( + Text( + "Send ${trade.payInCurrency.toUpperCase()} to this address", + style: STextStyles.itemSubtitle(context), + ), + isDesktop + ? IconCopyButton( + data: trade.payInAddress, + ) + : GestureDetector( + onTap: () async { + final address = trade.payInAddress; + await Clipboard.setData( + ClipboardData( + text: address, + ), + ); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + )); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + trade.payInAddress, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 10, + ), + GestureDetector( + onTap: () { + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (_) { + final width = MediaQuery.of(context).size.width / 2; + return StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .infoItemIcons, + Center( + child: Text( + "Send ${trade.payInCurrency.toUpperCase()} to this address", + style: STextStyles.pageTitleH2(context), + ), ), const SizedBox( - width: 4, + height: 12, ), - Text( - "Copy", - style: STextStyles.link2(context), + Center( + child: RepaintBoundary( + // key: _qrKey, + child: SizedBox( + width: width + 20, + height: width + 20, + child: QrImage( + data: trade.payInAddress, + size: width, + backgroundColor: Theme.of(context) + .extension()! + .popupBG, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + const SizedBox( + height: 12, + ), + Center( + child: SizedBox( + width: width, + child: TextButton( + onPressed: () async { + // await _capturePng(true); + Navigator.of(context).pop(); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.button(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), ), ], ), - ), - ], - ), - const SizedBox( - height: 4, - ), - SelectableText( - trade.payInAddress, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 10, - ), - GestureDetector( - onTap: () { - showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (_) { - final width = - MediaQuery.of(context).size.width / 2; - return StackDialogBase( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Center( - child: Text( - "Send ${trade.payInCurrency.toUpperCase()} to this address", - style: - STextStyles.pageTitleH2(context), - ), - ), - const SizedBox( - height: 12, - ), - Center( - child: RepaintBoundary( - // key: _qrKey, - child: SizedBox( - width: width + 20, - height: width + 20, - child: QrImage( - data: trade.payInAddress, - size: width, - backgroundColor: Theme.of( - context) - .extension()! - .popupBG, - foregroundColor: Theme.of( - context) - .extension()! - .accentColorDark), - ), - ), - ), - const SizedBox( - height: 12, - ), - Center( - child: SizedBox( - width: width, - child: TextButton( - onPressed: () async { - // await _capturePng(true); - Navigator.of(context).pop(); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorDark), - ), - ), - ), - ), - ], - ), - ); - }, ); }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.qrcode, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Show QR code", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ); + }, + child: Row( children: [ - Text( - "Trade note", - style: STextStyles.itemSubtitle(context), + SvgPicture.asset( + Assets.svg.qrcode, + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .infoItemIcons, ), - GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditTradeNoteView.routeName, - arguments: Tuple2( - tradeId, - ref - .read(tradeNoteServiceProvider) - .getNote(tradeId: tradeId), - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Edit", - style: STextStyles.link2(context), - ), - ], - ), + const SizedBox( + width: 4, + ), + Text( + "Show QR code", + style: STextStyles.link2(context), ), ], ), - const SizedBox( - height: 4, - ), - SelectableText( - ref.watch(tradeNoteServiceProvider.select( - (value) => value.getNote(tradeId: tradeId))), - style: STextStyles.itemSubtitle12(context), - ), - ], - ), + ), + ], ), - if (sentFromStack) - const SizedBox( + ), + isDesktop + ? const _Divider() + : const SizedBox( height: 12, ), - if (sentFromStack) - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transaction note", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade note", + style: STextStyles.itemSubtitle(context), + ), + isDesktop + ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditTradeNoteView( + tradeId: tradeId, + note: _note, + ), + ); + }, + ); + }, + ) + : GestureDetector( onTap: () { Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple3( - transactionIfSentFromStack!.txid, - walletId!, - _note, + EditTradeNoteView.routeName, + arguments: Tuple2( + tradeId, + ref + .read(tradeNoteServiceProvider) + .getNote(tradeId: tradeId), ), ); }, @@ -616,193 +715,371 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - ], - ), - const SizedBox( - height: 4, - ), - FutureBuilder( - future: ref.watch( - notesServiceChangeNotifierProvider(walletId!) - .select((value) => value.getNoteFor( - txid: transactionIfSentFromStack!.txid))), - builder: - (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - _note = snapshot.data ?? ""; - } - return SelectableText( - _note, - style: STextStyles.itemSubtitle12(context), - ); - }, + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + ref.watch(tradeNoteServiceProvider + .select((value) => value.getNote(tradeId: tradeId))), + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + if (sentFromStack) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (sentFromStack) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction note", + style: STextStyles.itemSubtitle(context), ), + isDesktop + ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: + transactionIfSentFromStack!.txid, + walletId: walletId!, + note: _note, + ), + ); + }, + ); + }, + ) + : GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple3( + transactionIfSentFromStack!.txid, + walletId!, + _note, + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Edit", + style: STextStyles.link2(context), + ), + ], + ), + ), ], ), - ), - const SizedBox( - height: 12, + const SizedBox( + height: 4, + ), + FutureBuilder( + future: ref.watch( + notesServiceChangeNotifierProvider(walletId!).select( + (value) => value.getNoteFor( + txid: transactionIfSentFromStack!.txid))), + builder: + (builderContext, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _note = snapshot.data ?? ""; + } + return SelectableText( + _note, + style: STextStyles.itemSubtitle12(context), + ); + }, + ), + ], ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Date", style: STextStyles.itemSubtitle(context), ), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - Format.extractDateFrom( - trade.timestamp.millisecondsSinceEpoch ~/ 1000), - style: STextStyles.itemSubtitle12(context), - ), - // ), - // ), + if (isDesktop) + const SizedBox( + height: 2, + ), + if (isDesktop) + SelectableText( + Format.extractDateFrom( + trade.timestamp.millisecondsSinceEpoch ~/ 1000), + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), ], ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + if (!isDesktop) + SelectableText( + Format.extractDateFrom( + trade.timestamp.millisecondsSinceEpoch ~/ 1000), + style: STextStyles.itemSubtitle12(context), + ), + if (isDesktop) + IconCopyButton( + data: Format.extractDateFrom( + trade.timestamp.millisecondsSinceEpoch ~/ 1000), + ), + ], + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Exchange", style: STextStyles.itemSubtitle(context), ), - SelectableText( - trade.exchangeName, - style: STextStyles.itemSubtitle12(context), - ), + if (isDesktop) + const SizedBox( + height: 2, + ), + if (isDesktop) + SelectableText( + trade.exchangeName, + style: STextStyles.itemSubtitle12(context), + ), ], ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( + if (isDesktop) + IconCopyButton( + data: trade.exchangeName, + ), + if (!isDesktop) + SelectableText( + trade.exchangeName, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trade ID", style: STextStyles.itemSubtitle(context), ), - const Spacer(), - Row( - children: [ - Text( - trade.tradeId, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - width: 10, - ), - GestureDetector( - onTap: () async { - final data = ClipboardData(text: trade.tradeId); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 12, - ), - ) - ], - ) - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Tracking", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - Builder(builder: (context) { - late final String url; - switch (trade.exchangeName) { - case ChangeNowExchange.exchangeName: - url = - "https://changenow.io/exchange/txs/${trade.tradeId}"; - break; - case SimpleSwapExchange.exchangeName: - url = - "https://simpleswap.io/exchange?id=${trade.tradeId}"; - break; - } - return GestureDetector( - onTap: () { - launchUrl( - Uri.parse(url), - mode: LaunchMode.externalApplication, - ); - }, - child: Text( - url, - style: STextStyles.link2(context), - ), - ); - }), - ], - ), - ), - const SizedBox( - height: 12, - ), - if (isStackCoin(trade.payInCurrency) && - (trade.status == "New" || - trade.status == "new" || - trade.status == "waiting" || - trade.status == "Waiting")) - SecondaryButton( - label: "Send from Stack", - onPressed: () { - final amount = sendAmount; - final address = trade.payInAddress; - - final coin = - coinFromTickerCaseInsensitive(trade.payInCurrency); - - Navigator.of(context).pushNamed( - SendFromView.routeName, - arguments: Tuple4( - coin, - amount, - address, - trade, + if (isDesktop) + const SizedBox( + height: 2, ), - ); - }, + if (isDesktop) + Text( + trade.tradeId, + style: STextStyles.itemSubtitle12(context), + ), + ], ), - ], + if (isDesktop) + IconCopyButton( + data: trade.tradeId, + ), + if (!isDesktop) + Row( + children: [ + Text( + trade.tradeId, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + GestureDetector( + onTap: () async { + final data = ClipboardData(text: trade.tradeId); + await clipboard.setData(data); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + )); + }, + child: SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 12, + ), + ) + ], + ), + ], + ), ), - ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Tracking", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + Builder(builder: (context) { + late final String url; + switch (trade.exchangeName) { + case ChangeNowExchange.exchangeName: + url = + "https://changenow.io/exchange/txs/${trade.tradeId}"; + break; + case SimpleSwapExchange.exchangeName: + url = + "https://simpleswap.io/exchange?id=${trade.tradeId}"; + break; + } + return GestureDetector( + onTap: () { + launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + }, + child: Text( + url, + style: STextStyles.link2(context), + ), + ); + }), + ], + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) + SecondaryButton( + label: "Send from Stack", + onPressed: () { + final amount = sendAmount; + final address = trade.payInAddress; + + final coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + Navigator.of(context).pushNamed( + SendFromView.routeName, + arguments: Tuple4( + coin, + amount, + address, + trade, + ), + ); + }, + ), + ], ), ), ); } } + +class _Divider extends StatelessWidget { + const _Divider({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 1, + color: Theme.of(context).extension()!.background, + ); + } +} diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart index d23d3082f..11353c7c6 100644 --- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart +++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart @@ -7,10 +7,14 @@ import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/manager.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/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/trade_card.dart'; import 'package:stackwallet/widgets/transaction_card.dart'; @@ -94,18 +98,79 @@ class _TransactionsListState extends ConsumerState { // this may mess with combined firo transactions key: Key(tx.toString() + trade.uuid), // trade: trade, - onTap: () { - unawaited( - Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4( - trade.tradeId, - tx, - widget.walletId, - ref.read(managerProvider).walletName, + onTap: () async { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3(context), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: trade.tradeId, + transactionIfSentFromStack: tx, + walletName: + ref.read(managerProvider).walletName, + walletId: widget.walletId, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, ), - ), - ); + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4( + trade.tradeId, + tx, + widget.walletId, + ref.read(managerProvider).walletName, + ), + ), + ); + } }, ) ], From 2bdf5f152c606cd83311ef5e5b899e3a9b8cb0d7 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 16:43:26 -0600 Subject: [PATCH 003/225] dynamic secure storage provider --- lib/hive/db.dart | 2 + lib/main.dart | 29 +- ...w_wallet_recovery_phrase_warning_view.dart | 2 + .../restore_wallet_view.dart | 2 + lib/pages/pinpad_views/create_pin_view.dart | 8 +- lib/pages/pinpad_views/lock_screen_view.dart | 8 +- .../add_edit_node_view.dart | 8 +- .../manage_nodes_views/node_details_view.dart | 8 +- .../change_pin_view/change_pin_view.dart | 16 +- .../create_auto_backup_view.dart | 16 +- .../create_backup_view.dart | 424 +++++++++--------- .../edit_auto_backup_view.dart | 16 +- .../helpers/restore_create_backup.dart | 112 +++-- .../stack_restore_progress_view.dart | 2 + .../create_password/create_password_view.dart | 7 - .../global/auto_swb_service_provider.dart | 8 +- .../global/node_service_provider.dart | 5 +- .../global/secure_store_provider.dart | 18 + .../global/wallets_service_provider.dart | 7 +- lib/services/auto_swb_service.dart | 5 +- .../coins/bitcoin/bitcoin_wallet.dart | 11 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 11 +- lib/services/coins/coin_service.dart | 16 + .../coins/dogecoin/dogecoin_wallet.dart | 11 +- .../coins/epiccash/epiccash_wallet.dart | 9 +- lib/services/coins/firo/firo_wallet.dart | 11 +- .../coins/litecoin/litecoin_wallet.dart | 11 +- lib/services/coins/monero/monero_wallet.dart | 8 +- .../coins/namecoin/namecoin_wallet.dart | 11 +- .../coins/wownero/wownero_wallet.dart | 8 +- lib/services/node_service.dart | 5 +- lib/services/wallets.dart | 3 +- lib/services/wallets_service.dart | 6 +- lib/utilities/db_version_migration.dart | 16 +- lib/utilities/desktop_password_service.dart | 33 +- .../flutter_secure_storage_interface.dart | 119 +++-- ...flutter_secure_storage_interface_test.dart | 6 +- .../coins/bitcoin/bitcoin_wallet_test.dart | 10 +- .../bitcoincash/bitcoincash_wallet_test.dart | 10 +- .../coins/dogecoin/dogecoin_wallet_test.dart | 8 +- .../coins/namecoin/namecoin_wallet_test.dart | 210 ++++----- test/services/node_service_test.dart | 3 +- test/services/wallets_service_test.dart | 24 +- test/services/wallets_service_test.mocks.dart | 2 +- 44 files changed, 701 insertions(+), 564 deletions(-) create mode 100644 lib/providers/global/secure_store_provider.dart diff --git a/lib/hive/db.dart b/lib/hive/db.dart index e1232696b..d5402752e 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -33,6 +33,7 @@ class DB { static const String boxNamePriceCache = "priceAPIPrice24hCache"; static const String boxNameDBInfo = "dbInfo"; static const String boxNameTheme = "theme"; + static const String boxNameDesktopData = "desktopData"; String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache"; String boxNameSetCache({required Coin coin}) => @@ -58,6 +59,7 @@ class DB { late final Box _boxPrefs; late final Box _boxTradeLookup; late final Box _boxDBInfo; + late final Box _boxDesktopData; final Map> _walletBoxes = {}; diff --git a/lib/main.dart b/lib/main.dart index 77a8b1441..7f7c4a44d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:isar/isar.dart'; @@ -51,6 +52,7 @@ import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; @@ -143,15 +145,24 @@ void main() async { await Hive.initFlutter(appDirectory.path); await Hive.openBox(DB.boxNameDBInfo); - int dbVersion = DB.instance.get( - boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? - 0; - if (dbVersion < Constants.currentHiveDbVersion) { - try { - await DbVersionMigrator().migrate(dbVersion); - } catch (e, s) { - Logging.instance.log("Cannot migrate database\n$e $s", - level: LogLevel.Error, printFullLength: true); + + if (!Util.isDesktop) { + int dbVersion = DB.instance.get( + boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? + 0; + if (dbVersion < Constants.currentHiveDbVersion) { + try { + await DbVersionMigrator().migrate( + dbVersion, + secureStore: const SecureStorageWrapper( + store: FlutterSecureStorage(), + isDesktop: false, + ), + ); + } catch (e, s) { + Logging.instance.log("Cannot migrate database\n$e $s", + level: LogLevel.Error, printFullLength: true); + } } } diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index 83dc43933..24a2e1a44 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my_stack_button.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -241,6 +242,7 @@ class _NewWalletRecoveryPhraseWarningViewState coin, walletId, walletName, + ref.read(secureStoreProvider), node, txTracker, ref.read(prefsChangeNotifierProvider), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index a6b7e7e77..2f325f536 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widge import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my_stack_button.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -265,6 +266,7 @@ class _RestoreWalletViewState extends ConsumerState { widget.coin, walletId, widget.walletName, + ref.read(secureStoreProvider), node, txTracker, ref.read(prefsChangeNotifierProvider), diff --git a/lib/pages/pinpad_views/create_pin_view.dart b/lib/pages/pinpad_views/create_pin_view.dart index f8b84cfb4..5ea8bc363 100644 --- a/lib/pages/pinpad_views/create_pin_view.dart +++ b/lib/pages/pinpad_views/create_pin_view.dart @@ -2,10 +2,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -20,15 +20,11 @@ class CreatePinView extends ConsumerStatefulWidget { const CreatePinView({ Key? key, this.popOnSuccess = false, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), this.biometrics = const Biometrics(), }) : super(key: key); static const String routeName = "/createPin"; - final FlutterSecureStorageInterface secureStore; final Biometrics biometrics; final bool popOnSuccess; @@ -63,7 +59,7 @@ class _CreatePinViewState extends ConsumerState { @override initState() { - _secureStore = widget.secureStore; + _secureStore = ref.read(secureStoreProvider); biometrics = widget.biometrics; super.initState(); } diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 00d8b1914..137f3d55d 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; // import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -33,9 +33,6 @@ class LockscreenView extends ConsumerStatefulWidget { this.popOnSuccess = false, this.isInitialAppLogin = false, this.routeOnSuccessArguments, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), this.biometrics = const Biometrics(), this.onSuccess, }) : super(key: key); @@ -50,7 +47,6 @@ class LockscreenView extends ConsumerStatefulWidget { final String biometricsAuthenticationTitle; final String biometricsLocalizedReason; final String biometricsCancelButtonString; - final FlutterSecureStorageInterface secureStore; final Biometrics biometrics; final VoidCallback? onSuccess; @@ -134,7 +130,7 @@ class _LockscreenViewState extends ConsumerState { void initState() { _shakeController = ShakeController(); - _secureStore = widget.secureStore; + _secureStore = ref.read(secureStoreProvider); biometrics = widget.biometrics; _attempts = 0; _timeout = Duration.zero; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 382e3f09e..39ab493f0 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -3,11 +3,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -40,9 +40,6 @@ class AddEditNodeView extends ConsumerStatefulWidget { required this.coin, required this.nodeId, required this.routeOnSuccessOrDelete, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/addEditNode"; @@ -51,7 +48,6 @@ class AddEditNodeView extends ConsumerStatefulWidget { final Coin coin; final String routeOnSuccessOrDelete; final String? nodeId; - final FlutterSecureStorageInterface secureStore; @override ConsumerState createState() => _AddEditNodeViewState(); @@ -533,7 +529,7 @@ class _AddEditNodeViewState extends ConsumerState { children: [ NodeForm( node: node, - secureStore: widget.secureStore, + secureStore: ref.read(secureStoreProvider), readOnly: false, coin: widget.coin, onChanged: (canSave, canTest) { diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index f9b64c460..6d9641b7d 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -32,14 +32,10 @@ class NodeDetailsView extends ConsumerStatefulWidget { required this.coin, required this.nodeId, required this.popRouteName, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/nodeDetails"; - final FlutterSecureStorageInterface secureStore; final Coin coin; final String nodeId; final String popRouteName; @@ -58,7 +54,7 @@ class _NodeDetailsViewState extends ConsumerState { @override initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); coin = widget.coin; nodeId = widget.nodeId; popRouteName = widget.popRouteName; diff --git a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart index 39c95cad7..46c2fd9cf 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -11,23 +12,18 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; -class ChangePinView extends StatefulWidget { +class ChangePinView extends ConsumerStatefulWidget { const ChangePinView({ Key? key, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/changePin"; - final FlutterSecureStorageInterface secureStore; - @override - State createState() => _ChangePinViewState(); + ConsumerState createState() => _ChangePinViewState(); } -class _ChangePinViewState extends State { +class _ChangePinViewState extends ConsumerState { BoxDecoration get _pinPutDecoration { return BoxDecoration( color: Theme.of(context).extension()!.textSubtitle2, @@ -53,7 +49,7 @@ class _ChangePinViewState extends State { @override void initState() { - _secureStore = widget.secureStore; + _secureStore = ref.read(secureStoreProvider); super.initState(); } diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index 3b5dbd0b0..1082acc99 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; @@ -13,6 +12,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -21,26 +21,20 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; -import 'package:stackwallet/utilities/util.dart'; - class CreateAutoBackupView extends ConsumerStatefulWidget { const CreateAutoBackupView({ Key? key, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/createAutoBackup"; - final FlutterSecureStorageInterface secureStore; - @override ConsumerState createState() => _EnableAutoBackupViewState(); @@ -75,7 +69,7 @@ class _EnableAutoBackupViewState extends ConsumerState { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); @@ -585,7 +579,9 @@ class _EnableAutoBackupViewState extends ConsumerState { final String fileToSave = createAutoBackupFilename(pathToSave, now); - final backup = await SWB.createStackWalletJSON(); + final backup = await SWB.createStackWalletJSON( + secureStorage: secureStore, + ); bool result = await SWB.encryptStackWalletWithADK( fileToSave, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index eacdda66a..fc4719fe1 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -443,222 +444,229 @@ class _RestoreFromFileViewState extends State { ), if (!isDesktop) const Spacer(), !isDesktop - ? TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = - fileLocationController.text; - final String passphrase = passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; + ? Consumer(builder: (context, ref, __) { + return TextButton( + style: shouldEnableCreate + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; - if (pathToSave.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - )); - return; - } - if (!(await Directory(pathToSave).exists())) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - )); - return; - } - if (passphrase.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - )); - return; - } - if (passphrase != repeatPassphrase) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - )); - return; - } - - unawaited(showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting backup", - message: "This shouldn't take long", - ), - )); - // make sure the dialog is able to be displayed for at least 1 second - await Future.delayed( - const Duration(seconds: 1)); - - final DateTime now = DateTime.now(); - final String fileToSave = - "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; - - final backup = await SWB.createStackWalletJSON(); - - bool result = - await SWB.encryptStackWalletWithPassphrase( - fileToSave, - passphrase, - jsonEncode(backup), - ); - - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); - - if (result) { - await showDialog( + if (pathToSave.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: "Backup saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: "Backup creation succeeded"), - ); - passwordController.text = ""; - passwordRepeatController.text = ""; - setState(() {}); - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Backup creation failed"), - ); + )); + return; } - } - }, - child: Text( - "Create backup", - style: STextStyles.button(context), - ), - ) + if (!(await Directory(pathToSave).exists())) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + )); + return; + } + if (passphrase.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + )); + return; + } + if (passphrase != repeatPassphrase) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + )); + return; + } + + unawaited(showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting backup", + message: "This shouldn't take long", + ), + )); + // make sure the dialog is able to be displayed for at least 1 second + await Future.delayed( + const Duration(seconds: 1)); + + final DateTime now = DateTime.now(); + final String fileToSave = + "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; + + final backup = await SWB.createStackWalletJSON( + secureStorage: ref.read(secureStoreProvider)); + + bool result = + await SWB.encryptStackWalletWithPassphrase( + fileToSave, + passphrase, + jsonEncode(backup), + ); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: "Backup creation succeeded"), + ); + passwordController.text = ""; + passwordRepeatController.text = ""; + setState(() {}); + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Backup creation failed"), + ); + } + } + }, + child: Text( + "Create backup", + style: STextStyles.button(context), + ), + ); + }) : Row( children: [ - PrimaryButton( - width: 183, - desktopMed: true, - label: "Create backup", - enabled: shouldEnableCreate, - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = - fileLocationController.text; - final String passphrase = - passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; + Consumer(builder: (context, ref, __) { + return PrimaryButton( + width: 183, + desktopMed: true, + label: "Create backup", + enabled: shouldEnableCreate, + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = + passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; - if (pathToSave.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - )); - return; - } - if (!(await Directory(pathToSave).exists())) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - )); - return; - } - if (passphrase.isEmpty) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - )); - return; - } - if (passphrase != repeatPassphrase) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - )); - return; - } - - unawaited(showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting backup", - message: "This shouldn't take long", - ), - )); - // make sure the dialog is able to be displayed for at least 1 second - await Future.delayed( - const Duration(seconds: 1)); - - final DateTime now = DateTime.now(); - final String fileToSave = - "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; - - final backup = - await SWB.createStackWalletJSON(); - - bool result = - await SWB.encryptStackWalletWithPassphrase( - fileToSave, - passphrase, - jsonEncode(backup), - ); - - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); - - if (result) { - await showDialog( + if (pathToSave.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: "Backup saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: - "Backup creation succeeded"), - ); - passwordController.text = ""; - passwordRepeatController.text = ""; - setState(() {}); - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Backup creation failed"), - ); + )); + return; } - } - }, - ), + if (!(await Directory(pathToSave).exists())) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + )); + return; + } + if (passphrase.isEmpty) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + )); + return; + } + if (passphrase != repeatPassphrase) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + )); + return; + } + + unawaited(showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting backup", + message: "This shouldn't take long", + ), + )); + // make sure the dialog is able to be displayed for at least 1 second + await Future.delayed( + const Duration(seconds: 1)); + + final DateTime now = DateTime.now(); + final String fileToSave = + "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb"; + + final backup = + await SWB.createStackWalletJSON( + secureStorage: + ref.read(secureStoreProvider)); + + bool result = await SWB + .encryptStackWalletWithPassphrase( + fileToSave, + passphrase, + jsonEncode(backup), + ); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: + "Backup creation succeeded"), + ); + passwordController.text = ""; + passwordRepeatController.text = ""; + setState(() {}); + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: "Backup creation failed"), + ); + } + } + }, + ); + }), const SizedBox( width: 16, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 105146aa0..d5ba21634 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; @@ -13,6 +12,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -21,26 +21,20 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; -import '../../../../utilities/util.dart'; - class EditAutoBackupView extends ConsumerStatefulWidget { const EditAutoBackupView({ Key? key, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/editAutoBackup"; - final FlutterSecureStorageInterface secureStore; - @override ConsumerState createState() => _EditAutoBackupViewState(); } @@ -74,7 +68,7 @@ class _EditAutoBackupViewState extends ConsumerState { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); @@ -586,7 +580,9 @@ class _EditAutoBackupViewState extends ConsumerState { final String fileToSave = createAutoBackupFilename(pathToSave, now); - final backup = await SWB.createStackWalletJSON(); + final backup = await SWB.createStackWalletJSON( + secureStorage: ref.read(secureStoreProvider), + ); bool result = await SWB.encryptStackWalletWithADK( fileToSave, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 6d803eb6d..9b755c95a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -3,10 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; -import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; @@ -91,10 +88,13 @@ abstract class SWB { static bool _shouldCancelRestore = false; - static bool _checkShouldCancel(PreRestoreState? revertToState) { + static bool _checkShouldCancel( + PreRestoreState? revertToState, + FlutterSecureStorageInterface secureStorageInterface, + ) { if (_shouldCancelRestore) { if (revertToState != null) { - _revert(revertToState); + _revert(revertToState, secureStorageInterface); } else { _cancelCompleter!.complete(); _shouldCancelRestore = false; @@ -193,15 +193,15 @@ abstract class SWB { /// [secureStorage] parameter exposed for testing purposes static Future> createStackWalletJSON({ - FlutterSecureStorageInterface? secureStorage, + required FlutterSecureStorageInterface secureStorage, }) async { Logging.instance .log("Starting createStackWalletJSON...", level: LogLevel.Info); final _wallets = Wallets.sharedInstance; Map backupJson = {}; - NodeService nodeService = NodeService(); - final _secureStore = - secureStorage ?? const SecureStorageWrapper(FlutterSecureStorage()); + NodeService nodeService = + NodeService(secureStorageInterface: secureStorage); + final _secureStore = secureStorage; Logging.instance.log("createStackWalletJSON awaiting DB.instance.mutex...", level: LogLevel.Info); @@ -448,6 +448,7 @@ abstract class SWB { Map validJSON, StackRestoringUIState? uiState, Map oldToNewWalletIdMap, + FlutterSecureStorageInterface secureStorageInterface, ) async { Map prefs = validJSON["prefs"] as Map; List? addressBookEntries = @@ -486,7 +487,11 @@ abstract class SWB { "SWB restoring nodes", level: LogLevel.Warning, ); - await _restoreNodes(nodes, primaryNodes); + await _restoreNodes( + nodes, + primaryNodes, + secureStorageInterface, + ); uiState?.nodes = StackRestoringStatus.success; uiState?.trades = StackRestoringStatus.restoring; @@ -543,6 +548,7 @@ abstract class SWB { static Future restoreStackWalletJSON( String jsonBackup, StackRestoringUIState? uiState, + FlutterSecureStorageInterface secureStorageInterface, ) async { if (!Platform.isLinux) await Wakelock.enable(); @@ -550,7 +556,8 @@ abstract class SWB { "SWB creating temp backup", level: LogLevel.Warning, ); - final preRestoreJSON = await createStackWalletJSON(); + final preRestoreJSON = + await createStackWalletJSON(secureStorage: secureStorageInterface); Logging.instance.log( "SWB temp backup created", level: LogLevel.Warning, @@ -587,19 +594,34 @@ abstract class SWB { // basic cancel check here // no reverting required yet as nothing has been written to store - if (_checkShouldCancel(null)) { + if (_checkShouldCancel( + null, + secureStorageInterface, + )) { return false; } - await _restoreEverythingButWallets(validJSON, uiState, oldToNewWalletIdMap); + await _restoreEverythingButWallets( + validJSON, + uiState, + oldToNewWalletIdMap, + secureStorageInterface, + ); // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } - final nodeService = NodeService(); - final walletsService = WalletsService(); + final nodeService = NodeService( + secureStorageInterface: secureStorageInterface, + ); + final walletsService = WalletsService( + secureStorageInterface: secureStorageInterface, + ); final _prefs = Prefs.instance; await _prefs.init(); @@ -609,7 +631,10 @@ abstract class SWB { for (var walletbackup in wallets) { // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } @@ -647,7 +672,10 @@ abstract class SWB { final failovers = nodeService.failoverNodesFor(coin: coin); // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } @@ -655,6 +683,7 @@ abstract class SWB { coin, walletId, walletName, + secureStorageInterface, node, txTracker, _prefs, @@ -665,7 +694,10 @@ abstract class SWB { managers.add(Tuple2(walletbackup, manager)); // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } @@ -679,7 +711,10 @@ abstract class SWB { } // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } @@ -690,7 +725,10 @@ abstract class SWB { // start restoring wallets for (final tuple in managers) { // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } final bools = await asyncRestore(tuple, uiState, walletsService); @@ -698,13 +736,19 @@ abstract class SWB { } // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } for (Future status in restoreStatuses) { // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } await status; @@ -712,7 +756,10 @@ abstract class SWB { if (!Platform.isLinux) await Wakelock.disable(); // check if cancel was requested and restore previous state - if (_checkShouldCancel(preRestoreState)) { + if (_checkShouldCancel( + preRestoreState, + secureStorageInterface, + )) { return false; } @@ -720,7 +767,10 @@ abstract class SWB { return true; } - static Future _revert(PreRestoreState revertToState) async { + static Future _revert( + PreRestoreState revertToState, + FlutterSecureStorageInterface secureStorageInterface, + ) async { Map prefs = revertToState.validJSON["prefs"] as Map; List? addressBookEntries = @@ -788,7 +838,9 @@ abstract class SWB { } // nodes - NodeService nodeService = NodeService(); + NodeService nodeService = NodeService( + secureStorageInterface: secureStorageInterface, + ); final currentNodes = nodeService.nodes; if (nodes == null) { // no pre nodes found so we delete all but defaults @@ -914,7 +966,8 @@ abstract class SWB { } // finally remove any added wallets - final walletsService = WalletsService(); + final walletsService = + WalletsService(secureStorageInterface: secureStorageInterface); final namesData = await walletsService.walletNames; for (final entry in namesData.entries) { if (!revertToState.walletIds.contains(entry.value.walletId)) { @@ -989,8 +1042,11 @@ abstract class SWB { static Future _restoreNodes( List? nodes, List? primaryNodes, + FlutterSecureStorageInterface secureStorageInterface, ) async { - NodeService nodeService = NodeService(); + NodeService nodeService = NodeService( + secureStorageInterface: secureStorageInterface, + ); if (nodes != null) { for (var node in nodes) { await nodeService.add( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 5e5142425..3097ec72a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_item_card.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/stack_restore/stack_restoring_ui_state_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -107,6 +108,7 @@ class _StackRestoreProgressViewState finished = await SWB.restoreStackWalletJSON( widget.jsonString, uiState, + ref.read(secureStoreProvider), ); } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index 0a8429058..1e137500d 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; @@ -10,7 +9,6 @@ import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.da import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -23,15 +21,10 @@ import 'package:zxcvbn/zxcvbn.dart'; class CreatePasswordView extends ConsumerStatefulWidget { const CreatePasswordView({ Key? key, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); static const String routeName = "/createPasswordDesktop"; - final FlutterSecureStorageInterface secureStore; - @override ConsumerState createState() => _CreatePasswordViewState(); } diff --git a/lib/providers/global/auto_swb_service_provider.dart b/lib/providers/global/auto_swb_service_provider.dart index 51a7c4e59..10cf2592f 100644 --- a/lib/providers/global/auto_swb_service_provider.dart +++ b/lib/providers/global/auto_swb_service_provider.dart @@ -1,5 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/services/auto_swb_service.dart'; -final autoSWBServiceProvider = - ChangeNotifierProvider((_) => AutoSWBService()); +final autoSWBServiceProvider = ChangeNotifierProvider( + (ref) => AutoSWBService( + secureStorageInterface: ref.read(secureStoreProvider), + ), +); diff --git a/lib/providers/global/node_service_provider.dart b/lib/providers/global/node_service_provider.dart index 97cea48f9..81ab22426 100644 --- a/lib/providers/global/node_service_provider.dart +++ b/lib/providers/global/node_service_provider.dart @@ -1,15 +1,16 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/services/node_service.dart'; int _count = 0; final nodeServiceChangeNotifierProvider = - ChangeNotifierProvider((_) { + ChangeNotifierProvider((ref) { if (kDebugMode) { _count++; debugPrint( "nodeServiceChangeNotifierProvider instantiation count: $_count"); } - return NodeService(); + return NodeService(secureStorageInterface: ref.read(secureStoreProvider)); }); diff --git a/lib/providers/global/secure_store_provider.dart b/lib/providers/global/secure_store_provider.dart new file mode 100644 index 000000000..299ea0f5c --- /dev/null +++ b/lib/providers/global/secure_store_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/util.dart'; + +final secureStoreProvider = Provider((ref) { + if (Util.isDesktop) { + final handler = ref.read(storageCryptoHandlerProvider).handler; + return SecureStorageWrapper( + store: DesktopPWStore(handler), isDesktop: true); + } else { + return const SecureStorageWrapper( + store: FlutterSecureStorage(), + isDesktop: false, + ); + } +}); diff --git a/lib/providers/global/wallets_service_provider.dart b/lib/providers/global/wallets_service_provider.dart index 7e46d076b..363d57613 100644 --- a/lib/providers/global/wallets_service_provider.dart +++ b/lib/providers/global/wallets_service_provider.dart @@ -1,16 +1,19 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/services/wallets_service.dart'; int _count = 0; final walletsServiceChangeNotifierProvider = - ChangeNotifierProvider((_) { + ChangeNotifierProvider((ref) { if (kDebugMode) { _count++; debugPrint( "walletsServiceChangeNotifierProvider instantiation count: $_count"); } - return WalletsService(); + return WalletsService( + secureStorageInterface: ref.read(secureStoreProvider), + ); }); diff --git a/lib/services/auto_swb_service.dart b/lib/services/auto_swb_service.dart index e3ce02f15..06ec7a2fb 100644 --- a/lib/services/auto_swb_service.dart +++ b/lib/services/auto_swb_service.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -27,9 +26,7 @@ class AutoSWBService extends ChangeNotifier { final FlutterSecureStorageInterface secureStorageInterface; - AutoSWBService( - {this.secureStorageInterface = - const SecureStorageWrapper(FlutterSecureStorage())}); + AutoSWBService({required this.secureStorageInterface}); /// Attempt a backup. Future doBackup() async { diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index d0920075d..e3c2f13fd 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -12,7 +12,6 @@ import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; @@ -1369,7 +1368,7 @@ class BitcoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1379,13 +1378,12 @@ class BitcoinWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map((e) => ElectrumXNode( address: e.host, @@ -1423,7 +1421,8 @@ class BitcoinWallet extends CoinServiceAPI { } Future getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 5b3b54663..063c74315 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -13,7 +13,6 @@ import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; @@ -1274,7 +1273,7 @@ class BitcoinCashWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1284,13 +1283,12 @@ class BitcoinCashWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map((e) => ElectrumXNode( address: e.host, @@ -1328,7 +1326,8 @@ class BitcoinCashWallet extends CoinServiceAPI { } Future getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 655865494..e63ff86e1 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'litecoin/litecoin_wallet.dart'; @@ -24,6 +25,7 @@ abstract class CoinServiceAPI { Coin coin, String walletId, String walletName, + FlutterSecureStorageInterface secureStorageInterface, NodeModel node, TransactionNotificationTracker tracker, Prefs prefs, @@ -68,6 +70,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -77,6 +80,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -87,6 +91,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -97,6 +102,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -107,6 +113,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -117,6 +124,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -127,6 +135,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -137,6 +146,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -147,6 +157,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, @@ -157,6 +168,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, // tracker: tracker, ); @@ -165,6 +177,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, // tracker: tracker, ); @@ -173,6 +186,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, // tracker: tracker, ); @@ -181,6 +195,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, tracker: tracker, cachedClient: cachedClient, client: client, @@ -191,6 +206,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, + secureStore: secureStorageInterface, client: client, cachedClient: cachedClient, tracker: tracker, diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 67be291a2..c3100b0f7 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -12,7 +12,6 @@ import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; @@ -1137,7 +1136,7 @@ class DogecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1147,13 +1146,12 @@ class DogecoinWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map((e) => ElectrumXNode( address: e.host, @@ -1191,7 +1189,8 @@ class DogecoinWallet extends CoinServiceAPI { } Future getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index 6c71f39e4..d7b7f35dd 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -6,7 +6,6 @@ import 'dart:isolate'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_libepiccash/epic_cash.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; @@ -518,14 +517,13 @@ class EpicCashWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore}) { + required FlutterSecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; Logging.instance.log("$walletName isolate length: ${isolates.length}", level: LogLevel.Info); @@ -537,7 +535,8 @@ class EpicCashWallet extends CoinServiceAPI { @override Future updateNode(bool shouldRefresh) async { - _epicNode = NodeService().getPrimaryNodeFor(coin: coin) ?? + _epicNode = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); // TODO notify ui/ fire event for node changed? diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index d19c4f1ab..d0f99ef1d 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -11,7 +11,6 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:lelantus/lelantus.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; @@ -1321,7 +1320,7 @@ class FiroWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1331,8 +1330,7 @@ class FiroWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; Logging.instance.log("$walletName isolates length: ${isolates.length}", level: LogLevel.Info); @@ -1870,7 +1868,7 @@ class FiroWallet extends CoinServiceAPI { @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map( (e) => ElectrumXNode( @@ -3071,7 +3069,8 @@ class FiroWallet extends CoinServiceAPI { } Future _getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 4551325f7..30d6ede39 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -12,7 +12,6 @@ import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; @@ -1371,7 +1370,7 @@ class LitecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1381,13 +1380,12 @@ class LitecoinWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map((e) => ElectrumXNode( address: e.host, @@ -1425,7 +1423,8 @@ class LitecoinWallet extends CoinServiceAPI { } Future getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 662d4077b..569498a15 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -72,7 +72,8 @@ class MoneroWallet extends CoinServiceAPI { late PriceAPI _priceAPI; Future getCurrentNode() async { - return NodeService().getPrimaryNodeFor(coin: coin) ?? + return NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); } @@ -81,14 +82,13 @@ class MoneroWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore}) { + required FlutterSecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } bool _shouldAutoSync = false; diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 8a4b26012..1ebf87233 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -12,7 +12,6 @@ import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; @@ -1362,7 +1361,7 @@ class NamecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore, + required FlutterSecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; @@ -1372,13 +1371,12 @@ class NamecoinWallet extends CoinServiceAPI { _cachedElectrumXClient = cachedClient; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } @override Future updateNode(bool shouldRefresh) async { - final failovers = NodeService() + final failovers = NodeService(secureStorageInterface: _secureStore) .failoverNodesFor(coin: coin) .map((e) => ElectrumXNode( address: e.host, @@ -1416,7 +1414,8 @@ class NamecoinWallet extends CoinServiceAPI { } Future getCurrentNode() async { - final node = NodeService().getPrimaryNodeFor(coin: coin) ?? + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); return ElectrumXNode( diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 788f2f9d8..7a219158a 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -73,7 +73,8 @@ class WowneroWallet extends CoinServiceAPI { late PriceAPI _priceAPI; Future getCurrentNode() async { - return NodeService().getPrimaryNodeFor(coin: coin) ?? + return NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); } @@ -82,14 +83,13 @@ class WowneroWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - FlutterSecureStorageInterface? secureStore}) { + required FlutterSecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = - secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + _secureStore = secureStore; } bool _shouldAutoSync = false; diff --git a/lib/services/node_service.dart b/lib/services/node_service.dart index 5b9fe5063..0dd706781 100644 --- a/lib/services/node_service.dart +++ b/lib/services/node_service.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; @@ -17,9 +16,7 @@ class NodeService extends ChangeNotifier { /// Exposed [secureStorageInterface] in order to inject mock for tests NodeService({ - this.secureStorageInterface = const SecureStorageWrapper( - FlutterSecureStorage(), - ), + required this.secureStorageInterface, }); Future updateDefaults() async { diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index db3011d4e..cebba2ce5 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -205,13 +205,14 @@ class Wallets extends ChangeNotifier { final txTracker = TransactionNotificationTracker(walletId: walletId); - final failovers = NodeService().failoverNodesFor(coin: coin); + final failovers = nodeService.failoverNodesFor(coin: coin); // load wallet final wallet = CoinServiceAPI.from( coin, walletId, entry.value.name, + nodeService.secureStorageInterface, node, txTracker, prefs, diff --git a/lib/services/wallets_service.dart b/lib/services/wallets_service.dart index b30f9e9e5..237df8026 100644 --- a/lib/services/wallets_service.dart +++ b/lib/services/wallets_service.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/notifications_service.dart'; @@ -55,10 +54,7 @@ class WalletsService extends ChangeNotifier { _walletNames ??= _fetchWalletNames(); WalletsService({ - FlutterSecureStorageInterface secureStorageInterface = - const SecureStorageWrapper( - FlutterSecureStorage(), - ), + required FlutterSecureStorageInterface secureStorageInterface, }) { _secureStore = secureStorageInterface; } diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index d1763e266..afb38a487 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -1,4 +1,3 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/hive/db.dart'; @@ -17,9 +16,7 @@ import 'package:stackwallet/utilities/prefs.dart'; class DbVersionMigrator { Future migrate( int fromVersion, { - FlutterSecureStorageInterface secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), + required FlutterSecureStorageInterface secureStore, }) async { Logging.instance.log( "Running migrate fromVersion $fromVersion", @@ -29,8 +26,9 @@ class DbVersionMigrator { case 0: await Hive.openBox(DB.boxNameAllWalletsData); await Hive.openBox(DB.boxNamePrefs); - final walletsService = WalletsService(); - final nodeService = NodeService(); + final walletsService = + WalletsService(secureStorageInterface: secureStore); + final nodeService = NodeService(secureStorageInterface: secureStore); final prefs = Prefs.instance; final walletInfoList = await walletsService.walletNames; await prefs.init(); @@ -118,7 +116,7 @@ class DbVersionMigrator { boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 1); // try to continue migrating - return await migrate(1); + return await migrate(1, secureStore: secureStore); case 1: await Hive.openBox(DB.boxNameTrades); @@ -142,7 +140,7 @@ class DbVersionMigrator { boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 2); // try to continue migrating - return await migrate(2); + return await migrate(2, secureStore: secureStore); case 2: await Hive.openBox(DB.boxNamePrefs); final prefs = Prefs.instance; @@ -154,7 +152,7 @@ class DbVersionMigrator { // update version await DB.instance.put( boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 3); - return await migrate(3); + return await migrate(3, secureStore: secureStore); default: // finally return diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index da537b3c2..7a2047c30 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -1,6 +1,6 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; import 'package:stack_wallet_backup/secure_storage.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/utilities/logger.dart'; const String _kKeyBlobKey = "swbKeyBlobKeyStringID"; @@ -24,7 +24,6 @@ String _getMessageFromException(Object exception) { class DPS { StorageCryptoHandler? _handler; - final SecureStorageWrapper secureStorageWrapper; StorageCryptoHandler get handler { if (_handler == null) { @@ -34,11 +33,7 @@ class DPS { return _handler!; } - DPS({ - this.secureStorageWrapper = const SecureStorageWrapper( - FlutterSecureStorage(), - ), - }); + DPS(); Future initFromNew(String passphrase) async { if (_handler != null) { @@ -47,10 +42,14 @@ class DPS { try { _handler = await StorageCryptoHandler.fromNewPassphrase(passphrase); - await secureStorageWrapper.write( + + final box = await Hive.openBox(DB.boxNameDesktopData); + await DB.instance.put( + boxName: DB.boxNameDesktopData, key: _kKeyBlobKey, value: await _handler!.getKeyBlob(), ); + await box.close(); } catch (e, s) { Logging.instance.log( "${_getMessageFromException(e)}\n$s", @@ -65,7 +64,13 @@ class DPS { throw Exception( "DPS: attempted to re initialize with existing passphrase"); } - final keyBlob = await secureStorageWrapper.read(key: _kKeyBlobKey); + + final box = await Hive.openBox(DB.boxNameDesktopData); + final keyBlob = DB.instance.get( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + ); + await box.close(); if (keyBlob == null) { throw Exception( @@ -84,6 +89,12 @@ class DPS { } Future hasPassword() async { - return (await secureStorageWrapper.read(key: _kKeyBlobKey)) != null; + final box = await Hive.openBox(DB.boxNameDesktopData); + final keyBlob = DB.instance.get( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + ); + await box.close(); + return keyBlob != null; } } diff --git a/lib/utilities/flutter_secure_storage_interface.dart b/lib/utilities/flutter_secure_storage_interface.dart index f8163ae49..f36af94f7 100644 --- a/lib/utilities/flutter_secure_storage_interface.dart +++ b/lib/utilities/flutter_secure_storage_interface.dart @@ -1,4 +1,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:isar/isar.dart'; +import 'package:stack_wallet_backup/secure_storage.dart'; abstract class FlutterSecureStorageInterface { Future write({ @@ -33,10 +35,49 @@ abstract class FlutterSecureStorageInterface { }); } -class SecureStorageWrapper implements FlutterSecureStorageInterface { - final FlutterSecureStorage secureStore; +class DesktopPWStore { + final StorageCryptoHandler handler; + late final Isar isar; - const SecureStorageWrapper(this.secureStore); + DesktopPWStore(this.handler); + + Future init() async {} + + Future read({ + required String key, + }) async { + // final String encryptedString = + + return ""; + } + + Future write({ + required String key, + required String? value, + }) async { + return; + } + + Future delete({ + required String key, + }) async { + return; + } +} + +/// all *Options params ignored on desktop +class SecureStorageWrapper implements FlutterSecureStorageInterface { + final dynamic _store; + final bool _isDesktop; + + const SecureStorageWrapper({ + required dynamic store, + required bool isDesktop, + }) : assert(isDesktop + ? store is DesktopPWStore + : store is FlutterSecureStorage), + _store = store, + _isDesktop = isDesktop; @override Future read({ @@ -47,16 +88,20 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, - }) { - return secureStore.read( - key: key, - iOptions: iOptions, - aOptions: aOptions, - lOptions: lOptions, - webOptions: webOptions, - mOptions: mOptions, - wOptions: wOptions, - ); + }) async { + if (_isDesktop) { + return await (_store as DesktopPWStore).read(key: key); + } else { + return await (_store as FlutterSecureStorage).read( + key: key, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } } @override @@ -69,17 +114,21 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions, - }) { - return secureStore.write( - key: key, - value: value, - iOptions: iOptions, - aOptions: aOptions, - lOptions: lOptions, - webOptions: webOptions, - mOptions: mOptions, - wOptions: wOptions, - ); + }) async { + if (_isDesktop) { + return await (_store as DesktopPWStore).write(key: key, value: value); + } else { + return await (_store as FlutterSecureStorage).write( + key: key, + value: value, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } } @override @@ -92,15 +141,19 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { MacOsOptions? mOptions, WindowsOptions? wOptions, }) async { - await secureStore.delete( - key: key, - iOptions: iOptions, - aOptions: aOptions, - lOptions: lOptions, - webOptions: webOptions, - mOptions: mOptions, - wOptions: wOptions, - ); + if (_isDesktop) { + return (_store as DesktopPWStore).delete(key: key); + } else { + return await (_store as FlutterSecureStorage).delete( + key: key, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } } } diff --git a/test/flutter_secure_storage_interface_test.dart b/test/flutter_secure_storage_interface_test.dart index 90dfdcf13..a421b14c1 100644 --- a/test/flutter_secure_storage_interface_test.dart +++ b/test/flutter_secure_storage_interface_test.dart @@ -13,7 +13,7 @@ void main() { when(secureStore.write(key: "testKey", value: "some value")) .thenAnswer((_) async => null); - final wrapper = SecureStorageWrapper(secureStore); + final wrapper = SecureStorageWrapper(store: secureStore, isDesktop: false); await expectLater( () async => await wrapper.write(key: "testKey", value: "some value"), @@ -27,7 +27,7 @@ void main() { final secureStore = MockFlutterSecureStorage(); when(secureStore.read(key: "testKey")) .thenAnswer((_) async => "some value"); - final wrapper = SecureStorageWrapper(secureStore); + final wrapper = SecureStorageWrapper(store: secureStore, isDesktop: false); final result = await wrapper.read(key: "testKey"); @@ -40,7 +40,7 @@ void main() { test("SecureStorageWrapper delete", () async { final secureStore = MockFlutterSecureStorage(); when(secureStore.delete(key: "testKey")).thenAnswer((_) async {}); - final wrapper = SecureStorageWrapper(secureStore); + final wrapper = SecureStorageWrapper(store: secureStore, isDesktop: false); await expectLater( () async => await wrapper.delete(key: "testKey"), returnsNormally); diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.dart index 8a240a3dd..e33ffafdf 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.dart @@ -103,7 +103,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinWallet? testnetWallet; @@ -194,7 +194,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinWallet? mainnetWallet; @@ -363,7 +363,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinWallet? btc; @@ -428,7 +428,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinWallet? btc; @@ -640,7 +640,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinWallet? btc; diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index 077163809..1c32802c9 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -64,7 +64,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinCashWallet? mainnetWallet; @@ -203,7 +203,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinCashWallet? mainnetWallet; @@ -314,7 +314,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinCashWallet? bch; @@ -383,7 +383,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinCashWallet? bch; @@ -606,7 +606,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; BitcoinCashWallet? bch; diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.dart index 7fcb1cdbd..7c1535ec5 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.dart @@ -97,7 +97,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; DogecoinWallet? mainnetWallet; @@ -196,7 +196,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; DogecoinWallet? doge; @@ -266,7 +266,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; DogecoinWallet? doge; @@ -489,7 +489,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; DogecoinWallet? doge; diff --git a/test/services/coins/namecoin/namecoin_wallet_test.dart b/test/services/coins/namecoin/namecoin_wallet_test.dart index f6bc1b065..46afd06bd 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.dart @@ -103,7 +103,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; NamecoinWallet? mainnetWallet; @@ -132,7 +132,7 @@ void main() { mainnetWallet?.addressType( address: "N673DDbjPcrNgJmrhJ1xQXF9LLizQzvjEs"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); @@ -144,7 +144,7 @@ void main() { mainnetWallet?.addressType( address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v"), DerivePathType.bip84); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); @@ -156,7 +156,7 @@ void main() { () => mainnetWallet?.addressType( address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); @@ -168,7 +168,7 @@ void main() { () => mainnetWallet?.addressType( address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); @@ -180,7 +180,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; NamecoinWallet? nmc; @@ -208,7 +208,7 @@ void main() { when(client?.ping()).thenAnswer((_) async => false); final bool? result = await nmc?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -219,7 +219,7 @@ void main() { when(client?.ping()).thenThrow(Exception); final bool? result = await nmc?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -230,7 +230,7 @@ void main() { when(client?.ping()).thenAnswer((_) async => true); final bool? result = await nmc?.testNetworkConnection(); expect(result, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -245,7 +245,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; NamecoinWallet? nmc; @@ -271,7 +271,7 @@ void main() { test("get networkType main", () async { expect(Coin.namecoin, Coin.namecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -289,7 +289,7 @@ void main() { secureStore: secureStore, ); expect(Coin.namecoin, Coin.namecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -297,7 +297,7 @@ void main() { test("get cryptoCurrency", () async { expect(Coin.namecoin, Coin.namecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -305,7 +305,7 @@ void main() { test("get coinName", () async { expect(Coin.namecoin, Coin.namecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -313,7 +313,7 @@ void main() { test("get coinTicker", () async { expect(Coin.namecoin, Coin.namecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -323,7 +323,7 @@ void main() { expect(Coin.namecoin, Coin.namecoin); nmc?.walletName = "new name"; expect(nmc?.walletName, "new name"); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -338,7 +338,7 @@ void main() { expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -372,7 +372,7 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -409,7 +409,7 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -440,7 +440,7 @@ void main() { // verify(client?.estimateFee(blocks: 1)).called(1); // verify(client?.estimateFee(blocks: 5)).called(1); // verify(client?.estimateFee(blocks: 20)).called(1); - // expect(secureStore?.interactions, 0); + // expect(secureStore.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); // verifyNoMoreInteractions(tracker); @@ -457,7 +457,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; MockPriceAPI? priceAPI; - FakeSecureStorage? secureStore; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; NamecoinWallet? nmc; @@ -504,7 +504,7 @@ void main() { // test("initializeWallet no network", () async { // when(client?.ping()).thenAnswer((_) async => false); // expect(await nmc?.initializeWallet(), false); - // expect(secureStore?.interactions, 0); + // expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); @@ -515,7 +515,7 @@ void main() { // when(client?.ping()).thenThrow(Exception("Network connection failed")); // final wallets = await Hive.openBox(testWalletId); // expect(await nmc?.initializeExisting(), false); - // expect(secureStore?.interactions, 0); + // expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); @@ -539,7 +539,7 @@ void main() { expectLater(() => nmc?.initializeExisting(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); @@ -560,13 +560,13 @@ void main() { "hash_function": "sha256", "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); final wallets = await Hive.openBox(testWalletId); expectLater(() => nmc?.initializeExisting(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 1); + expect(secureStore.interactions, 1); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); @@ -603,7 +603,7 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -623,7 +623,7 @@ void main() { "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic words"); bool hasThrown = false; @@ -640,7 +640,7 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -691,10 +691,10 @@ void main() { verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - expect(secureStore?.interactions, 20); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 13); - expect(secureStore?.deletes, 0); + expect(secureStore.interactions, 20); + expect(secureStore.writes, 7); + expect(secureStore.reads, 13); + expect(secureStore.deletes, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -814,10 +814,10 @@ void main() { true); } - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); + expect(secureStore.interactions, 14); + expect(secureStore.writes, 7); + expect(secureStore.reads, 7); + expect(secureStore.deletes, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -911,17 +911,17 @@ void main() { final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( + final preReceiveDerivationsStringP2PKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); + final preChangeDerivationsStringP2PKH = + await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + final preReceiveDerivationsStringP2SH = + await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); final preChangeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore?.read( + await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + final preReceiveDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore?.read( + final preChangeDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_changeDerivationsP2WPKH"); // destroy the data that the rescan will fix @@ -943,17 +943,17 @@ void main() { await wallet.put('changeIndexP2PKH', 123); await wallet.put('changeIndexP2SH', 123); await wallet.put('changeIndexP2WPKH', 123); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); bool hasThrown = false; @@ -980,17 +980,17 @@ void main() { final changeIndexP2SH = await wallet.get('changeIndexP2SH'); final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( + final receiveDerivationsStringP2PKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); + final changeDerivationsStringP2PKH = + await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + final receiveDerivationsStringP2SH = + await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); final changeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore?.read( + await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + final receiveDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore?.read( + final changeDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_changeDerivationsP2WPKH"); expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); @@ -1082,9 +1082,9 @@ void main() { // // argCount.forEach((key, value) => print("arg: $key\ncount: $value")); - expect(secureStore?.writes, 25); - expect(secureStore?.reads, 32); - expect(secureStore?.deletes, 6); + expect(secureStore.writes, 25); + expect(secureStore.reads, 32); + expect(secureStore.deletes, 6); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -1182,17 +1182,17 @@ void main() { final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( + final preReceiveDerivationsStringP2PKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); + final preChangeDerivationsStringP2PKH = + await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + final preReceiveDerivationsStringP2SH = + await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); final preChangeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore?.read( + await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + final preReceiveDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore?.read( + final preChangeDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_changeDerivationsP2WPKH"); when(client?.getBatchHistory(args: historyBatchArgs0)) @@ -1222,17 +1222,17 @@ void main() { final changeIndexP2SH = await wallet.get('changeIndexP2SH'); final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( + final receiveDerivationsStringP2PKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); + final changeDerivationsStringP2PKH = + await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + final receiveDerivationsStringP2SH = + await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); final changeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore?.read( + await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + final receiveDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore?.read( + final changeDerivationsStringP2WPKH = await secureStore.read( key: "${testWalletId}_changeDerivationsP2WPKH"); expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); @@ -1296,9 +1296,9 @@ void main() { verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) .called(1); - expect(secureStore?.writes, 19); - expect(secureStore?.reads, 32); - expect(secureStore?.deletes, 12); + expect(secureStore.writes, 19); + expect(secureStore.reads, 32); + expect(secureStore.deletes, 12); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -1366,21 +1366,21 @@ void main() { height: 4000); // modify addresses to properly mock data to build a tx - final rcv44 = await secureStore?.read( + final rcv44 = await secureStore.read( key: testWalletId + "_receiveDerivationsP2PKH"); - await secureStore?.write( + await secureStore.write( key: testWalletId + "_receiveDerivationsP2PKH", value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); - final rcv49 = await secureStore?.read( - key: testWalletId + "_receiveDerivationsP2SH"); - await secureStore?.write( + final rcv49 = + await secureStore.read(key: testWalletId + "_receiveDerivationsP2SH"); + await secureStore.write( key: testWalletId + "_receiveDerivationsP2SH", value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); - final rcv84 = await secureStore?.read( + final rcv84 = await secureStore.read( key: testWalletId + "_receiveDerivationsP2WPKH"); - await secureStore?.write( + await secureStore.write( key: testWalletId + "_receiveDerivationsP2WPKH", value: rcv84?.replaceFirst( "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", @@ -1436,10 +1436,10 @@ void main() { true); } - expect(secureStore?.interactions, 20); - expect(secureStore?.writes, 10); - expect(secureStore?.reads, 10); - expect(secureStore?.deletes, 0); + expect(secureStore.interactions, 20); + expect(secureStore.writes, 10); + expect(secureStore.reads, 10); + expect(secureStore.deletes, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -1456,7 +1456,7 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -1472,7 +1472,7 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -1492,7 +1492,7 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -1513,7 +1513,7 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(priceAPI); @@ -1538,7 +1538,7 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); @@ -1658,10 +1658,10 @@ void main() { true); } - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); + expect(secureStore.interactions, 14); + expect(secureStore.writes, 7); + expect(secureStore.reads, 7); + expect(secureStore.deletes, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -1726,10 +1726,10 @@ void main() { verify(client?.getBatchHistory(args: map)).called(1); } - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); + expect(secureStore.interactions, 14); + expect(secureStore.writes, 7); + expect(secureStore.reads, 7); + expect(secureStore.deletes, 0); // verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index 81f8ca6ed..cea30be2d 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -141,7 +141,8 @@ void main() { ); setUp(() async { - await NodeService().updateDefaults(); + await NodeService(secureStorageInterface: FakeSecureStorage()) + .updateDefaults(); }); test("setPrimaryNodeFor and getPrimaryNodeFor", () async { diff --git a/test/services/wallets_service_test.dart b/test/services/wallets_service_test.dart index 1cb712759..9cd808b7c 100644 --- a/test/services/wallets_service_test.dart +++ b/test/services/wallets_service_test.dart @@ -32,7 +32,7 @@ void main() { }); test("get walletNames", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect((await service.walletNames).toString(), '{wallet_id: WalletInfo: {"name":"My Firo Wallet","id":"wallet_id","coin":"bitcoin"}, wallet_id2: WalletInfo: {"name":"wallet2","id":"wallet_id2","coin":"bitcoin"}}'); }); @@ -40,13 +40,13 @@ void main() { test("get null wallet names", () async { final wallets = await Hive.openBox('wallets'); await wallets.put('names', null); - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.walletNames, {}); expect((await service.walletNames).toString(), '{}'); }); test("rename wallet to same name", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect( await service.renameWallet( from: "My Firo Wallet", @@ -58,7 +58,7 @@ void main() { }); test("rename wallet to new name", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect( await service.renameWallet( from: "My Firo Wallet", @@ -71,7 +71,7 @@ void main() { }); test("attempt rename wallet to another existing name", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect( await service.renameWallet( from: "My Firo Wallet", @@ -83,7 +83,7 @@ void main() { }); test("add new wallet name", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect( await service.addNewWallet( name: "wallet3", coin: Coin.bitcoin, shouldNotifyListeners: false), @@ -92,7 +92,7 @@ void main() { }); test("add duplicate wallet name fails", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect( await service.addNewWallet( name: "wallet2", coin: Coin.bitcoin, shouldNotifyListeners: false), @@ -103,27 +103,27 @@ void main() { test("check for duplicates when null names", () async { final wallets = await Hive.openBox('wallets'); await wallets.put('names', null); - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.checkForDuplicate("anything"), false); }); test("check for duplicates when some names with no matches", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.checkForDuplicate("anything"), false); }); test("check for duplicates when some names with a match", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.checkForDuplicate("wallet2"), true); }); test("get existing wallet id", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.getWalletId("wallet2"), "wallet_id2"); }); test("get non existent wallet id", () async { - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expectLater(await service.getWalletId("wallet 99"), null); }); diff --git a/test/services/wallets_service_test.mocks.dart b/test/services/wallets_service_test.mocks.dart index c553bbf93..0950a2cbf 100644 --- a/test/services/wallets_service_test.mocks.dart +++ b/test/services/wallets_service_test.mocks.dart @@ -42,7 +42,7 @@ class MockSecureStorageWrapper extends _i1.Mock } @override - _i2.FlutterSecureStorage get secureStore => (super.noSuchMethod( + _i2.FlutterSecureStorage get _secureStore => (super.noSuchMethod( Invocation.getter(#secureStore), returnValue: _FakeFlutterSecureStorage_0( this, From 3ee0e97628060446550a4809d974d45eee224d5f Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 17:48:43 -0600 Subject: [PATCH 004/225] DesktopSecureStore implementation using Isar as backend, renamed FlutterSecureStorageInterface --- .../isar/models/encrypted_string_value.dart | 18 + .../isar/models/encrypted_string_value.g.dart | 748 ++++++++++++++++++ lib/models/node_model.dart | 3 +- lib/pages/pinpad_views/create_pin_view.dart | 2 +- lib/pages/pinpad_views/lock_screen_view.dart | 2 +- .../add_edit_node_view.dart | 2 +- .../manage_nodes_views/node_details_view.dart | 2 +- .../change_pin_view/change_pin_view.dart | 2 +- .../create_auto_backup_view.dart | 2 +- .../edit_auto_backup_view.dart | 2 +- .../helpers/restore_create_backup.dart | 12 +- .../create_password/create_password_view.dart | 5 + .../desktop_login_view.dart | 39 +- .../global/secure_store_provider.dart | 4 +- lib/services/auto_swb_service.dart | 2 +- .../coins/bitcoin/bitcoin_wallet.dart | 4 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 4 +- lib/services/coins/coin_service.dart | 2 +- .../coins/dogecoin/dogecoin_wallet.dart | 4 +- .../coins/epiccash/epiccash_wallet.dart | 6 +- lib/services/coins/firo/firo_wallet.dart | 4 +- .../coins/litecoin/litecoin_wallet.dart | 4 +- lib/services/coins/monero/monero_wallet.dart | 4 +- .../coins/namecoin/namecoin_wallet.dart | 4 +- .../coins/wownero/wownero_wallet.dart | 4 +- lib/services/node_service.dart | 2 +- lib/services/wallets_service.dart | 4 +- lib/utilities/db_version_migration.dart | 2 +- lib/utilities/desktop_password_service.dart | 2 +- .../flutter_secure_storage_interface.dart | 59 +- test/cached_electrumx_test.mocks.dart | 8 + test/electrumx_test.mocks.dart | 8 + .../pages/send_view/send_view_test.mocks.dart | 45 +- .../exchange/exchange_view_test.mocks.dart | 8 + .../lockscreen_view_screen_test.mocks.dart | 7 +- .../create_pin_view_screen_test.mocks.dart | 7 +- ...restore_wallet_view_screen_test.mocks.dart | 6 +- ...dd_custom_node_view_screen_test.mocks.dart | 7 +- .../node_details_view_screen_test.mocks.dart | 7 +- ...twork_settings_view_screen_test.mocks.dart | 7 +- ...allet_settings_view_screen_test.mocks.dart | 16 + .../bitcoin/bitcoin_wallet_test.mocks.dart | 16 + .../bitcoincash_wallet_test.mocks.dart | 16 + .../dogecoin/dogecoin_wallet_test.mocks.dart | 16 + .../coins/firo/firo_wallet_test.mocks.dart | 16 + test/services/coins/manager_test.mocks.dart | 18 + .../namecoin/namecoin_wallet_test.mocks.dart | 16 + test/services/wallets_service_test.mocks.dart | 85 +- .../managed_favorite_test.mocks.dart | 35 +- test/widget_tests/node_card_test.mocks.dart | 7 +- .../node_options_sheet_test.mocks.dart | 14 +- .../table_view/table_view_row_test.mocks.dart | 28 + .../transaction_card_test.mocks.dart | 36 + test/widget_tests/wallet_card_test.mocks.dart | 18 + ...et_info_row_balance_future_test.mocks.dart | 35 +- .../wallet_info_row_test.mocks.dart | 35 +- 56 files changed, 1306 insertions(+), 165 deletions(-) create mode 100644 lib/models/isar/models/encrypted_string_value.dart create mode 100644 lib/models/isar/models/encrypted_string_value.g.dart diff --git a/lib/models/isar/models/encrypted_string_value.dart b/lib/models/isar/models/encrypted_string_value.dart new file mode 100644 index 000000000..79e9fcaae --- /dev/null +++ b/lib/models/isar/models/encrypted_string_value.dart @@ -0,0 +1,18 @@ +import 'package:isar/isar.dart'; + +part 'encrypted_string_value.g.dart'; + +@Collection() +class EncryptedStringValue { + Id id = Isar.autoIncrement; + + @Index(unique: true, replace: true) + late String key; + + late String value; + + @override + String toString() { + return "EncryptedStringValue {\n key=$key\n value=$value\n}"; + } +} diff --git a/lib/models/isar/models/encrypted_string_value.g.dart b/lib/models/isar/models/encrypted_string_value.g.dart new file mode 100644 index 000000000..2315c5d85 --- /dev/null +++ b/lib/models/isar/models/encrypted_string_value.g.dart @@ -0,0 +1,748 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'encrypted_string_value.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals + +extension GetEncryptedStringValueCollection on Isar { + IsarCollection get encryptedStringValues => + this.collection(); +} + +const EncryptedStringValueSchema = CollectionSchema( + name: r'EncryptedStringValue', + id: 4826543019451092626, + properties: { + r'key': PropertySchema( + id: 0, + name: r'key', + type: IsarType.string, + ), + r'value': PropertySchema( + id: 1, + name: r'value', + type: IsarType.string, + ) + }, + estimateSize: _encryptedStringValueEstimateSize, + serializeNative: _encryptedStringValueSerializeNative, + deserializeNative: _encryptedStringValueDeserializeNative, + deserializePropNative: _encryptedStringValueDeserializePropNative, + serializeWeb: _encryptedStringValueSerializeWeb, + deserializeWeb: _encryptedStringValueDeserializeWeb, + deserializePropWeb: _encryptedStringValueDeserializePropWeb, + idName: r'id', + indexes: { + r'key': IndexSchema( + id: -4906094122524121629, + name: r'key', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'key', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _encryptedStringValueGetId, + getLinks: _encryptedStringValueGetLinks, + attach: _encryptedStringValueAttach, + version: 5, +); + +int _encryptedStringValueEstimateSize( + EncryptedStringValue object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.key.length * 3; + bytesCount += 3 + object.value.length * 3; + return bytesCount; +} + +int _encryptedStringValueSerializeNative( + EncryptedStringValue object, + IsarBinaryWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.key); + writer.writeString(offsets[1], object.value); + return writer.usedBytes; +} + +EncryptedStringValue _encryptedStringValueDeserializeNative( + int id, + IsarBinaryReader reader, + List offsets, + Map> allOffsets, +) { + final object = EncryptedStringValue(); + object.id = id; + object.key = reader.readString(offsets[0]); + object.value = reader.readString(offsets[1]); + return object; +} + +P _encryptedStringValueDeserializePropNative

( + Id id, + IsarBinaryReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Object _encryptedStringValueSerializeWeb( + IsarCollection collection, + EncryptedStringValue object) { + /*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError(); +} + +EncryptedStringValue _encryptedStringValueDeserializeWeb( + IsarCollection collection, Object jsObj) { + /*final object = EncryptedStringValue();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.key = IsarNative.jsObjectGet(jsObj, r'key') ?? '';object.value = IsarNative.jsObjectGet(jsObj, r'value') ?? '';*/ + //return object; + throw UnimplementedError(); +} + +P _encryptedStringValueDeserializePropWeb

( + Object jsObj, String propertyName) { + switch (propertyName) { + default: + throw IsarError('Illegal propertyName'); + } +} + +int? _encryptedStringValueGetId(EncryptedStringValue object) { + if (object.id == Isar.autoIncrement) { + return null; + } else { + return object.id; + } +} + +List> _encryptedStringValueGetLinks( + EncryptedStringValue object) { + return []; +} + +void _encryptedStringValueAttach( + IsarCollection col, Id id, EncryptedStringValue object) { + object.id = id; +} + +extension EncryptedStringValueByIndex on IsarCollection { + Future getByKey(String key) { + return getByIndex(r'key', [key]); + } + + EncryptedStringValue? getByKeySync(String key) { + return getByIndexSync(r'key', [key]); + } + + Future deleteByKey(String key) { + return deleteByIndex(r'key', [key]); + } + + bool deleteByKeySync(String key) { + return deleteByIndexSync(r'key', [key]); + } + + Future> getAllByKey(List keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return getAllByIndex(r'key', values); + } + + List getAllByKeySync(List keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'key', values); + } + + Future deleteAllByKey(List keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'key', values); + } + + int deleteAllByKeySync(List keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'key', values); + } + + Future putByKey(EncryptedStringValue object) { + return putByIndex(r'key', object); + } + + int putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) { + return putByIndexSync(r'key', object, saveLinks: saveLinks); + } + + Future> putAllByKey(List objects) { + return putAllByIndex(r'key', objects); + } + + List putAllByKeySync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'key', objects, saveLinks: saveLinks); + } +} + +extension EncryptedStringValueQueryWhereSort + on QueryBuilder { + QueryBuilder + anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension EncryptedStringValueQueryWhere + on QueryBuilder { + QueryBuilder + idEqualTo(int id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder + idNotEqualTo(int id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder + idGreaterThan(int id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder + idLessThan(int id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder + idBetween( + int lowerId, + int upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + keyEqualTo(String key) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'key', + value: [key], + )); + }); + } + + QueryBuilder + keyNotEqualTo(String key) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'key', + lower: [], + upper: [key], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'key', + lower: [key], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'key', + lower: [key], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'key', + lower: [], + upper: [key], + includeUpper: false, + )); + } + }); + } +} + +extension EncryptedStringValueQueryFilter on QueryBuilder { + QueryBuilder idEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder keyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder keyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder keyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder keyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'key', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder keyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder keyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + keyContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + keyMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'key', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'value', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } +} + +extension EncryptedStringValueQueryObject on QueryBuilder {} + +extension EncryptedStringValueQueryLinks on QueryBuilder {} + +extension EncryptedStringValueQuerySortBy + on QueryBuilder { + QueryBuilder + sortByKey() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.asc); + }); + } + + QueryBuilder + sortByKeyDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.desc); + }); + } + + QueryBuilder + sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder + sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } +} + +extension EncryptedStringValueQuerySortThenBy + on QueryBuilder { + QueryBuilder + thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder + thenByKey() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.asc); + }); + } + + QueryBuilder + thenByKeyDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.desc); + }); + } + + QueryBuilder + thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder + thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } +} + +extension EncryptedStringValueQueryWhereDistinct + on QueryBuilder { + QueryBuilder + distinctByKey({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'key', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByValue({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value', caseSensitive: caseSensitive); + }); + } +} + +extension EncryptedStringValueQueryProperty on QueryBuilder< + EncryptedStringValue, EncryptedStringValue, QQueryProperty> { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder keyProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'key'); + }); + } + + QueryBuilder valueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'value'); + }); + } +} diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index 342f8b2ef..2628c5dd9 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -65,8 +65,7 @@ class NodeModel { } /// convenience getter to retrieve login password - Future getPassword( - FlutterSecureStorageInterface secureStorage) async { + Future getPassword(SecureStorageInterface secureStorage) async { return await secureStorage.read(key: "${id}_nodePW"); } diff --git a/lib/pages/pinpad_views/create_pin_view.dart b/lib/pages/pinpad_views/create_pin_view.dart index 5ea8bc363..766f10fa5 100644 --- a/lib/pages/pinpad_views/create_pin_view.dart +++ b/lib/pages/pinpad_views/create_pin_view.dart @@ -54,7 +54,7 @@ class _CreatePinViewState extends ConsumerState { final TextEditingController _pinPutController2 = TextEditingController(); final FocusNode _pinPutFocusNode2 = FocusNode(); - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late Biometrics biometrics; @override diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 137f3d55d..60d317e21 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -158,7 +158,7 @@ class _LockscreenViewState extends ConsumerState { final _pinTextController = TextEditingController(); final FocusNode _pinFocusNode = FocusNode(); - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late Biometrics biometrics; Scaffold get _body => Scaffold( diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 39ab493f0..890953caf 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -634,7 +634,7 @@ class NodeForm extends ConsumerStatefulWidget { }) : super(key: key); final NodeModel? node; - final FlutterSecureStorageInterface secureStore; + final SecureStorageInterface secureStore; final bool readOnly; final Coin coin; final void Function(bool canSave, bool canTestConnection)? onChanged; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 6d9641b7d..a80a64147 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -45,7 +45,7 @@ class NodeDetailsView extends ConsumerStatefulWidget { } class _NodeDetailsViewState extends ConsumerState { - late final FlutterSecureStorageInterface secureStore; + late final SecureStorageInterface secureStore; late final Coin coin; late final String nodeId; late final String popRouteName; diff --git a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart index 46c2fd9cf..c88da0521 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart @@ -45,7 +45,7 @@ class _ChangePinViewState extends ConsumerState { final TextEditingController _pinPutController2 = TextEditingController(); final FocusNode _pinPutFocusNode2 = FocusNode(); - late final FlutterSecureStorageInterface _secureStore; + late final SecureStorageInterface _secureStore; @override void initState() { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index 1082acc99..334d50e35 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -41,7 +41,7 @@ class CreateAutoBackupView extends ConsumerStatefulWidget { } class _EnableAutoBackupViewState extends ConsumerState { - late final FlutterSecureStorageInterface secureStore; + late final SecureStorageInterface secureStore; late final TextEditingController fileLocationController; late final TextEditingController passwordController; diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index d5ba21634..0be718549 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -40,7 +40,7 @@ class EditAutoBackupView extends ConsumerStatefulWidget { } class _EditAutoBackupViewState extends ConsumerState { - late final FlutterSecureStorageInterface secureStore; + late final SecureStorageInterface secureStore; late final TextEditingController fileLocationController; late final TextEditingController passwordController; diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 9b755c95a..3d599f717 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -90,7 +90,7 @@ abstract class SWB { static bool _checkShouldCancel( PreRestoreState? revertToState, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) { if (_shouldCancelRestore) { if (revertToState != null) { @@ -193,7 +193,7 @@ abstract class SWB { /// [secureStorage] parameter exposed for testing purposes static Future> createStackWalletJSON({ - required FlutterSecureStorageInterface secureStorage, + required SecureStorageInterface secureStorage, }) async { Logging.instance .log("Starting createStackWalletJSON...", level: LogLevel.Info); @@ -448,7 +448,7 @@ abstract class SWB { Map validJSON, StackRestoringUIState? uiState, Map oldToNewWalletIdMap, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { Map prefs = validJSON["prefs"] as Map; List? addressBookEntries = @@ -548,7 +548,7 @@ abstract class SWB { static Future restoreStackWalletJSON( String jsonBackup, StackRestoringUIState? uiState, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { if (!Platform.isLinux) await Wakelock.enable(); @@ -769,7 +769,7 @@ abstract class SWB { static Future _revert( PreRestoreState revertToState, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { Map prefs = revertToState.validJSON["prefs"] as Map; @@ -1042,7 +1042,7 @@ abstract class SWB { static Future _restoreNodes( List? nodes, List? primaryNodes, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { NodeService nodeService = NodeService( secureStorageInterface: secureStorageInterface, diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index 1e137500d..d5ce42679 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -72,6 +72,11 @@ class _CreatePasswordViewState extends ConsumerState { } try { + if (await ref.read(storageCryptoHandlerProvider).hasPassword()) { + throw Exception( + "Tried creating a new password and attempted to overwrite an existing entry!"); + } + await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase); } catch (e) { unawaited(showFloatingFlushBar( diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index fe05d719f..0a47c2a96 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -1,9 +1,15 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -11,7 +17,7 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; -class DesktopLoginView extends StatefulWidget { +class DesktopLoginView extends ConsumerStatefulWidget { const DesktopLoginView({ Key? key, this.startupWalletId, @@ -22,10 +28,10 @@ class DesktopLoginView extends StatefulWidget { final String? startupWalletId; @override - State createState() => _DesktopLoginViewState(); + ConsumerState createState() => _DesktopLoginViewState(); } -class _DesktopLoginViewState extends State { +class _DesktopLoginViewState extends ConsumerState { late final TextEditingController passwordController; late final FocusNode passwordFocusNode; @@ -153,13 +159,28 @@ class _DesktopLoginViewState extends State { PrimaryButton( label: "Continue", enabled: _continueEnabled, - onPressed: () { - // todo auth + onPressed: () async { + try { + await ref + .read(storageCryptoHandlerProvider) + .initFromExisting(passwordController.text); - Navigator.of(context).pushNamedAndRemoveUntil( - DesktopHomeView.routeName, - (route) => false, - ); + // if no errors passphrase is correct + if (mounted) { + unawaited( + Navigator.of(context).pushNamedAndRemoveUntil( + DesktopHomeView.routeName, + (route) => false, + ), + ); + } + } catch (e) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ); + } }, ), const SizedBox( diff --git a/lib/providers/global/secure_store_provider.dart b/lib/providers/global/secure_store_provider.dart index 299ea0f5c..32304d0f2 100644 --- a/lib/providers/global/secure_store_provider.dart +++ b/lib/providers/global/secure_store_provider.dart @@ -4,11 +4,11 @@ import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.da import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/util.dart'; -final secureStoreProvider = Provider((ref) { +final secureStoreProvider = Provider((ref) { if (Util.isDesktop) { final handler = ref.read(storageCryptoHandlerProvider).handler; return SecureStorageWrapper( - store: DesktopPWStore(handler), isDesktop: true); + store: DesktopSecureStore(handler), isDesktop: true); } else { return const SecureStorageWrapper( store: FlutterSecureStorage(), diff --git a/lib/services/auto_swb_service.dart b/lib/services/auto_swb_service.dart index 06ec7a2fb..f7efc994e 100644 --- a/lib/services/auto_swb_service.dart +++ b/lib/services/auto_swb_service.dart @@ -24,7 +24,7 @@ class AutoSWBService extends ChangeNotifier { bool _isActiveTimer = false; bool get isActivePeriodicTimer => _isActiveTimer; - final FlutterSecureStorageInterface secureStorageInterface; + final SecureStorageInterface secureStorageInterface; AutoSWBService({required this.secureStorageInterface}); diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index e3c2f13fd..dfd5ea180 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1356,7 +1356,7 @@ class BitcoinWallet extends CoinServiceAPI { CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1368,7 +1368,7 @@ class BitcoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 063c74315..429af898e 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1261,7 +1261,7 @@ class BitcoinCashWallet extends CoinServiceAPI { CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1273,7 +1273,7 @@ class BitcoinCashWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index e63ff86e1..16015ea0c 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -25,7 +25,7 @@ abstract class CoinServiceAPI { Coin coin, String walletId, String walletName, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, NodeModel node, TransactionNotificationTracker tracker, Prefs prefs, diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index c3100b0f7..41778a9e0 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1124,7 +1124,7 @@ class DogecoinWallet extends CoinServiceAPI { CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1136,7 +1136,7 @@ class DogecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index d7b7f35dd..683e26544 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -250,7 +250,7 @@ Future _deleteWalletWrapper(String wallet) async { Future deleteEpicWallet({ required String walletId, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) async { String? config = await secureStore.read(key: '${walletId}_config'); if (Platform.isIOS) { @@ -517,7 +517,7 @@ class EpicCashWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore}) { + required SecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; @@ -658,7 +658,7 @@ class EpicCashWallet extends CoinServiceAPI { @override Coin get coin => _coin; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index d0f99ef1d..4bd863f2c 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -1305,7 +1305,7 @@ class FiroWallet extends CoinServiceAPI { late CachedElectrumX _cachedElectrumXClient; CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1320,7 +1320,7 @@ class FiroWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 30d6ede39..db7c9d1fa 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1358,7 +1358,7 @@ class LitecoinWallet extends CoinServiceAPI { CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1370,7 +1370,7 @@ class LitecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 569498a15..69e0bd38d 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -67,7 +67,7 @@ class MoneroWallet extends CoinServiceAPI { Timer? moneroAutosaveTimer; late Coin _coin; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -82,7 +82,7 @@ class MoneroWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore}) { + required SecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 1ebf87233..c8c84fb27 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1349,7 +1349,7 @@ class NamecoinWallet extends CoinServiceAPI { CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -1361,7 +1361,7 @@ class NamecoinWallet extends CoinServiceAPI { required CachedElectrumX cachedClient, required TransactionNotificationTracker tracker, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) { txTracker = tracker; _walletId = walletId; diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 7a219158a..7476afbef 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -68,7 +68,7 @@ class WowneroWallet extends CoinServiceAPI { Timer? wowneroAutosaveTimer; late Coin _coin; - late FlutterSecureStorageInterface _secureStore; + late SecureStorageInterface _secureStore; late PriceAPI _priceAPI; @@ -83,7 +83,7 @@ class WowneroWallet extends CoinServiceAPI { required String walletName, required Coin coin, PriceAPI? priceAPI, - required FlutterSecureStorageInterface secureStore}) { + required SecureStorageInterface secureStore}) { _walletId = walletId; _walletName = walletName; _coin = coin; diff --git a/lib/services/node_service.dart b/lib/services/node_service.dart index 0dd706781..aa8d5a6d9 100644 --- a/lib/services/node_service.dart +++ b/lib/services/node_service.dart @@ -12,7 +12,7 @@ import 'package:stackwallet/utilities/logger.dart'; const kStackCommunityNodesEndpoint = "https://extras.stackwallet.com"; class NodeService extends ChangeNotifier { - final FlutterSecureStorageInterface secureStorageInterface; + final SecureStorageInterface secureStorageInterface; /// Exposed [secureStorageInterface] in order to inject mock for tests NodeService({ diff --git a/lib/services/wallets_service.dart b/lib/services/wallets_service.dart index 237df8026..1371b17b6 100644 --- a/lib/services/wallets_service.dart +++ b/lib/services/wallets_service.dart @@ -47,14 +47,14 @@ class WalletInfo { } class WalletsService extends ChangeNotifier { - late final FlutterSecureStorageInterface _secureStore; + late final SecureStorageInterface _secureStore; Future>? _walletNames; Future> get walletNames => _walletNames ??= _fetchWalletNames(); WalletsService({ - required FlutterSecureStorageInterface secureStorageInterface, + required SecureStorageInterface secureStorageInterface, }) { _secureStore = secureStorageInterface; } diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index afb38a487..ae5190fc4 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -16,7 +16,7 @@ import 'package:stackwallet/utilities/prefs.dart'; class DbVersionMigrator { Future migrate( int fromVersion, { - required FlutterSecureStorageInterface secureStore, + required SecureStorageInterface secureStore, }) async { Logging.instance.log( "Running migrate fromVersion $fromVersion", diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index 7a2047c30..178871e69 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -84,7 +84,7 @@ class DPS { "${_getMessageFromException(e)}\n$s", level: LogLevel.Error, ); - rethrow; + throw Exception(_getMessageFromException(e)); } } diff --git a/lib/utilities/flutter_secure_storage_interface.dart b/lib/utilities/flutter_secure_storage_interface.dart index f36af94f7..852c13f3d 100644 --- a/lib/utilities/flutter_secure_storage_interface.dart +++ b/lib/utilities/flutter_secure_storage_interface.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:isar/isar.dart'; import 'package:stack_wallet_backup/secure_storage.dart'; +import 'package:stackwallet/models/isar/models/encrypted_string_value.dart'; -abstract class FlutterSecureStorageInterface { +abstract class SecureStorageInterface { Future write({ required String key, required String? value, @@ -35,38 +38,66 @@ abstract class FlutterSecureStorageInterface { }); } -class DesktopPWStore { +class DesktopSecureStore { final StorageCryptoHandler handler; late final Isar isar; - DesktopPWStore(this.handler); + DesktopSecureStore(this.handler); - Future init() async {} + Future init() async { + Directory? appDirectory; + if (Platform.isLinux) { + appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); + await appDirectory.create(); + } + isar = await Isar.open( + [EncryptedStringValueSchema], + directory: appDirectory!.path, + inspector: false, + ); + } Future read({ required String key, }) async { - // final String encryptedString = + final value = + await isar.encryptedStringValues.filter().keyEqualTo(key).findFirst(); - return ""; + // value does not exist; + if (value == null) { + return null; + } + + return await handler.decryptValue(key, value.value); } Future write({ required String key, required String? value, }) async { - return; + if (value == null) { + // here we assume that a value is to be deleted + await isar.encryptedStringValues.deleteByKey(key); + } else { + // otherwise created encrypted object value + final object = EncryptedStringValue(); + object.key = key; + object.value = await handler.encryptValue(key, value); + + // store object value + await isar.encryptedStringValues.put(object); + } } Future delete({ required String key, }) async { - return; + await isar.encryptedStringValues.deleteByKey(key); } } /// all *Options params ignored on desktop -class SecureStorageWrapper implements FlutterSecureStorageInterface { +class SecureStorageWrapper implements SecureStorageInterface { final dynamic _store; final bool _isDesktop; @@ -74,7 +105,7 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { required dynamic store, required bool isDesktop, }) : assert(isDesktop - ? store is DesktopPWStore + ? store is DesktopSecureStore : store is FlutterSecureStorage), _store = store, _isDesktop = isDesktop; @@ -90,7 +121,7 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { WindowsOptions? wOptions, }) async { if (_isDesktop) { - return await (_store as DesktopPWStore).read(key: key); + return await (_store as DesktopSecureStore).read(key: key); } else { return await (_store as FlutterSecureStorage).read( key: key, @@ -116,7 +147,7 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { WindowsOptions? wOptions, }) async { if (_isDesktop) { - return await (_store as DesktopPWStore).write(key: key, value: value); + return await (_store as DesktopSecureStore).write(key: key, value: value); } else { return await (_store as FlutterSecureStorage).write( key: key, @@ -142,7 +173,7 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { WindowsOptions? wOptions, }) async { if (_isDesktop) { - return (_store as DesktopPWStore).delete(key: key); + return (_store as DesktopSecureStore).delete(key: key); } else { return await (_store as FlutterSecureStorage).delete( key: key, @@ -158,7 +189,7 @@ class SecureStorageWrapper implements FlutterSecureStorageInterface { } // Mock class for testing purposes -class FakeSecureStorage implements FlutterSecureStorageInterface { +class FakeSecureStorage implements SecureStorageInterface { final Map _store = {}; int _interactions = 0; int get interactions => _interactions; diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 1e3e70a0a..a45cdd402 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -678,6 +678,14 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override + _i4.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i4.Future.value(false), + ) as _i4.Future); + @override void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart index fa48ea2fd..9f29ae1e5 100644 --- a/test/electrumx_test.mocks.dart +++ b/test/electrumx_test.mocks.dart @@ -399,6 +399,14 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override + _i3.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index a07377309..d63dafb04 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -1,5 +1,5 @@ // Mocks generated by Mockito 5.3.2 from annotations -// in stackwallet/test/pages/send_view_test.dart. +// in stackwallet/test/pages/send_view/send_view_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes @@ -86,7 +86,7 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager { } class _FakeFlutterSecureStorageInterface_4 extends _i1.SmartFake - implements _i7.FlutterSecureStorageInterface { + implements _i7.SecureStorageInterface { _FakeFlutterSecureStorageInterface_4( Object parent, Invocation parentInvocation, @@ -621,14 +621,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { } @override - _i7.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i7.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_4( this, Invocation.getter(#secureStorageInterface), ), - ) as _i7.FlutterSecureStorageInterface); + ) as _i7.SecureStorageInterface); @override List<_i19.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), @@ -890,6 +889,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i9.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -1272,6 +1279,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -1875,6 +1892,14 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i16.Future.value(false), + ) as _i16.Future); + @override void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, @@ -2623,4 +2648,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { ), returnValue: _i16.Future.value(false), ) as _i16.Future); + @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); } diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 1d93023d5..0da12dbd0 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -350,6 +350,14 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override + _i7.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i7.Future.value(false), + ) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, diff --git a/test/screen_tests/lockscreen_view_screen_test.mocks.dart b/test/screen_tests/lockscreen_view_screen_test.mocks.dart index 1cd697e05..a33c4ef28 100644 --- a/test/screen_tests/lockscreen_view_screen_test.mocks.dart +++ b/test/screen_tests/lockscreen_view_screen_test.mocks.dart @@ -30,7 +30,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -309,14 +309,13 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i10.NodeService { @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i11.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart index 62d3c597d..3aed1dcb8 100644 --- a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart @@ -30,7 +30,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -309,14 +309,13 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i10.NodeService { @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i11.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart index cf891e829..cd4986e13 100644 --- a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart @@ -84,7 +84,7 @@ class _FakeTransactionData_4 extends _i1.SmartFake } class _FakeFlutterSecureStorageInterface_5 extends _i1.SmartFake - implements _i6.FlutterSecureStorageInterface { + implements _i6.SecureStorageInterface { _FakeFlutterSecureStorageInterface_5( Object parent, Invocation parentInvocation, @@ -744,14 +744,14 @@ class MockManager extends _i1.Mock implements _i12.Manager { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i13.NodeService { @override - _i6.FlutterSecureStorageInterface get secureStorageInterface => + _i6.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_5( this, Invocation.getter(#secureStorageInterface), ), - ) as _i6.FlutterSecureStorageInterface); + ) as _i6.SecureStorageInterface); @override List<_i14.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart index d80b97852..3ac5afcc7 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart @@ -29,7 +29,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -86,14 +86,13 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i6.NodeService { @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i7.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart index 89820ee97..0f6447a9e 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart @@ -29,7 +29,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -86,14 +86,13 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i6.NodeService { @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i7.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart index 7e8ff731e..707da7345 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart @@ -25,7 +25,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -40,14 +40,13 @@ class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i4.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart index 27ae85597..7eb0853dd 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart @@ -139,6 +139,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { _i8.Future>.value({}), ) as _i8.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i8.Future> getTransaction({ required String? txHash, required _i9.Coin? coin, diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart index 99e2e6231..32a6c7195 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart @@ -465,6 +465,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i6.Future> getTransaction({ required String? txHash, required _i8.Coin? coin, diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index a5a2018e7..bfc5f793b 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -465,6 +465,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i6.Future> getTransaction({ required String? txHash, required _i8.Coin? coin, diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart index 944e5faea..f7220922e 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart @@ -465,6 +465,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i6.Future> getTransaction({ required String? txHash, required _i8.Coin? coin, diff --git a/test/services/coins/firo/firo_wallet_test.mocks.dart b/test/services/coins/firo/firo_wallet_test.mocks.dart index 4fe6af52a..2d32cef48 100644 --- a/test/services/coins/firo/firo_wallet_test.mocks.dart +++ b/test/services/coins/firo/firo_wallet_test.mocks.dart @@ -465,6 +465,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i6.Future> getTransaction({ required String? txHash, required _i8.Coin? coin, diff --git a/test/services/coins/manager_test.mocks.dart b/test/services/coins/manager_test.mocks.dart index 0c20b752d..9a958702f 100644 --- a/test/services/coins/manager_test.mocks.dart +++ b/test/services/coins/manager_test.mocks.dart @@ -117,6 +117,14 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i4.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override _i2.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), returnValue: _FakeTransactionNotificationTracker_0( @@ -375,6 +383,16 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: false, ) as bool); @override + _i8.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart index a81c27fe0..91c3e5bfa 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -465,6 +465,22 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override + String base64ToHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToHex, + [source], + ), + returnValue: '', + ) as String); + @override + String base64ToReverseHex(String? source) => (super.noSuchMethod( + Invocation.method( + #base64ToReverseHex, + [source], + ), + returnValue: '', + ) as String); + @override _i6.Future> getTransaction({ required String? txHash, required _i8.Coin? coin, diff --git a/test/services/wallets_service_test.mocks.dart b/test/services/wallets_service_test.mocks.dart index 0950a2cbf..19d525196 100644 --- a/test/services/wallets_service_test.mocks.dart +++ b/test/services/wallets_service_test.mocks.dart @@ -3,12 +3,12 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; +import 'dart:async' as _i3; -import 'package:flutter_secure_storage/flutter_secure_storage.dart' as _i2; +import 'package:flutter_secure_storage/flutter_secure_storage.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i3; + as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -21,43 +21,24 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeFlutterSecureStorage_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorage { - _FakeFlutterSecureStorage_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [SecureStorageWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockSecureStorageWrapper extends _i1.Mock - implements _i3.SecureStorageWrapper { + implements _i2.SecureStorageWrapper { MockSecureStorageWrapper() { _i1.throwOnMissingStub(this); } @override - _i2.FlutterSecureStorage get _secureStore => (super.noSuchMethod( - Invocation.getter(#secureStore), - returnValue: _FakeFlutterSecureStorage_0( - this, - Invocation.getter(#secureStore), - ), - ) as _i2.FlutterSecureStorage); - @override - _i4.Future read({ + _i3.Future read({ required String? key, - _i2.IOSOptions? iOptions, - _i2.AndroidOptions? aOptions, - _i2.LinuxOptions? lOptions, - _i2.WebOptions? webOptions, - _i2.MacOsOptions? mOptions, - _i2.WindowsOptions? wOptions, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, }) => (super.noSuchMethod( Invocation.method( @@ -73,18 +54,18 @@ class MockSecureStorageWrapper extends _i1.Mock #wOptions: wOptions, }, ), - returnValue: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future write({ + _i3.Future write({ required String? key, required String? value, - _i2.IOSOptions? iOptions, - _i2.AndroidOptions? aOptions, - _i2.LinuxOptions? lOptions, - _i2.WebOptions? webOptions, - _i2.MacOsOptions? mOptions, - _i2.WindowsOptions? wOptions, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, }) => (super.noSuchMethod( Invocation.method( @@ -101,18 +82,18 @@ class MockSecureStorageWrapper extends _i1.Mock #wOptions: wOptions, }, ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i4.Future delete({ + _i3.Future delete({ required String? key, - _i2.IOSOptions? iOptions, - _i2.AndroidOptions? aOptions, - _i2.LinuxOptions? lOptions, - _i2.WebOptions? webOptions, - _i2.MacOsOptions? mOptions, - _i2.WindowsOptions? wOptions, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, }) => (super.noSuchMethod( Invocation.method( @@ -128,7 +109,7 @@ class MockSecureStorageWrapper extends _i1.Mock #wOptions: wOptions, }, ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index 7d1d864df..50f906c3c 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -166,7 +166,7 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake } class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.FlutterSecureStorageInterface { + implements _i12.SecureStorageInterface { _FakeFlutterSecureStorageInterface_12( Object parent, Invocation parentInvocation, @@ -681,6 +681,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -1063,6 +1071,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -1380,14 +1398,13 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_12( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.FlutterSecureStorageInterface); + ) as _i12.SecureStorageInterface); @override List<_i21.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), @@ -2291,4 +2308,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { ), returnValue: _i16.Future.value(false), ) as _i16.Future); + @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); } diff --git a/test/widget_tests/node_card_test.mocks.dart b/test/widget_tests/node_card_test.mocks.dart index 673182ceb..2bb32b58d 100644 --- a/test/widget_tests/node_card_test.mocks.dart +++ b/test/widget_tests/node_card_test.mocks.dart @@ -25,7 +25,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' // ignore_for_file: subtype_of_sealed_class class _FakeFlutterSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.FlutterSecureStorageInterface { + implements _i2.SecureStorageInterface { _FakeFlutterSecureStorageInterface_0( Object parent, Invocation parentInvocation, @@ -44,14 +44,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { } @override - _i2.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_0( this, Invocation.getter(#secureStorageInterface), ), - ) as _i2.FlutterSecureStorageInterface); + ) as _i2.SecureStorageInterface); @override List<_i4.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 173edf1bf..c9e4e2bb8 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -77,7 +77,7 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager { } class _FakeFlutterSecureStorageInterface_4 extends _i1.SmartFake - implements _i7.FlutterSecureStorageInterface { + implements _i7.SecureStorageInterface { _FakeFlutterSecureStorageInterface_4( Object parent, Invocation parentInvocation, @@ -573,6 +573,14 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs { returnValueForMissingStub: _i10.Future.value(), ) as _i10.Future); @override + _i10.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i10.Future.value(false), + ) as _i10.Future); + @override void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, @@ -615,14 +623,14 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { } @override - _i7.FlutterSecureStorageInterface get secureStorageInterface => + _i7.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_4( this, Invocation.getter(#secureStorageInterface), ), - ) as _i7.FlutterSecureStorageInterface); + ) as _i7.SecureStorageInterface); @override List<_i16.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), diff --git a/test/widget_tests/table_view/table_view_row_test.mocks.dart b/test/widget_tests/table_view/table_view_row_test.mocks.dart index b4a0e9cc7..5a47436ff 100644 --- a/test/widget_tests/table_view/table_view_row_test.mocks.dart +++ b/test/widget_tests/table_view/table_view_row_test.mocks.dart @@ -666,6 +666,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -1048,6 +1056,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: _i15.Future.value(), ) as _i15.Future); @override + _i15.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i15.Future.value(), + returnValueForMissingStub: _i15.Future.value(), + ) as _i15.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -2013,4 +2031,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { ), returnValue: _i15.Future.value(false), ) as _i15.Future); + @override + _i15.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i15.Future.value(), + returnValueForMissingStub: _i15.Future.value(), + ) as _i15.Future); } diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index df696801a..f258a402b 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -1108,6 +1108,16 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { ), returnValue: _i16.Future.value(false), ) as _i16.Future); + @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); } /// A class which mocks [FiroWallet]. @@ -1127,6 +1137,14 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override _i10.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), returnValue: _FakeTransactionNotificationTracker_8( @@ -1386,6 +1404,16 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: false, ) as bool); @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); + @override _i16.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, @@ -2312,6 +2340,14 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i16.Future.value(false), + ) as _i16.Future); + @override void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, diff --git a/test/widget_tests/wallet_card_test.mocks.dart b/test/widget_tests/wallet_card_test.mocks.dart index ec4f90e22..e323911d4 100644 --- a/test/widget_tests/wallet_card_test.mocks.dart +++ b/test/widget_tests/wallet_card_test.mocks.dart @@ -429,6 +429,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -811,6 +819,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: _i14.Future.value(), ) as _i14.Future); @override + _i14.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i14.Future.value(), + returnValueForMissingStub: _i14.Future.value(), + ) as _i14.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, diff --git a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart index e5dc17bc4..a249c997d 100644 --- a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart @@ -165,7 +165,7 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake } class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.FlutterSecureStorageInterface { + implements _i12.SecureStorageInterface { _FakeFlutterSecureStorageInterface_12( Object parent, Invocation parentInvocation, @@ -680,6 +680,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -1062,6 +1070,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -1317,14 +1335,13 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_12( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.FlutterSecureStorageInterface); + ) as _i12.SecureStorageInterface); @override List<_i20.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), @@ -2228,4 +2245,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { ), returnValue: _i16.Future.value(false), ) as _i16.Future); + @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); } diff --git a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart index 2b7bedb15..820fbd96d 100644 --- a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart @@ -165,7 +165,7 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake } class _FakeFlutterSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.FlutterSecureStorageInterface { + implements _i12.SecureStorageInterface { _FakeFlutterSecureStorageInterface_12( Object parent, Invocation parentInvocation, @@ -680,6 +680,14 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override + set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( + Invocation.setter( + #cachedTxData, + _cachedTxData, + ), + returnValueForMissingStub: null, + ); + @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -1062,6 +1070,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: _i16.Future.value(), ) as _i16.Future); @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -1317,14 +1335,13 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.FlutterSecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( + _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeFlutterSecureStorageInterface_12( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.FlutterSecureStorageInterface); + ) as _i12.SecureStorageInterface); @override List<_i20.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), @@ -2228,4 +2245,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { ), returnValue: _i16.Future.value(false), ) as _i16.Future); + @override + _i16.Future updateSentCachedTxData(Map? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); } From 8b7e222d416ba2d38a1236a7a6b60b64efd21ffe Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 17:54:12 -0600 Subject: [PATCH 005/225] WIP: proper home directory location for linux --- lib/main.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 7f7c4a44d..c88ae2164 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -85,10 +85,20 @@ void main() async { if (Platform.isIOS) { appDirectory = (await getLibraryDirectory()); } - if (Platform.isLinux || Logging.isArmLinux) { + + if (Logging.isArmLinux) { appDirectory = Directory("${appDirectory.path}/.stackwallet"); await appDirectory.create(); } + + if (Platform.isLinux) { + appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); + + if (!appDirectory.existsSync()) { + await appDirectory.create(); + } + } + // FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); if (!(Logging.isArmLinux || Logging.isTestEnv)) { final isar = await Isar.open( From 2082e875536c1fea506ba633526bc5c2518a5c56 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 9 Nov 2022 17:55:10 -0600 Subject: [PATCH 006/225] WIP: desktop loading order --- lib/main.dart | 93 +++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c88ae2164..a8b515b02 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,6 +60,7 @@ import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:window_size/window_size.dart'; final openedFromSWBFileStringStateProvider = @@ -568,50 +569,56 @@ class _MaterialAppWithThemeState extends ConsumerState _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), ), ), - home: FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - // FlutterNativeSplash.remove(); - if (Util.isDesktop && - (_wallets.hasWallets || _desktopHasPassword)) { - String? startupWalletId; - if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; - } - - return DesktopLoginView(startupWalletId: startupWalletId); - } else if (!Util.isDesktop && - (_wallets.hasWallets || _prefs.hasPin)) { - // return HomeView(); - - String? startupWalletId; - if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; - } - - return LockscreenView( - isInitialAppLogin: true, - routeOnSuccess: HomeView.routeName, - routeOnSuccessArguments: startupWalletId, - biometricsAuthenticationTitle: "Unlock Stack", - biometricsLocalizedReason: - "Unlock your stack wallet using biometrics", - biometricsCancelButtonString: "Cancel", - ); - } else { - return const IntroView(); - } - } else { - // CURRENTLY DISABLED as cannot be animated - // technically not needed as FlutterNativeSplash will overlay - // anything returned here until the future completes but - // FutureBuilder requires you to return something - return const LoadingView(); - } + home: ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return child; }, + child: FutureBuilder( + future: load(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // FlutterNativeSplash.remove(); + if (Util.isDesktop && + (_wallets.hasWallets || _desktopHasPassword)) { + String? startupWalletId; + if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { + startupWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; + } + + return DesktopLoginView(startupWalletId: startupWalletId); + } else if (!Util.isDesktop && + (_wallets.hasWallets || _prefs.hasPin)) { + // return HomeView(); + + String? startupWalletId; + if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { + startupWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; + } + + return LockscreenView( + isInitialAppLogin: true, + routeOnSuccess: HomeView.routeName, + routeOnSuccessArguments: startupWalletId, + biometricsAuthenticationTitle: "Unlock Stack", + biometricsLocalizedReason: + "Unlock your stack wallet using biometrics", + biometricsCancelButtonString: "Cancel", + ); + } else { + return const IntroView(); + } + } else { + // CURRENTLY DISABLED as cannot be animated + // technically not needed as FlutterNativeSplash will overlay + // anything returned here until the future completes but + // FutureBuilder requires you to return something + return const LoadingView(); + } + }, + ), ), ); } From f2bef21853639653b1be0ded1fc4355c55b8cb68 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 11:40:33 -0600 Subject: [PATCH 007/225] temp disable erroring code --- .../restore_from_file_view.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index dbf46c729..ee1fcf666 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -9,7 +9,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; +// import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -49,14 +49,14 @@ class _RestoreFromFileViewState extends ConsumerState { bool hidePassword = true; Future restoreBackupPopup(BuildContext context) async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const RestoreBackupDialog(); - }, - ); + // await showDialog( + // context: context, + // useSafeArea: false, + // barrierDismissible: true, + // builder: (context) { + // return const RestoreBackupDialog(); + // }, + // ); } @override From a50520b37ffdf1630aca2a9bd4f5a12f50db0cf7 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 12:40:16 -0600 Subject: [PATCH 008/225] WIP: desktop password login and auth flow --- lib/hive/db.dart | 52 +++--- lib/main.dart | 153 ++++++++++-------- .../desktop_login_view.dart | 52 +++--- .../create_auto_backup.dart | 15 +- lib/utilities/desktop_password_service.dart | 2 - 5 files changed, 148 insertions(+), 126 deletions(-) diff --git a/lib/hive/db.dart b/lib/hive/db.dart index d5402752e..48e6ba154 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -43,23 +43,23 @@ class DB { static bool _initialized = false; - late final Box _boxAddressBook; - late final Box _boxDebugInfo; - late final Box _boxNodeModels; - late final Box _boxPrimaryNodes; - late final Box _boxAllWalletsData; - late final Box _boxNotifications; - late final Box _boxWatchedTransactions; - late final Box _boxWatchedTrades; - late final Box _boxTrades; - late final Box _boxTradesV2; - late final Box _boxTradeNotes; - late final Box _boxFavoriteWallets; - late final Box _walletInfoSource; - late final Box _boxPrefs; - late final Box _boxTradeLookup; - late final Box _boxDBInfo; - late final Box _boxDesktopData; + Box? _boxAddressBook; + Box? _boxDebugInfo; + Box? _boxNodeModels; + Box? _boxPrimaryNodes; + Box? _boxAllWalletsData; + Box? _boxNotifications; + Box? _boxWatchedTransactions; + Box? _boxWatchedTrades; + Box? _boxTrades; + Box? _boxTradesV2; + Box? _boxTradeNotes; + Box? _boxFavoriteWallets; + Box? _walletInfoSource; + Box? _boxPrefs; + Box? _boxTradeLookup; + Box? _boxDBInfo; + Box? _boxDesktopData; final Map> _walletBoxes = {}; @@ -68,7 +68,7 @@ class DB { final Map> _usedSerialsCacheBoxes = {}; // exposed for monero - Box get moneroWalletInfoBox => _walletInfoSource; + Box get moneroWalletInfoBox => _walletInfoSource!; // mutex for stack backup final mutex = Mutex(); @@ -124,6 +124,12 @@ class DB { _boxAllWalletsData = await Hive.openBox(boxNameAllWalletsData); } + if (Hive.isBoxOpen(boxNameDesktopData)) { + _boxDesktopData = Hive.box(boxNameDesktopData); + } else { + _boxDesktopData = await Hive.openBox(boxNameDesktopData); + } + _boxNotifications = await Hive.openBox(boxNameNotifications); _boxWatchedTransactions = @@ -147,11 +153,11 @@ class DB { _initialized = true; try { - if (_boxPrefs.get("familiarity") == null) { - await _boxPrefs.put("familiarity", 0); + if (_boxPrefs!.get("familiarity") == null) { + await _boxPrefs!.put("familiarity", 0); } - int count = _boxPrefs.get("familiarity") as int; - await _boxPrefs.put("familiarity", count + 1); + int count = _boxPrefs!.get("familiarity") as int; + await _boxPrefs!.put("familiarity", count + 1); Constants.exchangeForExperiencedUsers(count + 1); } catch (e, s) { print("$e $s"); @@ -160,7 +166,7 @@ class DB { } Future _loadWalletBoxes() async { - final names = _boxAllWalletsData.get("names") as Map? ?? {}; + final names = _boxAllWalletsData!.get("names") as Map? ?? {}; names.removeWhere((name, dyn) { final jsonObject = Map.from(dyn as Map); try { diff --git a/lib/main.dart b/lib/main.dart index a8b515b02..a04170bd9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -48,19 +48,16 @@ import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/notifications_service.dart'; import 'package:stackwallet/services/trade_service.dart'; -import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:window_size/window_size.dart'; final openedFromSWBFileStringStateProvider = @@ -221,8 +218,8 @@ class _MaterialAppWithThemeState extends ConsumerState static const platform = MethodChannel("STACK_WALLET_RESTORE"); final GlobalKey navigatorKey = GlobalKey(); - late final Wallets _wallets; - late final Prefs _prefs; + // late final Wallets _wallets; + // late final Prefs _prefs; late final NotificationsService _notificationsService; late final NodeService _nodeService; late final TradesService _tradesService; @@ -232,6 +229,16 @@ class _MaterialAppWithThemeState extends ConsumerState bool didLoad = false; bool _desktopHasPassword = false; + Future loadShared() async { + await DB.instance.init(); + await ref.read(prefsChangeNotifierProvider).init(); + + if (Util.isDesktop) { + _desktopHasPassword = + await ref.read(storageCryptoHandlerProvider).hasPassword(); + } + } + Future load() async { try { if (didLoad) { @@ -239,19 +246,15 @@ class _MaterialAppWithThemeState extends ConsumerState } didLoad = true; - await DB.instance.init(); - await _prefs.init(); - - if (Util.isDesktop) { - _desktopHasPassword = - await ref.read(storageCryptoHandlerProvider).hasPassword(); + if (!Util.isDesktop) { + await loadShared(); } _notificationsService = ref.read(notificationsProvider); _nodeService = ref.read(nodeServiceChangeNotifierProvider); _tradesService = ref.read(tradesServiceProvider); - NotificationApi.prefs = _prefs; + NotificationApi.prefs = ref.read(prefsChangeNotifierProvider); NotificationApi.notificationsService = _notificationsService; unawaited(ref.read(baseCurrenciesProvider).update()); @@ -260,23 +263,25 @@ class _MaterialAppWithThemeState extends ConsumerState await _notificationsService.init( nodeService: _nodeService, tradesService: _tradesService, - prefs: _prefs, + prefs: ref.read(prefsChangeNotifierProvider), ); ref.read(priceAnd24hChangeNotifierProvider).start(true); - await _wallets.load(_prefs); + await ref + .read(walletsChangeNotifierProvider) + .load(ref.read(prefsChangeNotifierProvider)); loadingCompleter.complete(); // TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet // unawaited(_nodeService.updateCommunityNodes()); // run without awaiting if (Constants.enableExchange && - _prefs.externalCalls && - await _prefs.isExternalCallsSet()) { + ref.read(prefsChangeNotifierProvider).externalCalls && + await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) { unawaited(ExchangeDataLoadingService().loadAll(ref)); } - if (_prefs.isAutoBackupEnabled) { - switch (_prefs.backupFrequencyType) { + if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) { + switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) { case BackupFrequencyType.everyTenMinutes: ref.read(autoSWBServiceProvider).startPeriodicBackupTimer( duration: const Duration(minutes: 10)); @@ -316,9 +321,6 @@ class _MaterialAppWithThemeState extends ConsumerState .read(localeServiceChangeNotifierProvider.notifier) .loadLocale(notify: false); - _prefs = ref.read(prefsChangeNotifierProvider); - _wallets = ref.read(walletsChangeNotifierProvider); - WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(colorThemeProvider.state).state = StackColors.fromStackColorTheme( @@ -423,7 +425,7 @@ class _MaterialAppWithThemeState extends ConsumerState } Future goToRestoreSWB(String encrypted) async { - if (!_prefs.hasPin) { + if (!ref.read(prefsChangeNotifierProvider).hasPin) { await Navigator.of(navigatorKey.currentContext!) .pushNamed(CreatePinView.routeName, arguments: true) .then((value) { @@ -569,57 +571,70 @@ class _MaterialAppWithThemeState extends ConsumerState _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), ), ), - home: ConditionalParent( - condition: Util.isDesktop, - builder: (child) { - return child; - }, - child: FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - // FlutterNativeSplash.remove(); - if (Util.isDesktop && - (_wallets.hasWallets || _desktopHasPassword)) { - String? startupWalletId; - if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; + home: Util.isDesktop + ? FutureBuilder( + future: loadShared(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (_desktopHasPassword) { + String? startupWalletId; + if (ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup) { + startupWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; + } + + return DesktopLoginView( + startupWalletId: startupWalletId, + load: load, + ); + } else { + return const IntroView(); + } + } else { + return const LoadingView(); } + }, + ) + : FutureBuilder( + future: load(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // FlutterNativeSplash.remove(); + if (ref.read(walletsChangeNotifierProvider).hasWallets || + ref.read(prefsChangeNotifierProvider).hasPin) { + // return HomeView(); - return DesktopLoginView(startupWalletId: startupWalletId); - } else if (!Util.isDesktop && - (_wallets.hasWallets || _prefs.hasPin)) { - // return HomeView(); + String? startupWalletId; + if (ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup) { + startupWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; + } - String? startupWalletId; - if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; + return LockscreenView( + isInitialAppLogin: true, + routeOnSuccess: HomeView.routeName, + routeOnSuccessArguments: startupWalletId, + biometricsAuthenticationTitle: "Unlock Stack", + biometricsLocalizedReason: + "Unlock your stack wallet using biometrics", + biometricsCancelButtonString: "Cancel", + ); + } else { + return const IntroView(); + } + } else { + // CURRENTLY DISABLED as cannot be animated + // technically not needed as FlutterNativeSplash will overlay + // anything returned here until the future completes but + // FutureBuilder requires you to return something + return const LoadingView(); } - - return LockscreenView( - isInitialAppLogin: true, - routeOnSuccess: HomeView.routeName, - routeOnSuccessArguments: startupWalletId, - biometricsAuthenticationTitle: "Unlock Stack", - biometricsLocalizedReason: - "Unlock your stack wallet using biometrics", - biometricsCancelButtonString: "Cancel", - ); - } else { - return const IntroView(); - } - } else { - // CURRENTLY DISABLED as cannot be animated - // technically not needed as FlutterNativeSplash will overlay - // anything returned here until the future completes but - // FutureBuilder requires you to return something - return const LoadingView(); - } - }, - ), - ), + }, + ), ); } } diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index 0a47c2a96..d90e742fb 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -21,11 +21,13 @@ class DesktopLoginView extends ConsumerStatefulWidget { const DesktopLoginView({ Key? key, this.startupWalletId, + this.load, }) : super(key: key); static const String routeName = "/desktopLogin"; final String? startupWalletId; + final Future Function()? load; @override ConsumerState createState() => _DesktopLoginViewState(); @@ -39,6 +41,32 @@ class _DesktopLoginViewState extends ConsumerState { bool hidePassword = true; bool _continueEnabled = false; + Future login() async { + try { + await ref + .read(storageCryptoHandlerProvider) + .initFromExisting(passwordController.text); + + await widget.load?.call(); + + // if no errors passphrase is correct + if (mounted) { + unawaited( + Navigator.of(context).pushNamedAndRemoveUntil( + DesktopHomeView.routeName, + (route) => false, + ), + ); + } + } catch (e) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ); + } + } + @override void initState() { passwordController = TextEditingController(); @@ -159,29 +187,7 @@ class _DesktopLoginViewState extends ConsumerState { PrimaryButton( label: "Continue", enabled: _continueEnabled, - onPressed: () async { - try { - await ref - .read(storageCryptoHandlerProvider) - .initFromExisting(passwordController.text); - - // if no errors passphrase is correct - if (mounted) { - unawaited( - Navigator.of(context).pushNamedAndRemoveUntil( - DesktopHomeView.routeName, - (route) => false, - ), - ); - } - } catch (e) { - await showFloatingFlushBar( - type: FlushBarType.warning, - message: e.toString(), - context: context, - ); - } - }, + onPressed: login, ), const SizedBox( height: 60, diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index e804071cc..2583e16c5 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -5,13 +5,13 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; @@ -35,13 +35,8 @@ import 'package:zxcvbn/zxcvbn.dart'; class CreateAutoBackup extends ConsumerStatefulWidget { const CreateAutoBackup({ Key? key, - this.secureStore = const SecureStorageWrapper( - FlutterSecureStorage(), - ), }) : super(key: key); - final FlutterSecureStorageInterface secureStore; - @override ConsumerState createState() => _CreateAutoBackup(); } @@ -51,7 +46,7 @@ class _CreateAutoBackup extends ConsumerState { late final TextEditingController passphraseController; late final TextEditingController passphraseRepeatController; - late final FlutterSecureStorageInterface secureStore; + late final SecureStorageInterface secureStore; late final StackFileSystem stackFileSystem; late final FocusNode passphraseFocusNode; @@ -85,7 +80,7 @@ class _CreateAutoBackup extends ConsumerState { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); @@ -686,7 +681,9 @@ class _CreateAutoBackup extends ConsumerState { final String fileToSave = createAutoBackupFilename(pathToSave, now); - final backup = await SWB.createStackWalletJSON(); + final backup = await SWB.createStackWalletJSON( + secureStorage: secureStore, + ); bool result = await SWB.encryptStackWalletWithADK( fileToSave, diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index 178871e69..24299b855 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -89,12 +89,10 @@ class DPS { } Future hasPassword() async { - final box = await Hive.openBox(DB.boxNameDesktopData); final keyBlob = DB.instance.get( boxName: DB.boxNameDesktopData, key: _kKeyBlobKey, ); - await box.close(); return keyBlob != null; } } From 3299f4ecd910c43e291bda713859db71d530615f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 15:07:44 -0600 Subject: [PATCH 009/225] comment out broken code in swb desktop restore + clean up unused imports and linter errors in wow/monero tests --- .../coins/monero/monero_wallet_test.dart | 57 ++++++++----------- .../coins/wownero/wownero_wallet_test.dart | 53 ++++++++--------- 2 files changed, 46 insertions(+), 64 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index d54959ab2..7bbc88ed9 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -1,43 +1,27 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_monero/api/wallet.dart'; -import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; -import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:cw_monero/monero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/monero/monero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - import 'package:stackwallet/services/wallets.dart'; - -import 'dart:developer' as developer; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // TODO trim down to the minimum imports above @@ -76,24 +60,29 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - monero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero.createMoneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Mainnet tests", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = monero + .createMoneroWalletService(_walletInfoSource as Box); + } + try { // if (name?.isEmpty ?? true) { // name = await generateName(); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 660bc1438..c58654b4b 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -1,38 +1,26 @@ -import 'dart:async'; import 'dart:core'; import 'dart:core' as core; import 'dart:io'; import 'dart:math'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/node.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_wownero/api/wallet.dart'; -import 'package:cw_wownero/pending_wownero_transaction.dart'; import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/view_model/send/output.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'wownero_wallet_test_data.dart'; @@ -67,24 +55,29 @@ void main() async { dirPath: ''); late WalletCredentials credentials; - WidgetsFlutterBinding.ensureInitialized(); - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - await Hive.close(); - Hive.init(appDir.path); - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - wownero.onStartup(); - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero.createWowneroWalletService(_walletInfoSource); + + bool hiveAdaptersRegistered = false; group("Wownero 14 word seed generation", () { setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + Hive.registerAdapter(NodeAdapter()); + Hive.registerAdapter(WalletInfoAdapter()); + Hive.registerAdapter(WalletTypeAdapter()); + Hive.registerAdapter(UnspentCoinsInfoAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', name); + + _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + walletService = wownero + .createWowneroWalletService(_walletInfoSource as Box); + } + bool hasThrown = false; try { name = 'namee${Random().nextInt(10000000)}'; From 7105deeb24b49800d98527cf403e0cca224c5eec Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 10 Nov 2022 15:40:23 -0600 Subject: [PATCH 010/225] initialize isar instance correctly for desktop secure wrapper --- .../desktop_login_view.dart | 4 ++++ .../flutter_secure_storage_interface.dart | 21 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index d90e742fb..93a281bf8 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -7,9 +7,11 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -47,6 +49,8 @@ class _DesktopLoginViewState extends ConsumerState { .read(storageCryptoHandlerProvider) .initFromExisting(passwordController.text); + await (ref.read(secureStoreProvider).store as DesktopSecureStore).init(); + await widget.load?.call(); // if no errors passphrase is correct diff --git a/lib/utilities/flutter_secure_storage_interface.dart b/lib/utilities/flutter_secure_storage_interface.dart index 852c13f3d..9e8aef95c 100644 --- a/lib/utilities/flutter_secure_storage_interface.dart +++ b/lib/utilities/flutter_secure_storage_interface.dart @@ -6,6 +6,8 @@ import 'package:stack_wallet_backup/secure_storage.dart'; import 'package:stackwallet/models/isar/models/encrypted_string_value.dart'; abstract class SecureStorageInterface { + dynamic get store; + Future write({ required String key, required String? value, @@ -54,6 +56,7 @@ class DesktopSecureStore { [EncryptedStringValueSchema], directory: appDirectory!.path, inspector: false, + name: "desktopStore", ); } @@ -77,7 +80,9 @@ class DesktopSecureStore { }) async { if (value == null) { // here we assume that a value is to be deleted - await isar.encryptedStringValues.deleteByKey(key); + await isar.writeTxn(() async { + await isar.encryptedStringValues.deleteByKey(key); + }); } else { // otherwise created encrypted object value final object = EncryptedStringValue(); @@ -85,14 +90,18 @@ class DesktopSecureStore { object.value = await handler.encryptValue(key, value); // store object value - await isar.encryptedStringValues.put(object); + await isar.writeTxn(() async { + await isar.encryptedStringValues.put(object); + }); } } Future delete({ required String key, }) async { - await isar.encryptedStringValues.deleteByKey(key); + await isar.writeTxn(() async { + await isar.encryptedStringValues.deleteByKey(key); + }); } } @@ -101,6 +110,9 @@ class SecureStorageWrapper implements SecureStorageInterface { final dynamic _store; final bool _isDesktop; + @override + dynamic get store => _store; + const SecureStorageWrapper({ required dynamic store, required bool isDesktop, @@ -245,4 +257,7 @@ class FakeSecureStorage implements SecureStorageInterface { _deletes++; _store.remove(key); } + + @override + dynamic get store => throw UnimplementedError(); } From dac1337a712525727361c3c2bfdbf7698877a2a0 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 10 Nov 2022 17:49:49 -0700 Subject: [PATCH 011/225] WIP: added border to cards; working on cancel restore --- .../restore_from_file_view.dart | 30 +- .../stack_restore_progress_view.dart | 539 +++++++++++++----- .../sub_widgets/restoring_wallet_card.dart | 398 +++++++++---- 3 files changed, 668 insertions(+), 299 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index ee1fcf666..e2d16db54 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -9,7 +9,6 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; -// import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -21,13 +20,14 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; -import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:tuple/tuple.dart'; +import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; + class RestoreFromFileView extends ConsumerStatefulWidget { const RestoreFromFileView({Key? key}) : super(key: key); @@ -48,17 +48,6 @@ class _RestoreFromFileViewState extends ConsumerState { bool hidePassword = true; - Future restoreBackupPopup(BuildContext context) async { - // await showDialog( - // context: context, - // useSafeArea: false, - // barrierDismissible: true, - // builder: (context) { - // return const RestoreBackupDialog(); - // }, - // ); - } - @override void initState() { stackFileSystem = StackFileSystem(); @@ -237,7 +226,7 @@ class _RestoreFromFileViewState extends ConsumerState { enableSuggestions: false, autocorrect: false, decoration: standardInputDecoration( - "Enter password", + "Enter passphrase", passwordFocusNode, context, ).copyWith( @@ -534,7 +523,7 @@ class _RestoreFromFileViewState extends ConsumerState { const EdgeInsets .all(32), child: Text( - "Restoring Stack Wallet", + "Restore Stack Wallet", style: STextStyles .desktopH3( context), @@ -546,12 +535,10 @@ class _RestoreFromFileViewState extends ConsumerState { const DesktopDialogCloseButton(), ], ), - const SizedBox( - height: 30, - ), Padding( - padding: EdgeInsets - .symmetric( + padding: + const EdgeInsets + .symmetric( horizontal: 32), child: @@ -560,6 +547,9 @@ class _RestoreFromFileViewState extends ConsumerState { jsonString, ), ), + const SizedBox( + height: 32, + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 7dec4e740..e7c65346f 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -19,10 +19,13 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; +import '../../../../../widgets/desktop/primary_button.dart'; + class StackRestoreProgressView extends ConsumerStatefulWidget { const StackRestoreProgressView({ Key? key, @@ -100,6 +103,30 @@ class _StackRestoreProgressViewState context: context, builder: (_) => const CancelStackRestoreDialog(), ); + // : await Row( + // children: [ + // SecondaryButton( + // width: 248, + // desktopMed: true, + // enabled: true, + // label: "Keep restoring", + // onPressed: () { + // false; + // }, + // ), + // const SizedBox(width: 16), + // PrimaryButton( + // width: 248, + // desktopMed: true, + // enabled: true, + // label: "Cancel anyway", + // onPressed: () { + // true; + // }, + // ) + // ], + // ); + if (result is bool && result) { return true; } @@ -128,6 +155,7 @@ class _StackRestoreProgressViewState } bool _success = false; + bool pendingCancel = false; Future _onWillPop() async { if (_success) { @@ -239,7 +267,7 @@ class _StackRestoreProgressViewState left: 4, top: 4, right: 4, - bottom: 0, + bottom: 4, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -255,40 +283,84 @@ class _StackRestoreProgressViewState builder: (_, ref, __) { final state = ref.watch(stackRestoringUIStateProvider .select((value) => value.preferences)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.gear, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.gear, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Preferences", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Preferences", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .popupBG, + borderColor: Theme.of(context) + .extension()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.gear, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Preferences", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ), + ); }, ), const SizedBox( @@ -298,39 +370,82 @@ class _StackRestoreProgressViewState builder: (_, ref, __) { final state = ref.watch(stackRestoringUIStateProvider .select((value) => value.addressBook)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: AddressBookIcon( - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: AddressBookIcon( + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Address book", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Address book", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .popupBG, + borderColor: Theme.of(context) + .extension()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: AddressBookIcon( + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Address book", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ), + ); }, ), const SizedBox( @@ -340,40 +455,83 @@ class _StackRestoreProgressViewState builder: (_, ref, __) { final state = ref.watch(stackRestoringUIStateProvider .select((value) => value.nodes)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.node, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.node, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Nodes", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Nodes", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .popupBG, + borderColor: Theme.of(context) + .extension()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.node, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Nodes", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + )); }, ), const SizedBox( @@ -383,40 +541,86 @@ class _StackRestoreProgressViewState builder: (_, ref, __) { final state = ref.watch(stackRestoringUIStateProvider .select((value) => value.trades)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, + return !isDesktop + ? Container( + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowRotate2, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Exchange history", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, ), - ), - ), - ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Exchange history", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, - ); + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .popupBG, + borderColor: Theme.of(context) + .extension()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowRotate2, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Exchange history", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, + ), + ); }, ), const SizedBox( @@ -446,28 +650,55 @@ class _StackRestoreProgressViewState ), SizedBox( width: MediaQuery.of(context).size.width - 32, - child: TextButton( - onPressed: () async { - if (_success) { - Navigator.of(context).pop(); - } else { - if (await _requestCancel()) { - await _cancel(); - } - } - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - _success ? "OK" : "Cancel restore process", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextPrimary, - ), - ), - ), + child: !isDesktop + ? TextButton( + onPressed: () async { + if (_success) { + Navigator.of(context).pop(); + } else { + if (await _requestCancel()) { + await _cancel(); + } + } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + _success ? "OK" : "Cancel restore process", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _success + ? PrimaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Done", + onPressed: () async { + Navigator.of(context).pop(); + }, + ) + : SecondaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Cancel restore process", + onPressed: () async { + if (await _requestCancel()) { + await _cancel(); + } + }, + ), + ], + ), ), ], ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart index 55f0588d2..2239eee71 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/stack_restoring_status.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; @@ -68,140 +69,287 @@ class _RestoringWalletCardState extends ConsumerState { final coin = ref.watch(provider.select((value) => value.coin)); final restoringStatus = ref.watch(provider.select((value) => value.restoringState)); - return RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context).extension()!.colorForCoin(coin), - child: Center( - child: SvgPicture.asset( - Assets.svg.iconFor( - coin: coin, - ), - height: 20, - width: 20, - ), - ), - ), - ), - onRightTapped: restoringStatus == StackRestoringStatus.failed - ? () async { - final manager = ref.read(provider).manager!; - - ref.read(stackRestoringUIStateProvider).update( - walletId: manager.walletId, - restoringStatus: StackRestoringStatus.restoring); - - try { - final mnemonicList = await manager.mnemonic; - int maxUnusedAddressGap = 20; - if (coin == Coin.firo) { - maxUnusedAddressGap = 50; - } - const maxNumberOfIndexesToCheck = 1000; - - if (mnemonicList.isEmpty) { - await manager.recoverFromMnemonic( - mnemonic: ref.read(provider).mnemonic!, - maxUnusedAddressGap: maxUnusedAddressGap, - maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, - height: ref.read(provider).height ?? 0, - ); - } else { - await manager.fullRescan( - maxUnusedAddressGap, - maxNumberOfIndexesToCheck, - ); - } - - if (mounted) { - final address = await manager.currentReceivingAddress; - - ref.read(stackRestoringUIStateProvider).update( - walletId: manager.walletId, - restoringStatus: StackRestoringStatus.success, - address: address, - ); - } - } catch (_) { - if (mounted) { - ref.read(stackRestoringUIStateProvider).update( - walletId: manager.walletId, - restoringStatus: StackRestoringStatus.failed, - ); - } - } - } - : null, - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState( - ref.watch(provider.select((value) => value.restoringState)), - ), - ), - title: - "${ref.watch(provider.select((value) => value.walletName))} (${coin.ticker})", - subTitle: restoringStatus == StackRestoringStatus.failed - ? Text( - "Unable to restore. Tap icon to retry.", - style: STextStyles.errorSmall(context), - ) - : ref.watch(provider.select((value) => value.address)) != null - ? Text( - ref.watch(provider.select((value) => value.address))!, - style: STextStyles.infoSmall(context), - ) - : null, - button: restoringStatus == StackRestoringStatus.failed - ? Container( - height: 20, - decoration: BoxDecoration( + return !Util.isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), color: Theme.of(context) .extension()! - .buttonBackSecondary, - borderRadius: BorderRadius.circular( - 1000, - ), - ), - child: RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, + .colorForCoin(coin), + child: Center( + child: SvgPicture.asset( + Assets.svg.iconFor( + coin: coin, + ), + height: 20, + width: 20, ), ), - onPressed: () async { - final mnemonic = ref.read(provider).mnemonic; + ), + ), + onRightTapped: restoringStatus == StackRestoringStatus.failed + ? () async { + final manager = ref.read(provider).manager!; - if (mnemonic != null) { - Navigator.of(context).push( - RouteGenerator.getRoute( - builder: (_) => RecoverPhraseView( - walletName: ref.read(provider).walletName, - mnemonic: mnemonic.split(" "), + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.restoring); + + try { + final mnemonicList = await manager.mnemonic; + int maxUnusedAddressGap = 20; + if (coin == Coin.firo) { + maxUnusedAddressGap = 50; + } + const maxNumberOfIndexesToCheck = 1000; + + if (mnemonicList.isEmpty) { + await manager.recoverFromMnemonic( + mnemonic: ref.read(provider).mnemonic!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + height: ref.read(provider).height ?? 0, + ); + } else { + await manager.fullRescan( + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + ); + } + + if (mounted) { + final address = await manager.currentReceivingAddress; + + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.success, + address: address, + ); + } + } catch (_) { + if (mounted) { + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.failed, + ); + } + } + } + : null, + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState( + ref.watch(provider.select((value) => value.restoringState)), + ), + ), + title: + "${ref.watch(provider.select((value) => value.walletName))} (${coin.ticker})", + subTitle: restoringStatus == StackRestoringStatus.failed + ? Text( + "Unable to restore. Tap icon to retry.", + style: STextStyles.errorSmall(context), + ) + : ref.watch(provider.select((value) => value.address)) != null + ? Text( + ref.watch(provider.select((value) => value.address))!, + style: STextStyles.infoSmall(context), + ) + : null, + button: restoringStatus == StackRestoringStatus.failed + ? Container( + height: 20, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + splashColor: + Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, ), ), - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Text( - "Show recovery phrase", - style: STextStyles.infoSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + onPressed: () async { + final mnemonic = ref.read(provider).mnemonic; + + if (mnemonic != null) { + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => RecoverPhraseView( + walletName: ref.read(provider).walletName, + mnemonic: mnemonic.split(" "), + ), + ), + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "Show recovery phrase", + style: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ) + : null, + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context).extension()!.popupBG, + borderColor: Theme.of(context).extension()!.background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .colorForCoin(coin), + child: Center( + child: SvgPicture.asset( + Assets.svg.iconFor( + coin: coin, + ), + height: 20, + width: 20, + ), ), ), ), - ) - : null, - ); + onRightTapped: restoringStatus == StackRestoringStatus.failed + ? () async { + final manager = ref.read(provider).manager!; + + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.restoring); + + try { + final mnemonicList = await manager.mnemonic; + int maxUnusedAddressGap = 20; + if (coin == Coin.firo) { + maxUnusedAddressGap = 50; + } + const maxNumberOfIndexesToCheck = 1000; + + if (mnemonicList.isEmpty) { + await manager.recoverFromMnemonic( + mnemonic: ref.read(provider).mnemonic!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: + maxNumberOfIndexesToCheck, + height: ref.read(provider).height ?? 0, + ); + } else { + await manager.fullRescan( + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + ); + } + + if (mounted) { + final address = await manager.currentReceivingAddress; + + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.success, + address: address, + ); + } + } catch (_) { + if (mounted) { + ref.read(stackRestoringUIStateProvider).update( + walletId: manager.walletId, + restoringStatus: StackRestoringStatus.failed, + ); + } + } + } + : null, + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState( + ref.watch(provider.select((value) => value.restoringState)), + ), + ), + title: + "${ref.watch(provider.select((value) => value.walletName))} (${coin.ticker})", + subTitle: restoringStatus == StackRestoringStatus.failed + ? Text( + "Unable to restore. Tap icon to retry.", + style: STextStyles.errorSmall(context), + ) + : ref.watch(provider.select((value) => value.address)) != null + ? Text( + ref.watch(provider.select((value) => value.address))!, + style: STextStyles.infoSmall(context), + ) + : null, + button: restoringStatus == StackRestoringStatus.failed + ? Container( + height: 20, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + splashColor: Theme.of(context) + .extension()! + .highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () async { + final mnemonic = ref.read(provider).mnemonic; + + if (mnemonic != null) { + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => RecoverPhraseView( + walletName: ref.read(provider).walletName, + mnemonic: mnemonic.split(" "), + ), + ), + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "Show recovery phrase", + style: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ) + : null, + ), + ); } } From de896d30bcfa87afb74a12bd6d2afe2c9aea141d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 10 Nov 2022 22:14:08 -0700 Subject: [PATCH 012/225] desktop dialog for disable auto backup --- .../backup_and_restore_settings.dart | 135 +++++++++++++----- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 3ea6cea6c..0df7c8975 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -14,7 +14,9 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.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/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -108,42 +110,103 @@ class _BackupRestoreSettings extends ConsumerState { useSafeArea: false, barrierDismissible: true, builder: (context) { - return StackDialog( - title: "Disable Auto Backup", - message: - "You are turning off Auto Backup. You can turn it back on at any time. Your previous Auto Backup file will not be deleted. Remember to backup your wallets manually so you don't lose important information.", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.button(context).copyWith( - color: - Theme.of(context).extension()!.accentColorDark, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Disable", - style: STextStyles.button(context), - ), - onPressed: () { - Navigator.of(context).pop(); - setState(() { - ref.watch(prefsChangeNotifierProvider).isAutoBackupEnabled = - false; - }); - }, - ), - ); + return !Util.isDesktop + ? StackDialog( + title: "Disable Auto Backup", + message: + "You are turning off Auto Backup. You can turn it back on at any time. Your previous Auto Backup file will not be deleted. Remember to backup your wallets manually so you don't lose important information.", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Disable", + style: STextStyles.button(context), + ), + onPressed: () { + Navigator.of(context).pop(); + setState(() { + ref + .watch(prefsChangeNotifierProvider) + .isAutoBackupEnabled = false; + }); + }, + ), + ) + : DesktopDialog( + maxHeight: 270, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 32), + child: Column( + children: [ + Text( + "Disable Auto Backup", + style: STextStyles.desktopH3(context), + ), + const SizedBox(height: 24), + SizedBox( + width: 600, + child: Text( + "You are turning off Auto Backup. You can turn it back on at any time. " + "Your previous Auto Backup file will not be deleted. Remember to backup your wallets " + "manually so you don't lose important information.", + style: STextStyles.desktopTextSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Back", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Disable", + onPressed: () { + Navigator.of(context).pop(); + setState(() { + ref + .watch(prefsChangeNotifierProvider) + .isAutoBackupEnabled = false; + }); + }, + ) + ], + ), + ], + ), + ), + ); }, ); if (mounted) { From 7a0ef9685188231eb8814c9e983e989b22cfc1f4 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 10 Nov 2022 22:14:47 -0700 Subject: [PATCH 013/225] desktop dialog for cancel stack restore --- .../dialogs/cancel_stack_restore_dialog.dart | 124 +++++++++++++----- 1 file changed, 93 insertions(+), 31 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart index 16e51a35d..a9f4e134d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class CancelStackRestoreDialog extends StatelessWidget { @@ -14,38 +19,95 @@ class CancelStackRestoreDialog extends StatelessWidget { onWillPop: () async { return false; }, - child: StackDialog( - title: "Cancel restore process", - message: - "Cancelling will revert any changes that may have been applied", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Yes, cancel", - style: STextStyles.itemSubtitle12(context).copyWith( - color: - Theme.of(context).extension()!.buttonTextPrimary, + child: !Util.isDesktop + ? StackDialog( + title: "Cancel restore process", + message: + "Cancelling will revert any changes that may have been applied", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Yes, cancel", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ) + : DesktopDialog( + maxHeight: 250, + maxWidth: 600, + child: Padding( + padding: const EdgeInsets.only( + top: 20, left: 32, right: 32, bottom: 20), + child: Column( + children: [ + Text( + "Cancel Restore Process", + style: STextStyles.desktopH3(context), + ), + const SizedBox(height: 24), + SizedBox( + width: 500, + child: RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackError, + child: Text( + "If you cancel, the restore will not complete, and " + "the wallets will not appear in your Stack.", + style: STextStyles.desktopTextMedium(context), + ), + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Keep restoring", + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Cancel anyway", + onPressed: () { + Navigator.of(context).pop(true); + }, + ) + ], + ), + ], + ), + ), ), - ), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - ), ); } } From 676b26ce3793d4abc697052ac781b15c5ddb8dfb Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 09:30:13 -0600 Subject: [PATCH 014/225] stop logging annoying monero sync non error --- lib/services/coins/monero/monero_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 91bb3c4db..cf0335197 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -196,7 +196,7 @@ class MoneroWallet extends CoinServiceAPI { try { syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } final cachedHeight = DB.instance.get(boxName: walletId, key: "storedSyncingHeight") From ca8f63c07a28ae77cb02a15c4427fbd066e8f2f2 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 09:31:01 -0600 Subject: [PATCH 015/225] ensure loadShared is only called once --- lib/main.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index a04170bd9..df9d683f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -227,9 +227,15 @@ class _MaterialAppWithThemeState extends ConsumerState late final Completer loadingCompleter; bool didLoad = false; + bool didLoadShared = false; bool _desktopHasPassword = false; Future loadShared() async { + if (didLoadShared) { + return; + } + didLoadShared = true; + await DB.instance.init(); await ref.read(prefsChangeNotifierProvider).init(); From b6613b2fd7eaeda927374ec3be4d34853dc375d4 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 09:32:55 -0600 Subject: [PATCH 016/225] stop logging monero sync non-error --- lib/services/coins/monero/monero_wallet.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index cf0335197..d6c238df0 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -154,7 +154,7 @@ class MoneroWallet extends CoinServiceAPI { try { _height = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int blocksRemaining = -1; @@ -163,7 +163,7 @@ class MoneroWallet extends CoinServiceAPI { blocksRemaining = (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int currentHeight = _height + blocksRemaining; if (_height == -1 || blocksRemaining == -1) { @@ -419,7 +419,7 @@ class MoneroWallet extends CoinServiceAPI { try { progress = (walletBase!.syncStatus!).progress(); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } await _fetchTransactionData(); From ba853837ce3f7b3e1877d8da6a53f4bbbd01749b Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 10:45:50 -0600 Subject: [PATCH 017/225] verify passphrase functionality added to password service --- lib/utilities/desktop_password_service.dart | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index 24299b855..f20525873 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -88,6 +88,33 @@ class DPS { } } + Future verifyPassphrase(String passphrase) async { + final box = await Hive.openBox(DB.boxNameDesktopData); + final keyBlob = DB.instance.get( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + ); + await box.close(); + + if (keyBlob == null) { + // no passphrase key blob found so any passphrase is technically bad + return false; + } + + try { + await StorageCryptoHandler.fromExisting(passphrase, keyBlob); + // existing passphrase matches key blob + return true; + } catch (e, s) { + Logging.instance.log( + "${_getMessageFromException(e)}\n$s", + level: LogLevel.Warning, + ); + // password is wrong or some other error + return false; + } + } + Future hasPassword() async { final keyBlob = DB.instance.get( boxName: DB.boxNameDesktopData, From 9b09f65f4d44f3b0d8c1829eb5640c8e4eec33d4 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 12:12:01 -0600 Subject: [PATCH 018/225] remove flutter secure storage explicit instantiations from wow/xmr --- lib/main.dart | 1 + lib/services/coins/monero/monero_wallet.dart | 20 ++++++++----------- .../coins/wownero/wownero_wallet.dart | 20 +++++++------------ 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index df9d683f5..70f80e8ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -154,6 +154,7 @@ void main() async { await Hive.openBox(DB.boxNameDBInfo); + // todo: db migrate stuff for desktop needs to be handled eventually if (!Util.isDesktop) { int dbVersion = DB.instance.get( boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index d6c238df0..9e9ffc783 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -23,7 +23,6 @@ import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; @@ -670,11 +669,10 @@ class MoneroWallet extends CoinServiceAPI { "Attempted to overwrite mnemonic on generate new wallet!"); } - storage = const FlutterSecureStorage(); walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; try { @@ -708,7 +706,7 @@ class MoneroWallet extends CoinServiceAPI { credentials.walletInfo = walletInfo; _walletCreationService = WalletCreationService( - secureStorage: storage, + secureStorage: _secureStore, sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, @@ -787,11 +785,11 @@ class MoneroWallet extends CoinServiceAPI { // Logging.instance.log("Caught in initializeWallet(): $e\n$s"); // return false; // } - storage = const FlutterSecureStorage(); + walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); await _generateNewWallet(); // var password; @@ -833,11 +831,10 @@ class MoneroWallet extends CoinServiceAPI { "Attempted to initialize an existing wallet using an unknown wallet ID!"); } - storage = const FlutterSecureStorage(); walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); await _prefs.init(); final data = @@ -889,7 +886,7 @@ class MoneroWallet extends CoinServiceAPI { bool longMutex = false; // TODO: are these needed? - FlutterSecureStorage? storage; + WalletService? walletService; SharedPreferences? prefs; KeyService? keysStorage; @@ -970,11 +967,10 @@ class MoneroWallet extends CoinServiceAPI { await DB.instance .put(boxName: walletId, key: "restoreHeight", value: height); - storage = const FlutterSecureStorage(); walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; String name = _walletId; @@ -1001,7 +997,7 @@ class MoneroWallet extends CoinServiceAPI { credentials.walletInfo = walletInfo; _walletCreationService = WalletCreationService( - secureStorage: storage, + secureStorage: _secureStore, sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index c872c7d7e..39d3038ee 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:io'; -import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -25,7 +24,6 @@ import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/view_model/send/output.dart' as wownero_output; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; @@ -672,12 +670,11 @@ class WowneroWallet extends CoinServiceAPI { "Attempted to overwrite mnemonic on generate new wallet!"); } - storage = const FlutterSecureStorage(); // TODO: Wallet Service may need to be switched to Wownero walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; try { @@ -702,7 +699,7 @@ class WowneroWallet extends CoinServiceAPI { credentials.walletInfo = walletInfo; _walletCreationService = WalletCreationService( - secureStorage: storage, + secureStorage: _secureStore, sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, @@ -793,11 +790,10 @@ class WowneroWallet extends CoinServiceAPI { // Logging.instance.log("Caught in initializeWallet(): $e\n$s"); // return false; // } - storage = const FlutterSecureStorage(); walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); await _generateNewWallet(seedWordsLength: seedWordsLength); // var password; @@ -839,11 +835,10 @@ class WowneroWallet extends CoinServiceAPI { "Attempted to initialize an existing wallet using an unknown wallet ID!"); } - storage = const FlutterSecureStorage(); walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); await _prefs.init(); final data = @@ -895,7 +890,7 @@ class WowneroWallet extends CoinServiceAPI { bool longMutex = false; // TODO: are these needed? - FlutterSecureStorage? storage; + WalletService? walletService; SharedPreferences? prefs; KeyService? keysStorage; @@ -993,11 +988,10 @@ class WowneroWallet extends CoinServiceAPI { await DB.instance .put(boxName: walletId, key: "restoreHeight", value: height); - storage = const FlutterSecureStorage(); walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); prefs = await SharedPreferences.getInstance(); - keysStorage = KeyService(storage!); + keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; String name = _walletId; @@ -1024,7 +1018,7 @@ class WowneroWallet extends CoinServiceAPI { credentials.walletInfo = walletInfo; _walletCreationService = WalletCreationService( - secureStorage: storage, + secureStorage: _secureStore, sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, From f7bf624028462d4ec4958e1edb05331f12910b65 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 11 Nov 2022 12:37:44 -0600 Subject: [PATCH 019/225] initialize store on successful password creation --- .../create_password/create_password_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index d5ce42679..c29fc3de6 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -6,9 +6,11 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -78,6 +80,7 @@ class _CreatePasswordViewState extends ConsumerState { } await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase); + await (ref.read(secureStoreProvider).store as DesktopSecureStore).init(); } catch (e) { unawaited(showFloatingFlushBar( type: FlushBarType.warning, From 6ce899cd27348cb682e721295b00f2bc1f5c5dfa Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 11 Nov 2022 12:27:39 -0700 Subject: [PATCH 020/225] textfields for desktop change password --- .../home/settings_menu/security_settings.dart | 373 ++++++++++++++++-- 1 file changed, 333 insertions(+), 40 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index cdcaed49a..40928a0af 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -2,11 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; import 'package:stackwallet/utilities/assets.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/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:zxcvbn/zxcvbn.dart'; class SecuritySettings extends ConsumerStatefulWidget { const SecuritySettings({Key? key}) : super(key: key); @@ -18,21 +21,55 @@ class SecuritySettings extends ConsumerStatefulWidget { } class _SecuritySettings extends ConsumerState { - Future enableAutoBackup() async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); + late bool changePassword = false; - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return EnableBackupDialog(); - }, - ); + late final TextEditingController passwordCurrentController; + late final TextEditingController passwordController; + late final TextEditingController passwordRepeatController; + + late final FocusNode passwordCurrentFocusNode; + late final FocusNode passwordFocusNode; + late final FocusNode passwordRepeatFocusNode; + final zxcvbn = Zxcvbn(); + + bool hidePassword = true; + bool shouldShowPasswordHint = true; + + double passwordStrength = 0.0; + + bool get shouldEnableSave { + return passwordCurrentController.text.isNotEmpty && + passwordController.text.isNotEmpty && + passwordRepeatController.text.isNotEmpty; + } + + String passwordFeedback = + "Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters."; + + @override + void initState() { + passwordCurrentController = TextEditingController(); + passwordController = TextEditingController(); + passwordRepeatController = TextEditingController(); + + passwordCurrentFocusNode = FocusNode(); + passwordFocusNode = FocusNode(); + passwordRepeatFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordCurrentController.dispose(); + passwordController.dispose(); + passwordRepeatController.dispose(); + + passwordCurrentFocusNode.dispose(); + passwordFocusNode.dispose(); + passwordRepeatFocusNode.dispose(); + + super.dispose(); } @override @@ -78,12 +115,290 @@ class _SecuritySettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: NewPasswordButton(), + child: changePassword + ? SizedBox( + width: 512, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Current password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityRestoreFromFilePasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter current password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) {}, + ), + ), + const SizedBox(height: 16), + Text( + "New password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey1"), + focusNode: passwordCurrentFocusNode, + controller: passwordCurrentController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter new password", + passwordCurrentFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey1"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = + zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result + .feedback.suggestions! + .toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += + result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; + + passwordStrength = result.score! / 4; + + // hack fix to format back string returned from zxcvbn + if (feedback + .contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", + "phrases\nNo need"); + } + + if (feedback.endsWith("\n")) { + feedback = feedback.substring( + 0, feedback.length - 2); + } + + setState(() { + passwordFeedback = feedback; + }); + }, + ), + ), + const SizedBox(height: 16), + Text( + "Confirm new password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm new password", + passwordRepeatFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey2"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 20), + PrimaryButton( + width: 142, + desktopMed: true, + enabled: shouldEnableSave, + label: "Save changes", + onPressed: () { + setState(() { + changePassword = false; + }); + }, + ) + ], + ), + ) + : PrimaryButton( + width: 192, + desktopMed: true, + enabled: true, + label: "Set up new password", + onPressed: () { + setState(() { + changePassword = true; + }); + }, + ), ), ], ), @@ -110,29 +425,7 @@ class NewPasswordButton extends ConsumerWidget { style: Theme.of(context) .extension()! .getPrimaryEnabledButtonColor(context), - onPressed: () { - // Expandable( - // header: Row( - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // NewPasswordButton(), - // ], - // ), - // body: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // Text( - // "Current Password", - // style: STextStyles.desktopTextExtraSmall(context).copyWith( - // color: - // Theme.of(context).extension()!.textDark3, - // ), - // textAlign: TextAlign.left, - // ), - // ], - // ), - // ); - }, + onPressed: () {}, child: Text( "Set up new password", style: STextStyles.button(context), From d084fac0571e7ac464bf322213c2a0f53e3c303c Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 11 Nov 2022 12:42:04 -0700 Subject: [PATCH 021/225] password progressbar fix --- .../home/settings_menu/security_settings.dart | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index 40928a0af..9ee9b5bfc 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -7,6 +7,7 @@ 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/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:zxcvbn/zxcvbn.dart'; @@ -145,15 +146,15 @@ class _SecuritySettings extends ConsumerState { child: TextField( key: const Key( "desktopSecurityRestoreFromFilePasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, + focusNode: passwordCurrentFocusNode, + controller: passwordCurrentController, style: STextStyles.field(context), obscureText: hidePassword, enableSuggestions: false, autocorrect: false, decoration: standardInputDecoration( "Enter current password", - passwordFocusNode, + passwordCurrentFocusNode, context, ).copyWith( labelStyle: @@ -214,15 +215,15 @@ class _SecuritySettings extends ConsumerState { child: TextField( key: const Key( "desktopSecurityCreateNewPasswordFieldKey1"), - focusNode: passwordCurrentFocusNode, - controller: passwordCurrentController, + focusNode: passwordFocusNode, + controller: passwordController, style: STextStyles.field(context), obscureText: hidePassword, enableSuggestions: false, autocorrect: false, decoration: standardInputDecoration( "Enter new password", - passwordCurrentFocusNode, + passwordFocusNode, context, ).copyWith( labelStyle: @@ -302,6 +303,57 @@ class _SecuritySettings extends ConsumerState { }, ), ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: + passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall( + context), + ) + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key( + "desktopSecurityCreateStackBackUpProgressBar"), + width: 450, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 + ? 0.03 + : passwordStrength, + ), + ), const SizedBox(height: 16), Text( "Confirm new password", From 7798ed39a06912425fc6c97bfbb6a6d021f1ed04 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 11 Nov 2022 15:58:47 -0700 Subject: [PATCH 022/225] desktop address book with no contacts --- assets/svg/plus-circle.svg | 12 +++ .../desktop_address_book.dart | 84 ++++++++++++++++++- lib/utilities/assets.dart | 1 + pubspec.yaml | 1 + 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 assets/svg/plus-circle.svg diff --git a/assets/svg/plus-circle.svg b/assets/svg/plus-circle.svg new file mode 100644 index 000000000..e673b9b0e --- /dev/null +++ b/assets/svg/plus-circle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 3622fcf1e..dd38b98a8 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.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/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -24,6 +27,11 @@ class _DesktopAddressBook extends ConsumerState { late final FocusNode _searchFocusNode; + List? _cache; + List? _cacheFav; + + late bool hasContacts = false; + String filter = ""; @override @@ -49,6 +57,7 @@ class _DesktopAddressBook extends ConsumerState { return Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ DesktopAppBar( isCompactHeight: true, @@ -127,12 +136,81 @@ class _DesktopAddressBook extends ConsumerState { ), ), ), + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension()! + .getDesktopMenuButtonColorSelected(context), + onPressed: () {}, + child: SizedBox( + width: 200, + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SvgPicture.asset(Assets.svg.filter), + ), + Text( + "Filter", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + ), + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () {}, + child: SizedBox( + width: 200, + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SvgPicture.asset(Assets.svg.circlePlus), + ), + Text( + "Add new", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .popupBG, + ), + ), + ], + ), + ), + ), ], ), ), - // Expanded( - // child: hasWallets ? const MyWallets() : const EmptyWallets(), - // ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 26), + child: SizedBox( + width: 489, + child: RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ), + ), ], ); } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index f853a00d8..e76a17c12 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -59,6 +59,7 @@ class _SVG { String txExchangeFailed(BuildContext context) => "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; + String get circlePlus => "assets/svg/plus-circle.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; String get themeLight => "assets/svg/light/light-mode.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 8b03cd57e..fd1c8a9b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -314,6 +314,7 @@ flutter: - assets/svg/exit-desktop.svg - assets/svg/keys.svg - assets/svg/arrow-down.svg + - assets/svg/plus-circle.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Litecoin.svg From f08a52cd0748a2a085724b21d4f5c3aa926c27d3 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 12 Nov 2022 09:16:07 -0600 Subject: [PATCH 023/225] remove direct dependency of unused SharedPreferences --- lib/services/coins/monero/monero_wallet.dart | 8 --- .../coins/wownero/wownero_wallet.dart | 8 --- pubspec.lock | 2 +- pubspec.yaml | 2 +- .../coins/monero/monero_wallet_test.dart | 53 ++++++++++--------- .../coins/wownero/wownero_wallet_test.dart | 24 ++++----- 6 files changed, 42 insertions(+), 55 deletions(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index fd8f9a79e..caf1185a5 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -26,7 +26,6 @@ import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -671,7 +670,6 @@ class MoneroWallet extends CoinServiceAPI { walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; @@ -707,7 +705,6 @@ class MoneroWallet extends CoinServiceAPI { _walletCreationService = WalletCreationService( secureStorage: _secureStore, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -788,7 +785,6 @@ class MoneroWallet extends CoinServiceAPI { walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); await _generateNewWallet(); @@ -833,7 +829,6 @@ class MoneroWallet extends CoinServiceAPI { walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); await _prefs.init(); @@ -888,7 +883,6 @@ class MoneroWallet extends CoinServiceAPI { // TODO: are these needed? WalletService? walletService; - SharedPreferences? prefs; KeyService? keysStorage; MoneroWalletBase? walletBase; WalletCreationService? _walletCreationService; @@ -969,7 +963,6 @@ class MoneroWallet extends CoinServiceAPI { walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; @@ -998,7 +991,6 @@ class MoneroWallet extends CoinServiceAPI { _walletCreationService = WalletCreationService( secureStorage: _secureStore, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index d03733edb..8f36352d0 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -27,7 +27,6 @@ import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -673,7 +672,6 @@ class WowneroWallet extends CoinServiceAPI { // TODO: Wallet Service may need to be switched to Wownero walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; @@ -700,7 +698,6 @@ class WowneroWallet extends CoinServiceAPI { _walletCreationService = WalletCreationService( secureStorage: _secureStore, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -792,7 +789,6 @@ class WowneroWallet extends CoinServiceAPI { // } walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); await _generateNewWallet(seedWordsLength: seedWordsLength); @@ -837,7 +833,6 @@ class WowneroWallet extends CoinServiceAPI { walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); await _prefs.init(); @@ -892,7 +887,6 @@ class WowneroWallet extends CoinServiceAPI { // TODO: are these needed? WalletService? walletService; - SharedPreferences? prefs; KeyService? keysStorage; WowneroWalletBase? walletBase; WalletCreationService? _walletCreationService; @@ -990,7 +984,6 @@ class WowneroWallet extends CoinServiceAPI { walletService = wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(_secureStore); WalletInfo walletInfo; WalletCredentials credentials; @@ -1019,7 +1012,6 @@ class WowneroWallet extends CoinServiceAPI { _walletCreationService = WalletCreationService( secureStorage: _secureStore, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); diff --git a/pubspec.lock b/pubspec.lock index 2f7770d3a..0b8f49c2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1244,7 +1244,7 @@ packages: source: hosted version: "3.0.1" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index b7947a58d..8a41a4edd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -126,7 +126,7 @@ dependencies: pointycastle: ^3.6.0 package_info_plus: ^1.4.2 lottie: ^1.3.0 - shared_preferences: ^2.0.15 +# shared_preferences: ^2.0.15 file_picker: ^5.0.1 connectivity_plus: 2.3.6+1 # document_file_save_plus: ^1.0.5 diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 79bf98a41..789d91cb1 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -19,7 +19,6 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; @@ -27,10 +26,8 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'monero_wallet_test_data.dart'; -//FlutterSecureStorage? storage; FakeSecureStorage? storage; WalletService? walletService; -SharedPreferences? prefs; KeyService? keysStorage; MoneroWalletBase? walletBase; late WalletCreationService _walletCreationService; @@ -46,7 +43,6 @@ WalletType type = WalletType.monero; @GenerateMocks([]) void main() async { storage = FakeSecureStorage(); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(storage!); WalletInfo walletInfo = WalletInfo.external( id: '', @@ -90,12 +86,12 @@ void main() async { final dirPath = await pathForWalletDir(name: name, type: type); path = await pathForWallet(name: name, type: type); credentials = - // // creating a new wallet - // monero.createMoneroNewWalletCredentials( - // name: name, language: "English"); - // restoring a previous wallet - monero.createMoneroRestoreWalletFromSeedCredentials( - name: name, height: 2580000, mnemonic: testMnemonic); + // // creating a new wallet + // monero.createMoneroNewWalletCredentials( + // name: name, language: "English"); + // restoring a previous wallet + monero.createMoneroRestoreWalletFromSeedCredentials( + name: name, height: 2580000, mnemonic: testMnemonic); walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), @@ -111,7 +107,6 @@ void main() async { _walletCreationService = WalletCreationService( secureStorage: storage, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -124,8 +119,8 @@ void main() async { test("Test mainnet address generation from seed", () async { final wallet = await - // _walletCreationService.create(credentials); - _walletCreationService.restoreFromSeed(credentials); + // _walletCreationService.create(credentials); + _walletCreationService.restoreFromSeed(credentials); walletInfo.address = wallet.walletAddresses.address; //print(walletInfo.address); @@ -134,8 +129,7 @@ void main() async { walletBase = wallet as MoneroWalletBase; //print("${walletBase?.seed}"); - expect( - await walletBase!.validateAddress(walletInfo.address ?? ''), true); + expect(await walletBase!.validateAddress(walletInfo.address ?? ''), true); // print(walletBase); // loggerPrint(walletBase.toString()); @@ -157,20 +151,31 @@ void main() async { expect( await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); + expect(await walletBase!.validateAddress(''), false); expect( - await walletBase!.validateAddress(''), false); + await walletBase!.validateAddress( + '4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), + true); expect( - await walletBase!.validateAddress('4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), true); + await walletBase!.validateAddress( + '4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), + false); expect( - await walletBase!.validateAddress('4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), false); + await walletBase!.validateAddress( + '8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), + false); expect( - await walletBase!.validateAddress('8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), false); + await walletBase!.validateAddress( + '84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), + true); expect( - await walletBase!.validateAddress('84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), true); + await walletBase!.validateAddress( + '8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), + false); expect( - await walletBase!.validateAddress('8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), false); - expect( - await walletBase!.validateAddress('44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), false); + await walletBase!.validateAddress( + '44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), + false); }); }); /* @@ -229,6 +234,6 @@ Future pathForWalletDir( } Future pathForWallet( - {required String name, required WalletType type}) async => + {required String name, required WalletType type}) async => await pathForWalletDir(name: name, type: type) .then((path) => path + '/$name'); diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 78a9c56c1..8ffb590fc 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -19,14 +19,12 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'wownero_wallet_test_data.dart'; FakeSecureStorage? storage; WalletService? walletService; -SharedPreferences? prefs; KeyService? keysStorage; WowneroWalletBase? walletBase; late WalletCreationService _walletCreationService; @@ -41,7 +39,6 @@ WalletType type = WalletType.wownero; @GenerateMocks([]) void main() async { storage = FakeSecureStorage(); - prefs = await SharedPreferences.getInstance(); keysStorage = KeyService(storage!); WalletInfo walletInfo = WalletInfo.external( id: '', @@ -102,7 +99,6 @@ void main() async { _walletCreationService = WalletCreationService( secureStorage: storage, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -127,19 +123,24 @@ void main() async { walletBase = wallet as WowneroWalletBase; expect( - await walletBase!.validateAddress(wallet.walletAddresses.address ?? ''), true); + await walletBase! + .validateAddress(wallet.walletAddresses.address ?? ''), + true); } catch (_) { hasThrown = true; } expect(hasThrown, false); // Address validation + expect(await walletBase!.validateAddress(''), false); expect( - await walletBase!.validateAddress(''), false); + await walletBase!.validateAddress( + 'Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), + true); expect( - await walletBase!.validateAddress('Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), true); - expect( - await walletBase!.validateAddress('WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), false); + await walletBase!.validateAddress( + 'WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), + false); walletBase?.close(); walletBase = wallet as WowneroWalletBase; @@ -174,7 +175,6 @@ void main() async { _walletCreationService = WalletCreationService( secureStorage: storage, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -248,7 +248,6 @@ void main() async { _walletCreationService = WalletCreationService( secureStorage: storage, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -312,7 +311,6 @@ void main() async { _walletCreationService = WalletCreationService( secureStorage: storage, - sharedPreferences: prefs, walletService: walletService, keyService: keysStorage, ); @@ -367,6 +365,6 @@ Future pathForWalletDir( } Future pathForWallet( - {required String name, required WalletType type}) async => + {required String name, required WalletType type}) async => await pathForWalletDir(name: name, type: type) .then((path) => path + '/$name'); From 5d8a1b030450064a92b3486f3ab00dc2e95ae710 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 12 Nov 2022 09:17:16 -0600 Subject: [PATCH 024/225] trim unused import --- test/services/coins/monero/monero_wallet_test.dart | 3 --- test/services/coins/wownero/wownero_wallet_test.dart | 1 - 2 files changed, 4 deletions(-) diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index 789d91cb1..d6d600e36 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -1,5 +1,4 @@ import 'dart:core'; -import 'dart:core' as core; import 'dart:io'; import 'dart:math'; @@ -22,8 +21,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -// TODO trim down to the minimum imports above - import 'monero_wallet_test_data.dart'; FakeSecureStorage? storage; diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 8ffb590fc..637a40b81 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -1,5 +1,4 @@ import 'dart:core'; -import 'dart:core' as core; import 'dart:io'; import 'dart:math'; From ca6578d367b7574bc0b2fd486e7d1afec32b8940 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Sat, 12 Nov 2022 11:10:46 -0700 Subject: [PATCH 025/225] desktop address book filter dialog --- .../subviews/address_book_filter_view.dart | 356 ++++++++++-------- .../desktop_address_book.dart | 21 +- 2 files changed, 229 insertions(+), 148 deletions(-) diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index 35968621a..df779331e 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -5,7 +5,12 @@ import 'package:stackwallet/providers/ui/address_book_providers/address_book_fil import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class AddressBookFilterView extends ConsumerStatefulWidget { @@ -41,167 +46,224 @@ class _AddressBookFilterViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Filter addresses", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: LayoutBuilder(builder: (builderContext, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, + final isDesktop = Util.isDesktop; + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Filter addresses", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(12), + child: LayoutBuilder(builder: (builderContext, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Text( + "Only selected cryptocurrency addresses will be displayed.", + style: STextStyles.itemSubtitle(context), + ), + ), + const SizedBox( + height: 12, + ), + Text( + "Select cryptocurrency", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + child, + ], + ), + ), + ), + ), + ); + }), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Select cryptocurrency", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - child: Text( - "Only selected cryptocurrency addresses will be displayed.", - style: STextStyles.itemSubtitle(context), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + scrollDirection: Axis.vertical, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32), + child: child, + ), + ], + ), ), ), - const SizedBox( - height: 12, - ), - Text( - "Select cryptocurrency", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Wrap( - children: [ - ..._coins.map( - (coin) => Row( - children: [ - GestureDetector( - onTap: () { - if (ref - .read(addressBookFilterProvider) - .coins - .contains(coin)) { - ref - .read(addressBookFilterProvider) - .remove(coin, true); - } else { + ); + }, + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SecondaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + // const SizedBox(width: 16), + PrimaryButton( + width: 248, + desktopMed: true, + enabled: true, + label: "Apply", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ], + ); + }, + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Wrap( + children: [ + ..._coins.map( + (coin) => Row( + children: [ + GestureDetector( + onTap: () { + if (ref + .read(addressBookFilterProvider) + .coins + .contains(coin)) { + ref + .read(addressBookFilterProvider) + .remove(coin, true); + } else { + ref.read(addressBookFilterProvider).add(coin, true); + } + setState(() {}); + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 20, + width: 20, + child: Checkbox( + value: ref + .watch(addressBookFilterProvider + .select((value) => value.coins)) + .contains(coin), + onChanged: (value) { + if (value is bool) { + if (value) { ref .read(addressBookFilterProvider) .add(coin, true); + } else { + ref + .read(addressBookFilterProvider) + .remove(coin, true); } setState(() {}); - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox( - height: 20, - width: 20, - child: Checkbox( - value: ref - .watch( - addressBookFilterProvider - .select((value) => - value.coins)) - .contains(coin), - onChanged: (value) { - if (value is bool) { - if (value) { - ref - .read( - addressBookFilterProvider) - .add(coin, true); - } else { - ref - .read( - addressBookFilterProvider) - .remove(coin, true); - } - setState(() {}); - } - }, - ), - ), - const SizedBox( - width: 12, - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - coin.prettyName, - style: - STextStyles.largeMedium14( - context), - ), - const SizedBox( - height: 2, - ), - Text( - coin.ticker, - style: - STextStyles.itemSubtitle( - context), - ), - ], - ) - ], - ), - ), - ), + } + }, + ), + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + coin.prettyName, + style: STextStyles.largeMedium14(context), + ), + const SizedBox( + height: 2, + ), + Text( + coin.ticker, + style: STextStyles.itemSubtitle(context), ), ], - ), - ), - ], + ) + ], + ), ), ), - const Spacer(), - // Row( - // children: [ - // TextButton( - // onPressed: () {}, - // child: Text("Cancel"), - // ), - // SizedBox( - // width: 16, - // ), - // TextButton( - // onPressed: () {}, - // child: Text("Cancel"), - // ), - // ], - // ) - ], - ), + ), + ], ), ), - ), - ); - }), + ], + ), + ), ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index dd38b98a8..367671a3e 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -2,12 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.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/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -34,6 +36,21 @@ class _DesktopAddressBook extends ConsumerState { String filter = ""; + Future selectCryptocurrency() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return DesktopDialog( + maxHeight: 609, + maxWidth: 576, + child: AddressBookFilterView(), + ); + }, + ); + } + @override void initState() { _searchController = TextEditingController(); @@ -141,7 +158,9 @@ class _DesktopAddressBook extends ConsumerState { style: Theme.of(context) .extension()! .getDesktopMenuButtonColorSelected(context), - onPressed: () {}, + onPressed: () { + selectCryptocurrency(); + }, child: SizedBox( width: 200, height: 56, From 0164679cceac10602ceda50a8f93f8004fc076b1 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 12 Nov 2022 16:04:16 -0600 Subject: [PATCH 026/225] File system path clean up --- lib/main.dart | 24 ++----- .../advanced_views/debug_view.dart | 23 +++---- .../create_auto_backup_view.dart | 6 +- .../create_backup_view.dart | 6 +- .../edit_auto_backup_view.dart | 6 +- ..._file_system.dart => swb_file_system.dart} | 5 +- .../restore_from_file_view.dart | 9 ++- .../create_auto_backup.dart | 7 +- .../coins/epiccash/epiccash_wallet.dart | 27 ++++---- lib/services/coins/monero/monero_wallet.dart | 12 +--- .../coins/wownero/wownero_wallet.dart | 11 +--- .../flutter_secure_storage_interface.dart | 10 +-- lib/utilities/stack_file_system.dart | 66 +++++++++++++++++++ 13 files changed, 119 insertions(+), 93 deletions(-) rename lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/{stack_file_system.dart => swb_file_system.dart} (95%) create mode 100644 lib/utilities/stack_file_system.dart diff --git a/lib/main.dart b/lib/main.dart index 70f80e8ed..21abd9df7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -53,6 +53,7 @@ import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; @@ -79,29 +80,11 @@ void main() async { setWindowMaxSize(Size.infinite); } - Directory appDirectory = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDirectory = (await getLibraryDirectory()); - } - - if (Logging.isArmLinux) { - appDirectory = Directory("${appDirectory.path}/.stackwallet"); - await appDirectory.create(); - } - - if (Platform.isLinux) { - appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); - - if (!appDirectory.existsSync()) { - await appDirectory.create(); - } - } - // FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); if (!(Logging.isArmLinux || Logging.isTestEnv)) { final isar = await Isar.open( [LogSchema], - directory: appDirectory.path, + directory: (await StackFileSystem.applicationIsarDirectory()).path, inspector: false, ); await Logging.instance.init(isar); @@ -150,7 +133,8 @@ void main() async { Hive.registerAdapter(WalletTypeAdapter()); Hive.registerAdapter(UnspentCoinsInfoAdapter()); - await Hive.initFlutter(appDirectory.path); + await Hive.initFlutter( + (await StackFileSystem.applicationHiveDirectory()).path); await Hive.openBox(DB.boxNameDBInfo); diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index a3aa925a0..055773ef6 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -7,19 +7,25 @@ import 'package:event_bus/event_bus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -28,15 +34,6 @@ import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; -import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; - -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; - -import 'package:stackwallet/utilities/clipboard_interface.dart'; - -import 'package:stackwallet/utilities/util.dart'; class DebugView extends ConsumerStatefulWidget { const DebugView({Key? key}) : super(key: key); @@ -352,10 +349,10 @@ class _DebugViewState extends ConsumerState { BlueTextButton( text: "Save logs to file", onTap: () async { - final systemfile = StackFileSystem(); + final systemfile = SWBFileSystem(); await systemfile.prepareStorage(); - Directory rootPath = - (await getApplicationDocumentsDirectory()); + Directory rootPath = await StackFileSystem + .applicationRootDirectory(); if (Platform.isAndroid) { rootPath = Directory("/storage/emulated/0/"); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index 334d50e35..bf8bd40e7 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -9,7 +9,7 @@ import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; @@ -49,7 +49,7 @@ class _EnableAutoBackupViewState extends ConsumerState { late final FocusNode passwordFocusNode; late final FocusNode passwordRepeatFocusNode; - late final StackFileSystem stackFileSystem; + late final SWBFileSystem stackFileSystem; final zxcvbn = Zxcvbn(); String passwordFeedback = @@ -70,7 +70,7 @@ class _EnableAutoBackupViewState extends ConsumerState { @override void initState() { secureStore = ref.read(secureStoreProvider); - stackFileSystem = StackFileSystem(); + stackFileSystem = SWBFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); passwordRepeatController = TextEditingController(); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index fc4719fe1..b7ee6b4be 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -41,7 +41,7 @@ class _RestoreFromFileViewState extends State { late final FocusNode passwordFocusNode; late final FocusNode passwordRepeatFocusNode; - late final StackFileSystem stackFileSystem; + late final SWBFileSystem stackFileSystem; final zxcvbn = Zxcvbn(); String passwordFeedback = @@ -61,7 +61,7 @@ class _RestoreFromFileViewState extends State { @override void initState() { - stackFileSystem = StackFileSystem(); + stackFileSystem = SWBFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); passwordRepeatController = TextEditingController(); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 0be718549..4d3c6ca99 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -9,7 +9,7 @@ import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; @@ -48,7 +48,7 @@ class _EditAutoBackupViewState extends ConsumerState { late final FocusNode passwordFocusNode; late final FocusNode passwordRepeatFocusNode; - late final StackFileSystem stackFileSystem; + late final SWBFileSystem stackFileSystem; final zxcvbn = Zxcvbn(); String passwordFeedback = @@ -69,7 +69,7 @@ class _EditAutoBackupViewState extends ConsumerState { @override void initState() { secureStore = ref.read(secureStoreProvider); - stackFileSystem = StackFileSystem(); + stackFileSystem = SWBFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); passwordRepeatController = TextEditingController(); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart similarity index 95% rename from lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart rename to lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart index e57c5493f..82d3fd97d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart @@ -4,15 +4,16 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:stackwallet/utilities/util.dart'; -class StackFileSystem { +class SWBFileSystem { Directory? rootPath; Directory? startPath; String? filePath; String? dirPath; - final bool isDesktop = !(Platform.isAndroid || Platform.isIOS); + final bool isDesktop = Util.isDesktop; Future prepareStorage() async { if (Platform.isAndroid) { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index e2d16db54..c5ccfa6b3 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -20,14 +20,13 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:tuple/tuple.dart'; -import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; - class RestoreFromFileView extends ConsumerStatefulWidget { const RestoreFromFileView({Key? key}) : super(key: key); @@ -44,13 +43,13 @@ class _RestoreFromFileViewState extends ConsumerState { late final FocusNode passwordFocusNode; - late final StackFileSystem stackFileSystem; + late final SWBFileSystem stackFileSystem; bool hidePassword = true; @override void initState() { - stackFileSystem = StackFileSystem(); + stackFileSystem = SWBFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index cccb3b0b7..02d33fb95 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -9,14 +9,13 @@ import 'package:flutter_svg/svg.dart'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; -import 'package:stackwallet/utilities/enums/log_level_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -48,7 +47,7 @@ class _CreateAutoBackup extends ConsumerState { late final SecureStorageInterface secureStore; - late final StackFileSystem stackFileSystem; + late final SWBFileSystem stackFileSystem; late final FocusNode passphraseFocusNode; late final FocusNode passphraseRepeatFocusNode; final zxcvbn = Zxcvbn(); @@ -81,7 +80,7 @@ class _CreateAutoBackup extends ConsumerState { @override void initState() { secureStore = ref.read(secureStoreProvider); - stackFileSystem = StackFileSystem(); + stackFileSystem = SWBFileSystem(); fileLocationController = TextEditingController(); passphraseController = TextEditingController(); diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index 683e26544..1a5b2961f 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -9,7 +9,6 @@ import 'package:flutter_libepiccash/epic_cash.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:stack_wallet_backup/generate_password.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; @@ -31,6 +30,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:tuple/tuple.dart'; @@ -253,14 +253,16 @@ Future deleteEpicWallet({ required SecureStorageInterface secureStore, }) async { String? config = await secureStore.read(key: '${walletId}_config'); + // TODO: why double check for iOS? if (Platform.isIOS) { - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - if (Platform.isLinux) { - appDir = Directory("${appDir.path}/.stackwallet"); - } + Directory appDir = await StackFileSystem.applicationRootDirectory(); + // todo why double check for ios? + // if (Platform.isIOS) { + // appDir = (await getLibraryDirectory()); + // } + // if (Platform.isLinux) { + // appDir = Directory("${appDir.path}/.stackwallet"); + // } final path = "${appDir.path}/epiccash"; final String name = walletId; @@ -1237,13 +1239,8 @@ class EpicCashWallet extends CoinServiceAPI { } Future currentWalletDirPath() async { - Directory appDir = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - appDir = (await getLibraryDirectory()); - } - if (Platform.isLinux) { - appDir = Directory("${appDir.path}/.stackwallet"); - } + Directory appDir = await StackFileSystem.applicationRootDirectory(); + final path = "${appDir.path}/epiccash"; final String name = _walletId.trim(); return '$path/$name'; diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index caf1185a5..c35323d53 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -25,7 +25,6 @@ import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -47,6 +46,7 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; const int MINIMUM_CONFIRMATIONS = 10; @@ -897,14 +897,8 @@ class MoneroWallet extends CoinServiceAPI { required String name, required WalletType type, }) async { - Directory root = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - root = (await getLibraryDirectory()); - } - // - if (Platform.isLinux) { - root = Directory("${root.path}/.stackwallet"); - } + Directory root = await StackFileSystem.applicationRootDirectory(); + final prefix = walletTypeToString(type).toLowerCase(); final walletsDir = Directory('${root.path}/wallets'); final walletDire = Directory('${walletsDir.path}/$prefix/$name'); diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 8f36352d0..e39d13005 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -26,7 +26,6 @@ import 'package:flutter_libmonero/view_model/send/output.dart' import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -48,6 +47,7 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; const int MINIMUM_CONFIRMATIONS = 10; @@ -901,13 +901,8 @@ class WowneroWallet extends CoinServiceAPI { required String name, required WalletType type, }) async { - Directory root = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - root = (await getLibraryDirectory()); - } - if (Platform.isLinux) { - root = Directory("${root.path}/.stackwallet"); - } + Directory root = await StackFileSystem.applicationRootDirectory(); + final prefix = walletTypeToString(type).toLowerCase(); final walletsDir = Directory('${root.path}/wallets'); final walletDire = Directory('${walletsDir.path}/$prefix/$name'); diff --git a/lib/utilities/flutter_secure_storage_interface.dart b/lib/utilities/flutter_secure_storage_interface.dart index 9e8aef95c..2d9b19050 100644 --- a/lib/utilities/flutter_secure_storage_interface.dart +++ b/lib/utilities/flutter_secure_storage_interface.dart @@ -1,9 +1,8 @@ -import 'dart:io'; - import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:isar/isar.dart'; import 'package:stack_wallet_backup/secure_storage.dart'; import 'package:stackwallet/models/isar/models/encrypted_string_value.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; abstract class SecureStorageInterface { dynamic get store; @@ -47,14 +46,9 @@ class DesktopSecureStore { DesktopSecureStore(this.handler); Future init() async { - Directory? appDirectory; - if (Platform.isLinux) { - appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); - await appDirectory.create(); - } isar = await Isar.open( [EncryptedStringValueSchema], - directory: appDirectory!.path, + directory: (await StackFileSystem.applicationIsarDirectory()).path, inspector: false, name: "desktopStore", ); diff --git a/lib/utilities/stack_file_system.dart b/lib/utilities/stack_file_system.dart new file mode 100644 index 000000000..5177f1973 --- /dev/null +++ b/lib/utilities/stack_file_system.dart @@ -0,0 +1,66 @@ +import 'dart:io'; + +import 'package:path_provider/path_provider.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/util.dart'; + +abstract class StackFileSystem { + static Future applicationRootDirectory() async { + Directory appDirectory; + + // todo: can merge and do same as regular linux home dir? + if (Logging.isArmLinux) { + appDirectory = await getApplicationDocumentsDirectory(); + appDirectory = Directory("${appDirectory.path}/.stackwallet"); + } else if (Platform.isLinux) { + appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); + } else if (Platform.isWindows) { + // TODO: windows root .stackwallet dir location + throw Exception("Unsupported platform"); + } else if (Platform.isMacOS) { + // currently run in ipad mode?? + throw Exception("Unsupported platform"); + } else if (Platform.isIOS) { + // todo: check if we need different behaviour here + if (Util.isDesktop) { + appDirectory = await getLibraryDirectory(); + } else { + appDirectory = await getLibraryDirectory(); + } + } else if (Platform.isAndroid) { + appDirectory = await getApplicationDocumentsDirectory(); + } else { + throw Exception("Unsupported platform"); + } + if (!appDirectory.existsSync()) { + await appDirectory.create(recursive: true); + } + return appDirectory; + } + + static Future applicationIsarDirectory() async { + final root = await applicationRootDirectory(); + if (Util.isDesktop) { + final dir = Directory("${root.path}/isar"); + if (!dir.existsSync()) { + await dir.create(); + } + return dir; + } else { + return root; + } + } + + static Future applicationHiveDirectory() async { + final root = await applicationRootDirectory(); + if (Util.isDesktop) { + final dir = Directory("${root.path}/hive"); + if (!dir.existsSync()) { + await dir.create(); + } + return dir; + } else { + return root; + } + } +} From 357fd5e6fe23f2f31e3572efd27ad201df83affa Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 12 Nov 2022 16:34:34 -0600 Subject: [PATCH 027/225] update libmonero submodule dep --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 2da774385..e5e3f6ee8 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 2da77438527732dfaa5398aa391eab5253dabe19 +Subproject commit e5e3f6ee866a04f71534d71d62a7e371205b80d4 From 316a34914fd9849efa42a94ed7acf88a5ec8865e Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 12 Nov 2022 16:46:08 -0600 Subject: [PATCH 028/225] libmonero fix --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e5e3f6ee8..de29931da 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e5e3f6ee866a04f71534d71d62a7e371205b80d4 +Subproject commit de29931dacc9aefaf42a9ca139a8754a42adc40d From 94709623c4270a082277ad1f095e809d51be21e5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 07:37:24 -0600 Subject: [PATCH 029/225] temp firo balance dropdown --- .../wallet_view/sub_widgets/desktop_send.dart | 157 ++++++++++-------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 710bc8685..6f35a8570 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:decimal/decimal.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,7 +10,6 @@ import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; -import 'package:stackwallet/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -550,13 +550,13 @@ class _DesktopSendState extends ConsumerState { Future _firoBalanceFuture( ChangeNotifierProvider provider, String locale, + bool private, ) async { final wallet = ref.read(provider).wallet as FiroWallet?; if (wallet != null) { Decimal? balance; - if (ref.read(publicPrivateBalanceStateProvider.state).state == - "Private") { + if (private) { balance = await wallet.availablePrivateBalance(); } else { balance = await wallet.availablePublicBalance(); @@ -572,24 +572,21 @@ class _DesktopSendState extends ConsumerState { Widget firoBalanceFutureBuilder( BuildContext context, AsyncSnapshot snapshot, + bool private, ) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { - if (ref.read(publicPrivateBalanceStateProvider.state).state == - "Private") { + if (private) { _privateBalanceString = snapshot.data!; } else { _publicBalanceString = snapshot.data!; } } - if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private" && - _privateBalanceString != null) { + if (private && _privateBalanceString != null) { return Text( "$_privateBalanceString ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); - } else if (ref.read(publicPrivateBalanceStateProvider.state).state == - "Public" && - _publicBalanceString != null) { + } else if (!private && _publicBalanceString != null) { return Text( "$_publicBalanceString ${coin.ticker}", style: STextStyles.itemSubtitle(context), @@ -889,71 +886,95 @@ class _DesktopSendState extends ConsumerState { height: 10, ), if (coin == Coin.firo) - Stack( - children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - ), - child: RawMaterialButton( - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => FiroBalanceSelectionSheet( - walletId: walletId, - ), - ); - }, + DropdownButtonHideUnderline( + child: DropdownButton2( + offset: const Offset(0, -10), + isExpanded: true, + dropdownElevation: 0, + value: ref.watch(publicPrivateBalanceStateProvider.state).state, + items: [ + DropdownMenuItem( + value: "Private", child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Text( - "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - width: 10, - ), - FutureBuilder( - future: _firoBalanceFuture(provider, locale), - builder: firoBalanceFutureBuilder, - ), - ], + Text( + "Private balance", + style: STextStyles.itemSubtitle12(context), ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: Theme.of(context) - .extension()! - .textSubtitle2, + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _firoBalanceFuture(provider, locale, true), + builder: (context, AsyncSnapshot snapshot) => + firoBalanceFutureBuilder( + context, + snapshot, + true, + ), ), ], ), ), - ) - ], + DropdownMenuItem( + value: "Public", + child: Row( + children: [ + Text( + "Public balance", + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _firoBalanceFuture(provider, locale, false), + builder: (context, AsyncSnapshot snapshot) => + firoBalanceFutureBuilder( + context, + snapshot, + false, + ), + ), + ], + ), + ), + ], + onChanged: (value) { + if (value is String) { + setState(() { + ref.watch(publicPrivateBalanceStateProvider.state).state = + value; + }); + } + }, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context).extension()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), ), if (coin == Coin.firo) const SizedBox( From 9a9b10b1b3c244a5b0e0486187a85af8be055688 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 07:56:07 -0600 Subject: [PATCH 030/225] WIP: fee selection ui --- .../send_view/confirm_transaction_view.dart | 128 +++++++++++++----- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 26d1231f0..eef0f84e6 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -54,13 +54,6 @@ class _ConfirmTransactionViewState late final String routeOnSuccessName; late final bool isDesktop; - int _fee = 12; - final List _dropDownItems = [ - 12, - 22, - 234, - ]; - Future _attemptSend(BuildContext context) async { unawaited(showDialog( context: context, @@ -568,32 +561,101 @@ class _ConfirmTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, - ), - child: DropdownButtonFormField( - value: _fee, - items: _dropDownItems - .map( - (e) => DropdownMenuItem( - value: e, - child: Text( - e.toString(), - ), - ), - ) - .toList(), - onChanged: (value) { - if (value is int) { - setState(() { - _fee = value; - }); - } - }, - ), - ), + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Builder(builder: (context) { + final coin = ref + .watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))) + .coin; + + final fee = Format.satoshisToAmount( + transactionInfo["fee"] as int, + coin: coin, + ); + + return Text( + "${Format.localizedStringAsFixed( + value: fee, + locale: ref.watch(localeServiceChangeNotifierProvider + .select((value) => value.locale)), + decimalPlaces: coin == Coin.monero + ? Constants.decimalPlacesMonero + : coin == Coin.wownero + ? Constants.decimalPlacesWownero + : Constants.decimalPlaces, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), + ); + }), + ) + // DropdownButtonHideUnderline( + // child: DropdownButton2( + // offset: const Offset(0, -10), + // isExpanded: true, + // + // dropdownElevation: 0, + // value: _fee, + // items: [ + // ..._dropDownItems.map( + // (e) { + // String message = _fee.toString(); + // + // return DropdownMenuItem( + // value: e, + // child: Text(message), + // ); + // }, + // ), + // ], + // onChanged: (value) { + // if (value is int) { + // setState(() { + // _fee = value; + // }); + // } + // }, + // icon: SvgPicture.asset( + // Assets.svg.chevronDown, + // width: 12, + // height: 6, + // color: + // Theme.of(context).extension()!.textDark3, + // ), + // buttonPadding: const EdgeInsets.symmetric( + // horizontal: 16, + // vertical: 8, + // ), + // buttonDecoration: BoxDecoration( + // color: Theme.of(context) + // .extension()! + // .textFieldDefaultBG, + // borderRadius: BorderRadius.circular( + // Constants.size.circularBorderRadius, + // ), + // ), + // dropdownDecoration: BoxDecoration( + // color: Theme.of(context) + // .extension()! + // .textFieldDefaultBG, + // borderRadius: BorderRadius.circular( + // Constants.size.circularBorderRadius, + // ), + // ), + // ), + // ), + ), if (!isDesktop) const Spacer(), SizedBox( height: isDesktop ? 23 : 12, From 4238ce338ac8cf9f46ff402f7f5cff3186f59a3a Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 09:05:45 -0600 Subject: [PATCH 031/225] desktop password protected send flow --- .../send_view/confirm_transaction_view.dart | 165 ++++++++++++----- .../sending_transaction_dialog.dart | 69 +++++-- .../sub_widgets/desktop_auth_send.dart | 173 ++++++++++++++++++ .../wallet_view/sub_widgets/desktop_send.dart | 2 + 4 files changed, 348 insertions(+), 61 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index eef0f84e6..0f1692c08 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/route_generator.dart'; @@ -23,6 +24,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -55,14 +58,16 @@ class _ConfirmTransactionViewState late final bool isDesktop; Future _attemptSend(BuildContext context) async { - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return const SendingTransactionDialog(); - }, - )); + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return const SendingTransactionDialog(); + }, + ), + ); final note = transactionInfo["note"] as String? ?? ""; final manager = @@ -115,25 +120,66 @@ class _ConfirmTransactionViewState useSafeArea: false, barrierDismissible: true, builder: (context) { - return StackDialog( - title: "Broadcast transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Broadcast transaction failed", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + e.toString(), + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + desktopMed: true, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ), ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); + ); + } else { + return StackDialog( + title: "Broadcast transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } }, ); } @@ -736,25 +782,56 @@ class _ConfirmTransactionViewState label: "Send", desktopMed: true, onPressed: () async { - final unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", + final dynamic unlocked; + + if (isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), - ), - ); + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } if (unlocked is bool && unlocked && mounted) { unawaited(_attemptSend(context)); diff --git a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart index 1eb106b53..e5c86fe2e 100644 --- a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class SendingTransactionDialog extends StatefulWidget { @@ -43,24 +46,56 @@ class _RestoringDialogState extends State @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - return false; - }, - child: StackDialog( - title: "Sending transaction", - // // TODO get message from design team - // message: "", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: Theme.of(context).extension()!.accentColorDark, - width: 24, - height: 24, + if (Util.isDesktop) { + return DesktopDialog( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Sending transaction", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 40, + ), + RotationTransition( + turns: _spinAnimation, + child: SvgPicture.asset( + Assets.svg.arrowRotate, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 24, + height: 24, + ), + ), + ], ), ), - ), - ); + ); + } else { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: StackDialog( + title: "Sending transaction", + // // TODO get message from design team + // message: "", + icon: RotationTransition( + turns: _spinAnimation, + child: SvgPicture.asset( + Assets.svg.arrowRotate, + color: + Theme.of(context).extension()!.accentColorDark, + width: 24, + height: 24, + ), + ), + ), + ); + } } } diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart new file mode 100644 index 000000000..566a82b35 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/utilities/assets.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/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; + +class DesktopAuthSend extends ConsumerStatefulWidget { + const DesktopAuthSend({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _DesktopAuthSendState(); +} + +class _DesktopAuthSendState extends ConsumerState { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + + bool _confirmEnabled = false; + + Future verifyPassphrase() async { + return await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + } + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.keys, + width: 100, + ), + const SizedBox( + height: 56, + ), + Text( + "Confirm transaction", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 16, + ), + Text( + "Enter your wallet password to send BTC", + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + const SizedBox( + height: 24, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopLoginPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _confirmEnabled = passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox( + height: 48, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + desktopMed: true, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + enabled: _confirmEnabled, + label: "Confirm", + desktopMed: true, + onPressed: () async { + // TODO show spinner while verifying passphrase + + final passwordIsValid = await verifyPassphrase(); + + if (mounted) { + Navigator.of(context).pop(passwordIsValid); + } + }, + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 6f35a8570..071122def 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; @@ -332,6 +333,7 @@ class _DesktopSendState extends ConsumerState { child: ConfirmTransactionView( transactionInfo: txData, walletId: walletId, + routeOnSuccessName: DesktopHomeView.routeName, ), ), ), From daa7708ad0196e16145a551b9f5574c0972ff8e0 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 09:20:35 -0600 Subject: [PATCH 032/225] temp disable exchange option for desktop --- .../home/desktop_home_view.dart | 7 +-- .../home/desktop_menu.dart | 46 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index cb8aba255..76645442d 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -26,9 +26,10 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, ), - Container( - color: Colors.green, - ), + // Container( + // // todo: exchange + // color: Colors.green, + // ), Container( color: Colors.red, ), diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index 7409a4156..800a8416e 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -103,29 +103,29 @@ class _DesktopMenuState extends ConsumerState { const SizedBox( height: 2, ), - DesktopMenuItem( - icon: SvgPicture.asset( - Assets.svg.exchangeDesktop, - width: 20, - height: 20, - color: 1 == selectedMenuItem - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textDark - .withOpacity(0.8), - ), - label: "Exchange", - value: 1, - group: selectedMenuItem, - onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, - ), - const SizedBox( - height: 2, - ), + // DesktopMenuItem( + // icon: SvgPicture.asset( + // Assets.svg.exchangeDesktop, + // width: 20, + // height: 20, + // color: 1 == selectedMenuItem + // ? Theme.of(context) + // .extension()! + // .textDark + // : Theme.of(context) + // .extension()! + // .textDark + // .withOpacity(0.8), + // ), + // label: "Exchange", + // value: 1, + // group: selectedMenuItem, + // onChanged: updateSelectedMenuItem, + // iconOnly: _width == minimizedWidth, + // ), + // const SizedBox( + // height: 2, + // ), DesktopMenuItem( icon: SvgPicture.asset( Assets.svg.bell, From 5b47d5806d97f1206d336312d87e3a0a703974ef Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 09:25:06 -0600 Subject: [PATCH 033/225] disable seemingly pointless code --- .../coins/epiccash/epiccash_wallet.dart | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index 1a5b2961f..fcf728fb8 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -252,26 +252,27 @@ Future deleteEpicWallet({ required String walletId, required SecureStorageInterface secureStore, }) async { - String? config = await secureStore.read(key: '${walletId}_config'); - // TODO: why double check for iOS? - if (Platform.isIOS) { - Directory appDir = await StackFileSystem.applicationRootDirectory(); - // todo why double check for ios? - // if (Platform.isIOS) { - // appDir = (await getLibraryDirectory()); - // } - // if (Platform.isLinux) { - // appDir = Directory("${appDir.path}/.stackwallet"); - // } - final path = "${appDir.path}/epiccash"; - final String name = walletId; - - final walletDir = '$path/$name'; - var editConfig = jsonDecode(config as String); - - editConfig["wallet_dir"] = walletDir; - config = jsonEncode(editConfig); - } + // is this even needed for anything? + // String? config = await secureStore.read(key: '${walletId}_config'); + // // TODO: why double check for iOS? + // if (Platform.isIOS) { + // Directory appDir = await StackFileSystem.applicationRootDirectory(); + // // todo why double check for ios? + // // if (Platform.isIOS) { + // // appDir = (await getLibraryDirectory()); + // // } + // // if (Platform.isLinux) { + // // appDir = Directory("${appDir.path}/.stackwallet"); + // // } + // final path = "${appDir.path}/epiccash"; + // final String name = walletId; + // + // final walletDir = '$path/$name'; + // var editConfig = jsonDecode(config as String); + // + // editConfig["wallet_dir"] = walletDir; + // config = jsonEncode(editConfig); + // } final wallet = await secureStore.read(key: '${walletId}_wallet'); From 7cf3a8efba9a2968644c0df6e7b56257416db7c6 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 09:50:58 -0600 Subject: [PATCH 034/225] refactor desktop main menu and add WIP notifications view --- .../home/desktop_home_view.dart | 29 +++++---- .../home/desktop_menu.dart | 63 +++++++++++-------- .../desktop_notifications_view.dart | 59 +++++++++++++++++ lib/route_generator.dart | 7 +++ 4 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 76645442d..c0b0145f7 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -4,6 +4,7 @@ import 'package:stackwallet/pages_desktop_specific/home/address_book_view/deskto import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/route_generator.dart'; @@ -19,9 +20,9 @@ class DesktopHomeView extends ConsumerStatefulWidget { } class _DesktopHomeViewState extends ConsumerState { - int currentViewIndex = 0; - final List contentViews = [ - const Navigator( + DesktopMenuItemId currentViewKey = DesktopMenuItemId.myStack; + final Map contentViews = { + DesktopMenuItemId.myStack: const Navigator( key: Key("desktopStackHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, @@ -30,34 +31,36 @@ class _DesktopHomeViewState extends ConsumerState { // // todo: exchange // color: Colors.green, // ), - Container( - color: Colors.red, + DesktopMenuItemId.notifications: const Navigator( + key: Key("desktopNotificationsHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopNotificationsView.routeName, ), - const Navigator( + DesktopMenuItemId.addressBook: const Navigator( key: Key("desktopAddressBookHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopAddressBook.routeName, ), - const Navigator( + DesktopMenuItemId.settings: const Navigator( key: Key("desktopSettingHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopSettingsView.routeName, ), - const Navigator( + DesktopMenuItemId.support: const Navigator( key: Key("desktopSupportHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopSupportView.routeName, ), - const Navigator( + DesktopMenuItemId.about: const Navigator( key: Key("desktopAboutHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopAboutView.routeName, ), - ]; + }; - void onMenuSelectionChanged(int newIndex) { + void onMenuSelectionChanged(DesktopMenuItemId newKey) { setState(() { - currentViewIndex = newIndex; + currentViewKey = newKey; }); } @@ -75,7 +78,7 @@ class _DesktopHomeViewState extends ConsumerState { color: Theme.of(context).extension()!.background, ), Expanded( - child: contentViews[currentViewIndex], + child: contentViews[currentViewKey]!, ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index 800a8416e..cfa1a0ff0 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -8,13 +8,23 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +enum DesktopMenuItemId { + myStack, + exchange, + notifications, + addressBook, + settings, + support, + about, +} + class DesktopMenu extends ConsumerStatefulWidget { const DesktopMenu({ Key? key, required this.onSelectionChanged, }) : super(key: key); - final void Function(int)? onSelectionChanged; + final void Function(DesktopMenuItemId)? onSelectionChanged; @override ConsumerState createState() => _DesktopMenuState(); @@ -25,13 +35,13 @@ class _DesktopMenuState extends ConsumerState { static const minimizedWidth = 72.0; double _width = expandedWidth; - int selectedMenuItem = 0; + DesktopMenuItemId selectedMenuItem = DesktopMenuItemId.myStack; - void updateSelectedMenuItem(int index) { + void updateSelectedMenuItem(DesktopMenuItemId idKey) { setState(() { - selectedMenuItem = index; + selectedMenuItem = idKey; }); - widget.onSelectionChanged?.call(index); + widget.onSelectionChanged?.call(idKey); } void toggleMinimize() { @@ -85,7 +95,7 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.walletDesktop, width: 20, height: 20, - color: 0 == selectedMenuItem + color: DesktopMenuItemId.myStack == selectedMenuItem ? Theme.of(context) .extension()! .textDark @@ -95,7 +105,7 @@ class _DesktopMenuState extends ConsumerState { .withOpacity(0.8), ), label: "My Stack", - value: 0, + value: DesktopMenuItemId.myStack, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, @@ -108,7 +118,7 @@ class _DesktopMenuState extends ConsumerState { // Assets.svg.exchangeDesktop, // width: 20, // height: 20, - // color: 1 == selectedMenuItem + // color: DesktopMenuItemId.exchange == selectedMenuItem // ? Theme.of(context) // .extension()! // .textDark @@ -118,7 +128,7 @@ class _DesktopMenuState extends ConsumerState { // .withOpacity(0.8), // ), // label: "Exchange", - // value: 1, + // value: DesktopMenuItemId.exchange, // group: selectedMenuItem, // onChanged: updateSelectedMenuItem, // iconOnly: _width == minimizedWidth, @@ -131,17 +141,18 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.bell, width: 20, height: 20, - color: 2 == selectedMenuItem - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textDark - .withOpacity(0.8), + color: + DesktopMenuItemId.notifications == selectedMenuItem + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textDark + .withOpacity(0.8), ), label: "Notifications", - value: 2, + value: DesktopMenuItemId.notifications, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, @@ -154,7 +165,7 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: 3 == selectedMenuItem + color: DesktopMenuItemId.addressBook == selectedMenuItem ? Theme.of(context) .extension()! .textDark @@ -164,7 +175,7 @@ class _DesktopMenuState extends ConsumerState { .withOpacity(0.8), ), label: "Address Book", - value: 3, + value: DesktopMenuItemId.addressBook, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, @@ -177,7 +188,7 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.gear, width: 20, height: 20, - color: 4 == selectedMenuItem + color: DesktopMenuItemId.settings == selectedMenuItem ? Theme.of(context) .extension()! .textDark @@ -187,7 +198,7 @@ class _DesktopMenuState extends ConsumerState { .withOpacity(0.8), ), label: "Settings", - value: 4, + value: DesktopMenuItemId.settings, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, @@ -200,7 +211,7 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.messageQuestion, width: 20, height: 20, - color: 5 == selectedMenuItem + color: DesktopMenuItemId.support == selectedMenuItem ? Theme.of(context) .extension()! .textDark @@ -210,7 +221,7 @@ class _DesktopMenuState extends ConsumerState { .withOpacity(0.8), ), label: "Support", - value: 5, + value: DesktopMenuItemId.support, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, @@ -223,7 +234,7 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.aboutDesktop, width: 20, height: 20, - color: 6 == selectedMenuItem + color: DesktopMenuItemId.about == selectedMenuItem ? Theme.of(context) .extension()! .textDark @@ -233,7 +244,7 @@ class _DesktopMenuState extends ConsumerState { .withOpacity(0.8), ), label: "About", - value: 6, + value: DesktopMenuItemId.about, group: selectedMenuItem, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, diff --git a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart new file mode 100644 index 000000000..c8e688aa6 --- /dev/null +++ b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/notifications/notification_card.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.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 DesktopNotificationsView extends ConsumerStatefulWidget { + const DesktopNotificationsView({Key? key}) : super(key: key); + + static const String routeName = "/desktopNotifications"; + + @override + ConsumerState createState() => + _DesktopNotificationsViewState(); +} + +class _DesktopNotificationsViewState + extends ConsumerState { + @override + Widget build(BuildContext context) { + final notifications = + ref.watch(notificationsProvider.select((value) => value.notifications)); + + return DesktopScaffold( + background: Theme.of(context).extension()!.background, + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Padding( + padding: const EdgeInsets.only(left: 24), + child: Text( + "Notifications", + style: STextStyles.desktopH3(context), + ), + ), + ), + body: notifications.isEmpty + ? RoundedWhiteContainer( + child: Center( + child: Text( + "Notifications will appear here", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ) + : ListView.builder( + itemCount: notifications.length, + itemBuilder: (context, index) { + return NotificationCard( + notification: notifications[index], + ); + }, + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index f3e37e383..d7865d013 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -93,6 +93,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_v import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; +import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/appearance_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart'; @@ -1012,6 +1013,12 @@ class RouteGenerator { builder: (_) => const DesktopHomeView(), settings: RouteSettings(name: settings.name)); + case DesktopNotificationsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopNotificationsView(), + settings: RouteSettings(name: settings.name)); + case DesktopSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, From 60bdc6151bbffc8183cf0b0e539ee8e3b48005ad Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 10:40:31 -0600 Subject: [PATCH 035/225] desktop notifications view --- lib/notifications/notification_card.dart | 75 ++++++++++++++--- .../home/desktop_home_view.dart | 44 ++++++++-- .../home/desktop_menu.dart | 83 ++++++++++++------- .../desktop_notifications_view.dart | 20 ++++- .../desktop/current_desktop_menu_item.dart | 5 ++ 5 files changed, 179 insertions(+), 48 deletions(-) create mode 100644 lib/providers/desktop/current_desktop_menu_item.dart diff --git a/lib/notifications/notification_card.dart b/lib/notifications/notification_card.dart index 67be236f0..2a181499c 100644 --- a/lib/notifications/notification_card.dart +++ b/lib/notifications/notification_card.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/models/notification_model.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -20,22 +22,33 @@ class NotificationCard extends StatelessWidget { return Format.extractDateFrom(date.millisecondsSinceEpoch ~/ 1000); } + static const double mobileIconSize = 24; + static const double desktopIconSize = 30; + @override Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + return Stack( children: [ RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ) + : const EdgeInsets.all(12), child: Row( children: [ notification.changeNowId == null ? SvgPicture.asset( notification.iconAssetName, - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, ) : Container( - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(24), @@ -45,8 +58,8 @@ class NotificationCard extends StatelessWidget { color: Theme.of(context) .extension()! .accentColorDark, - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, ), ), const SizedBox( @@ -56,9 +69,35 @@ class NotificationCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - notification.title, - style: STextStyles.titleBold12(context), + ConditionalParent( + condition: isDesktop && !notification.read, + builder: (child) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + child, + Text( + "New", + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen, + ), + ) + ], + ), + child: Text( + notification.title, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.titleBold12(context), + ), ), const SizedBox( height: 2, @@ -68,11 +107,25 @@ class NotificationCard extends StatelessWidget { children: [ Text( notification.description, - style: STextStyles.label(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), ), Text( extractPrettyDateString(notification.date), - style: STextStyles.label(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index c0b0145f7..b1c35f00b 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -7,6 +7,9 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_v import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/global/notifications_provider.dart'; +import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -20,7 +23,6 @@ class DesktopHomeView extends ConsumerStatefulWidget { } class _DesktopHomeViewState extends ConsumerState { - DesktopMenuItemId currentViewKey = DesktopMenuItemId.myStack; final Map contentViews = { DesktopMenuItemId.myStack: const Navigator( key: Key("desktopStackHomeKey"), @@ -58,10 +60,36 @@ class _DesktopHomeViewState extends ConsumerState { ), }; - void onMenuSelectionChanged(DesktopMenuItemId newKey) { - setState(() { - currentViewKey = newKey; - }); + void onMenuSelectionWillChange(DesktopMenuItemId newKey) { + // check for unread notifications and refresh provider before + // showing notifications view + if (newKey == DesktopMenuItemId.notifications) { + ref.refresh(unreadNotificationsStateProvider); + } + // mark notifications as read if leaving notifications view + if (ref.read(currentDesktopMenuItemProvider.state).state == + DesktopMenuItemId.notifications && + newKey != DesktopMenuItemId.notifications) { + final Set unreadNotificationIds = + ref.read(unreadNotificationsStateProvider.state).state; + + if (unreadNotificationIds.isNotEmpty) { + List> futures = []; + for (int i = 0; i < unreadNotificationIds.length - 1; i++) { + futures.add(ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.elementAt(i), false)); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.last, true); + }); + } + } } @override @@ -71,14 +99,16 @@ class _DesktopHomeViewState extends ConsumerState { child: Row( children: [ DesktopMenu( - onSelectionChanged: onMenuSelectionChanged, + // onSelectionChanged: onMenuSelectionChanged, + onSelectionWillChange: onMenuSelectionWillChange, ), Container( width: 1, color: Theme.of(context).extension()!.background, ), Expanded( - child: contentViews[currentViewKey]!, + child: contentViews[ + ref.watch(currentDesktopMenuItemProvider.state).state]!, ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index cfa1a0ff0..bdaa1d6ce 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu_item.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -21,10 +22,12 @@ enum DesktopMenuItemId { class DesktopMenu extends ConsumerStatefulWidget { const DesktopMenu({ Key? key, - required this.onSelectionChanged, + this.onSelectionChanged, + this.onSelectionWillChange, }) : super(key: key); final void Function(DesktopMenuItemId)? onSelectionChanged; + final void Function(DesktopMenuItemId)? onSelectionWillChange; @override ConsumerState createState() => _DesktopMenuState(); @@ -35,12 +38,12 @@ class _DesktopMenuState extends ConsumerState { static const minimizedWidth = 72.0; double _width = expandedWidth; - DesktopMenuItemId selectedMenuItem = DesktopMenuItemId.myStack; void updateSelectedMenuItem(DesktopMenuItemId idKey) { - setState(() { - selectedMenuItem = idKey; - }); + widget.onSelectionWillChange?.call(idKey); + + ref.read(currentDesktopMenuItemProvider.state).state = idKey; + widget.onSelectionChanged?.call(idKey); } @@ -95,7 +98,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.walletDesktop, width: 20, height: 20, - color: DesktopMenuItemId.myStack == selectedMenuItem + color: DesktopMenuItemId.myStack == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -106,7 +112,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "My Stack", value: DesktopMenuItemId.myStack, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -118,7 +125,7 @@ class _DesktopMenuState extends ConsumerState { // Assets.svg.exchangeDesktop, // width: 20, // height: 20, - // color: DesktopMenuItemId.exchange == selectedMenuItem + // color: DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state // ? Theme.of(context) // .extension()! // .textDark @@ -129,7 +136,7 @@ class _DesktopMenuState extends ConsumerState { // ), // label: "Exchange", // value: DesktopMenuItemId.exchange, - // group: selectedMenuItem, + // group: ref.watch(currentDesktopMenuItemProvider.state).state, // onChanged: updateSelectedMenuItem, // iconOnly: _width == minimizedWidth, // ), @@ -141,19 +148,22 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.bell, width: 20, height: 20, - color: - DesktopMenuItemId.notifications == selectedMenuItem - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textDark - .withOpacity(0.8), + color: DesktopMenuItemId.notifications == + ref + .watch(currentDesktopMenuItemProvider.state) + .state + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textDark + .withOpacity(0.8), ), label: "Notifications", value: DesktopMenuItemId.notifications, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -165,7 +175,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: DesktopMenuItemId.addressBook == selectedMenuItem + color: DesktopMenuItemId.addressBook == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -176,7 +189,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Address Book", value: DesktopMenuItemId.addressBook, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -188,7 +202,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.gear, width: 20, height: 20, - color: DesktopMenuItemId.settings == selectedMenuItem + color: DesktopMenuItemId.settings == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -199,7 +216,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Settings", value: DesktopMenuItemId.settings, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -211,7 +229,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.messageQuestion, width: 20, height: 20, - color: DesktopMenuItemId.support == selectedMenuItem + color: DesktopMenuItemId.support == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -222,7 +243,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Support", value: DesktopMenuItemId.support, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -234,7 +256,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.aboutDesktop, width: 20, height: 20, - color: DesktopMenuItemId.about == selectedMenuItem + color: DesktopMenuItemId.about == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -245,7 +270,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "About", value: DesktopMenuItemId.about, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -262,7 +288,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exit", value: 7, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: (_) { // todo: save stuff/ notify before exit? exit(0); diff --git a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart index c8e688aa6..0c51f899d 100644 --- a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart +++ b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/notification_card.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -47,10 +48,25 @@ class _DesktopNotificationsViewState ), ) : ListView.builder( + primary: false, itemCount: notifications.length, itemBuilder: (context, index) { - return NotificationCard( - notification: notifications[index], + final notification = notifications[index]; + if (notification.read == false) { + ref + .read(unreadNotificationsStateProvider.state) + .state + .add(notification.id); + } + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 5, + ), + child: NotificationCard( + notification: notification, + ), ); }, ), diff --git a/lib/providers/desktop/current_desktop_menu_item.dart b/lib/providers/desktop/current_desktop_menu_item.dart new file mode 100644 index 000000000..6a58db6a0 --- /dev/null +++ b/lib/providers/desktop/current_desktop_menu_item.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; + +final currentDesktopMenuItemProvider = + StateProvider((ref) => DesktopMenuItemId.myStack); From 48bfabf74e67331383bb2e5e3b635d9c1316da72 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 10:49:45 -0600 Subject: [PATCH 036/225] update desktop directory paths for swb --- .../stack_backup_views/helpers/swb_file_system.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart index 82d3fd97d..e88e11dfe 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart @@ -26,11 +26,20 @@ class SWBFileSystem { } debugPrint(rootPath!.absolute.toString()); - Directory sampleFolder = - Directory('${rootPath!.path}Documents/Stack_backups'); + late Directory sampleFolder; + if (Platform.isIOS) { sampleFolder = Directory(rootPath!.path); + } else if (Platform.isAndroid) { + sampleFolder = Directory('${rootPath!.path}Documents/Stack_backups'); + } else if (Platform.isLinux) { + sampleFolder = Directory('${rootPath!.path}/Stack_backups'); + } else if (Platform.isWindows) { + sampleFolder = Directory('${rootPath!.path}/Stack_backups'); + } else if (Platform.isMacOS) { + sampleFolder = Directory('${rootPath!.path}/Stack_backups'); } + try { if (!sampleFolder.existsSync()) { sampleFolder.createSync(recursive: true); From ceaaa0a4f065f6543c48dada95d5c8e4edc10230 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 11:25:34 -0600 Subject: [PATCH 037/225] linter warning clean up and update process dialog popups --- .../backup_and_restore_settings.dart | 8 +- .../create_auto_backup.dart | 228 ++++++++++++------ .../enable_backup_dialog.dart | 5 +- 3 files changed, 162 insertions(+), 79 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 0df7c8975..47d1ffdc1 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; +import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/locale_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -15,6 +16,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -23,9 +25,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../../../providers/global/auto_swb_service_provider.dart'; -import '../../../../widgets/custom_buttons/blue_text_button.dart'; - class BackupRestoreSettings extends ConsumerStatefulWidget { const BackupRestoreSettings({Key? key}) : super(key: key); @@ -99,7 +98,7 @@ class _BackupRestoreSettings extends ConsumerState { useSafeArea: false, barrierDismissible: true, builder: (context) { - return CreateAutoBackup(); + return const CreateAutoBackup(); }, ); } @@ -428,6 +427,7 @@ class _BackupRestoreSettings extends ConsumerState { width: 190, label: "Edit auto backup", onPressed: () { + Navigator.of(context).pop(); createAutoBackup(); }, ), diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 02d33fb95..e383d6fe9 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -22,8 +23,8 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; @@ -119,10 +120,9 @@ class _CreateAutoBackup extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType "); - bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider - .select((value) => value.isAutoBackupEnabled)); + // bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider + // .select((value) => value.isAutoBackupEnabled)); - String? selectedItem = "Every 10 minutes"; final isDesktop = Util.isDesktop; return DesktopDialog( maxHeight: 680, @@ -140,25 +140,7 @@ class _CreateAutoBackup extends ConsumerState { textAlign: TextAlign.center, ), ), - Padding( - padding: const EdgeInsets.all(20.0), - child: AppBarIconButton( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - size: 40, - icon: SvgPicture.asset( - Assets.svg.x, - color: Theme.of(context).extension()!.textDark, - width: 22, - height: 22, - ), - onPressed: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); - }, - ), - ), + const DesktopDialogCloseButton(), ], ), const SizedBox( @@ -487,7 +469,7 @@ class _CreateAutoBackup extends ConsumerState { child: isDesktop ? DropdownButtonHideUnderline( child: DropdownButton2( - offset: Offset(0, -10), + offset: const Offset(0, -10), isExpanded: true, dropdownElevation: 0, value: _currentDropDownValue, @@ -570,12 +552,8 @@ class _CreateAutoBackup extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - onPressed: () { - int count = 0; - !isEnabledAutoBackup - ? Navigator.of(context).popUntil((_) => count++ >= 2) - : Navigator.of(context).pop(); - }, + desktopMed: true, + onPressed: Navigator.of(context).pop, ), ), const SizedBox( @@ -583,6 +561,7 @@ class _CreateAutoBackup extends ConsumerState { ), Expanded( child: PrimaryButton( + desktopMed: true, label: "Enable Auto Backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -595,44 +574,89 @@ class _CreateAutoBackup extends ConsumerState { passphraseRepeatController.text; if (pathToSave.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + ), ); return; } if (!(await Directory(pathToSave).exists())) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + ), ); return; } if (passphrase.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + ), ); return; } if (passphrase != repeatPassphrase) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + ), ); return; } - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting initial backup", - message: "This shouldn't take long", + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) { + if (Util.isDesktop) { + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all( + 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Encrypting initial backup", + style: STextStyles.desktopH3( + context), + ), + const SizedBox( + height: 40, + ), + Text( + "This shouldn't take long", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ], + ), + ), + ); + } else { + return const StackDialog( + title: "Encrypting initial backup", + message: "This shouldn't take long", + ); + } + }, ), ); @@ -653,10 +677,12 @@ class _CreateAutoBackup extends ConsumerState { .log("$err\n$s", level: LogLevel.Error); // pop encryption progress dialog Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: err, - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: err, + context: context, + ), ); return; } catch (e, s) { @@ -664,10 +690,12 @@ class _CreateAutoBackup extends ConsumerState { .log("$e\n$s", level: LogLevel.Error); // pop encryption progress dialog Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: "$e", - context: context, + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$e", + context: context, + ), ); return; } @@ -698,9 +726,7 @@ class _CreateAutoBackup extends ConsumerState { if (mounted) { // pop encryption progress dialog - int count = 0; - Navigator.of(context) - .popUntil((_) => count++ >= 2); + Navigator.of(context).pop(); if (result) { ref @@ -717,22 +743,76 @@ class _CreateAutoBackup extends ConsumerState { await showDialog( context: context, barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: - "Stack Auto Backup enabled and saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: "Stack Auto Backup enabled!"), + builder: (context) { + if (Platform.isAndroid) { + return StackOkDialog( + title: + "Stack Auto Backup enabled and saved to:", + message: fileToSave, + ); + } else if (Util.isDesktop) { + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 500, + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Stack Auto Backup enabled!", + style: + STextStyles.desktopH3( + context), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 40, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + label: "Ok", + desktopMed: true, + onPressed: () { + Navigator.of(context) + .pop(); + }, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return const StackOkDialog( + title: "Stack Auto Backup enabled!", + ); + } + }, ); if (mounted) { passphraseController.text = ""; passphraseRepeatController.text = ""; - int count = 0; - Navigator.of(context) - .popUntil((_) => count++ >= 2); + Navigator.of(context).pop(); } } else { await showDialog( diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart index 963fb4441..6496253d5 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart @@ -18,7 +18,7 @@ class EnableBackupDialog extends StatelessWidget { useSafeArea: false, barrierDismissible: true, builder: (context) { - return CreateAutoBackup(); + return const CreateAutoBackup(); }, ); } @@ -59,6 +59,7 @@ class EnableBackupDialog extends StatelessWidget { children: [ Expanded( child: SecondaryButton( + desktopMed: true, label: "Cancel", onPressed: () { Navigator.of(context).pop(); @@ -70,8 +71,10 @@ class EnableBackupDialog extends StatelessWidget { ), Expanded( child: PrimaryButton( + desktopMed: true, label: "Continue", onPressed: () { + Navigator.of(context).pop(); createAutoBackup(); }, ), From f46c0dacf9f22c8ec62acd6bd46933791b5bd7c3 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 08:26:36 -0700 Subject: [PATCH 038/225] fixed desktop sizing error --- .../desktop_address_book.dart | 245 ++++++++++-------- 1 file changed, 134 insertions(+), 111 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 367671a3e..e375bbcc7 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -11,6 +12,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -42,7 +44,7 @@ class _DesktopAddressBook extends ConsumerState { useSafeArea: false, barrierDismissible: true, builder: (context) { - return DesktopDialog( + return const DesktopDialog( maxHeight: 609, maxWidth: 576, child: AddressBookFilterView(), @@ -51,6 +53,21 @@ class _DesktopAddressBook extends ConsumerState { ); } + Future newContact() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const DesktopDialog( + maxHeight: 609, + maxWidth: 576, + child: AddAddressBookEntryView(), + ); + }, + ); + } + @override void initState() { _searchController = TextEditingController(); @@ -71,6 +88,7 @@ class _DesktopAddressBook extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; + final size = MediaQuery.of(context).size; return Column( mainAxisSize: MainAxisSize.min, @@ -93,127 +111,132 @@ class _DesktopAddressBook extends ConsumerState { const SizedBox(height: 53), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - children: [ - SizedBox( - height: 60, - width: 489, - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: false, - enableSuggestions: false, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => filter = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search...", - _searchFocusNode, - context, - ).copyWith( - labelStyle: STextStyles.fieldLabel(context) - .copyWith(fontSize: 16), - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, + child: RoundedContainer( + color: Theme.of(context).extension()!.background, + child: Row( + children: [ + SizedBox( + height: 60, + width: size.width - 800, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => filter = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + _searchFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context) + .copyWith(fontSize: 16), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - filter = ""; - }); - }, - ), - ], + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + filter = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), ), - ), - const SizedBox(width: 20), - TextButton( - style: Theme.of(context) - .extension()! - .getDesktopMenuButtonColorSelected(context), - onPressed: () { - selectCryptocurrency(); - }, - child: SizedBox( - width: 200, - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: SvgPicture.asset(Assets.svg.filter), - ), - Text( - "Filter", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension()! + .getDesktopMenuButtonColorSelected(context), + onPressed: () { + selectCryptocurrency(); + }, + child: SizedBox( + width: 200, + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SvgPicture.asset(Assets.svg.filter), ), - ), - ], + Text( + "Filter", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), ), ), - ), - const SizedBox(width: 20), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () {}, - child: SizedBox( - width: 200, - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: SvgPicture.asset(Assets.svg.circlePlus), - ), - Text( - "Add new", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .popupBG, + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + newContact(); + }, + child: SizedBox( + width: 200, + height: 56, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SvgPicture.asset(Assets.svg.circlePlus), ), - ), - ], + Text( + "Add new", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .popupBG, + ), + ), + ], + ), ), ), - ), - ], + ], + ), ), ), Padding( From e91c99883b24f8171db4c296845742f7712f552b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 11:43:00 -0700 Subject: [PATCH 039/225] edit auto backup navigation route error --- .../backup_and_restore/backup_and_restore_settings.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 47d1ffdc1..7d86999ac 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -427,7 +427,7 @@ class _BackupRestoreSettings extends ConsumerState { width: 190, label: "Edit auto backup", onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); createAutoBackup(); }, ), From 27b6293072b8287769d3c72f2171008ee7bd5d9b Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 14 Nov 2022 11:09:25 -0800 Subject: [PATCH 040/225] Add markdown python lib dependency to linux build script comments. --- scripts/linux/build_secure_storage_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/linux/build_secure_storage_deps.sh b/scripts/linux/build_secure_storage_deps.sh index 378f7a604..7a725d65c 100755 --- a/scripts/linux/build_secure_storage_deps.sh +++ b/scripts/linux/build_secure_storage_deps.sh @@ -20,7 +20,7 @@ cd "$LINUX_DIRECTORY" || exit # Build libSecret # sudo apt install meson libgirepository1.0-dev valac xsltproc gi-docgen docbook-xsl # sudo apt install python3-pip -#pip3 install --user meson --upgrade +#pip3 install --user meson markdown --upgrade # pip3 install --user gi-docgen cd build || exit git -C libsecret pull || git clone https://gitlab.gnome.org/GNOME/libsecret.git libsecret From 29ec03fb87572538f67b6d7832cca8896e8d4706 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 11:40:12 -0600 Subject: [PATCH 041/225] disable swb auto backup desktop popup --- .../backup_and_restore_settings.dart | 169 ++++++++++-------- 1 file changed, 91 insertions(+), 78 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 7d86999ac..53ce55424 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -19,8 +19,10 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -149,61 +151,80 @@ class _BackupRestoreSettings extends ConsumerState { ), ) : DesktopDialog( - maxHeight: 270, - child: Padding( - padding: EdgeInsets.symmetric(vertical: 20, horizontal: 32), - child: Column( - children: [ - Text( - "Disable Auto Backup", - style: STextStyles.desktopH3(context), - ), - const SizedBox(height: 24), - SizedBox( - width: 600, - child: Text( - "You are turning off Auto Backup. You can turn it back on at any time. " - "Your previous Auto Backup file will not be deleted. Remember to backup your wallets " - "manually so you don't lose important information.", - style: STextStyles.desktopTextSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Disable Auto Backup", + style: STextStyles.desktopH3(context), ), ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SecondaryButton( - width: 248, - desktopMed: true, - enabled: true, - label: "Back", - onPressed: () { - Navigator.of(context).pop(); - }, + SizedBox( + width: 600, + child: Text( + "You are turning off Auto Backup. You can turn it back on at any time. Your previous Auto Backup file will not be deleted. Remember to backup your wallets manually so you don't lose important information.", + style: STextStyles.desktopTextSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ), + const SizedBox( + height: 48, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SecondaryButton( + desktopMed: true, + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + desktopMed: true, + label: "Disable", + onPressed: () { + ref + .read(prefsChangeNotifierProvider) + .isAutoBackupEnabled = false; + Navigator.of(context).pop(); + }, + ), + ), + ], ), - const SizedBox(width: 20), - PrimaryButton( - width: 248, - desktopMed: true, - enabled: true, - label: "Disable", - onPressed: () { - Navigator.of(context).pop(); - setState(() { - ref - .watch(prefsChangeNotifierProvider) - .isAutoBackupEnabled = false; - }); - }, - ) ], ), - ], - ), + ), + ], ), ); }, @@ -372,40 +393,32 @@ class _BackupRestoreSettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( + RoundedContainer( width: 403, color: Theme.of(context) .extension()! .background, - child: Padding( - padding: - const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}", - style: STextStyles - .itemSubtitle( - context), - ), - BlueTextButton( - text: "Back up now", - onTap: () { - ref - .read( - autoSWBServiceProvider) - .doBackup(); - }, - ), - ], - ), - ], - ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}", + style: + STextStyles.itemSubtitle( + context), + ), + BlueTextButton( + text: "Back up now", + onTap: () { + ref + .read( + autoSWBServiceProvider) + .doBackup(); + }, + ), + ], ), ), const SizedBox( From bdf100842437628e60f50994cdbd33406c6108c7 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 12:19:26 -0600 Subject: [PATCH 042/225] desktop edit auto swb functionality --- .../edit_auto_backup_view.dart | 1170 +++++++++-------- .../backup_and_restore_settings.dart | 42 +- .../create_auto_backup.dart | 2 +- 3 files changed, 691 insertions(+), 523 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 4d3c6ca99..3b6d87b52 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -15,6 +17,7 @@ import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; @@ -22,7 +25,10 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -51,6 +57,14 @@ class _EditAutoBackupViewState extends ConsumerState { late final SWBFileSystem stackFileSystem; final zxcvbn = Zxcvbn(); + late BackupFrequencyType _currentDropDownValue; + + final List _dropDownItems = [ + BackupFrequencyType.everyTenMinutes, + BackupFrequencyType.everyAppStart, + BackupFrequencyType.afterClosingAWallet, + ]; + String passwordFeedback = "Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters."; @@ -66,6 +80,157 @@ class _EditAutoBackupViewState extends ConsumerState { passwordRepeatController.text.isNotEmpty; } + void onSavePressed() async { + final String pathToSave = fileLocationController.text; + final String passphrase = passwordController.text; + final String repeatPassphrase = passwordRepeatController.text; + + if (pathToSave.isEmpty) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + ), + ); + return; + } + if (!(await Directory(pathToSave).exists())) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + ), + ); + return; + } + if (passphrase.isEmpty) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + ), + ); + return; + } + if (passphrase != repeatPassphrase) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Updating Auto Backup", + message: "This shouldn't take long", + ), + ), + ); + // make sure the dialog is able to be displayed for at least 1 second + final fut = Future.delayed(const Duration(seconds: 1)); + + String adkString; + int adkVersion; + try { + final adk = await compute(generateAdk, passphrase); + adkString = Format.uint8listToString(adk.item2); + adkVersion = adk.item1; + } on Exception catch (e, s) { + String err = getErrorMessageFromSWBException(e); + Logging.instance.log("$err\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: err, + context: context, + ), + ); + return; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$e", + context: context, + ), + ); + return; + } + + await secureStore.write(key: "auto_adk_string", value: adkString); + await secureStore.write( + key: "auto_adk_version_string", value: adkVersion.toString()); + + final DateTime now = DateTime.now(); + final String fileToSave = createAutoBackupFilename(pathToSave, now); + + final backup = await SWB.createStackWalletJSON( + secureStorage: ref.read(secureStoreProvider), + ); + + bool result = await SWB.encryptStackWalletWithADK( + fileToSave, + adkString, + jsonEncode(backup), + adkVersion: adkVersion, + ); + + // this future should already be complete unless there was an error encrypting + await Future.wait([fut]); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + ref.read(prefsChangeNotifierProvider).autoBackupLocation = pathToSave; + ref.read(prefsChangeNotifierProvider).lastAutoBackup = now; + + ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = true; + + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: "Stack Auto Backup saved to:", + message: fileToSave, + ) + : const StackOkDialog(title: "Stack Auto Backup saved"), + ); + if (mounted) { + passwordController.text = ""; + passwordRepeatController.text = ""; + + Navigator.of(context) + .popUntil(ModalRoute.withName(AutoBackupView.routeName)); + } + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => + const StackOkDialog(title: "Failed to update Auto Backup"), + ); + } + } + } + @override void initState() { secureStore = ref.read(secureStoreProvider); @@ -77,6 +242,9 @@ class _EditAutoBackupViewState extends ConsumerState { fileLocationController.text = ref.read(prefsChangeNotifierProvider).autoBackupLocation ?? ""; + _currentDropDownValue = + ref.read(prefsChangeNotifierProvider).backupFrequencyType; + passwordFocusNode = FocusNode(); passwordRepeatFocusNode = FocusNode(); @@ -110,547 +278,509 @@ class _EditAutoBackupViewState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Edit Auto Backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit Auto Backup", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ); + }), ), ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder(builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, + child: Column( + crossAxisAlignment: + isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + Text( + "Create your backup", + style: STextStyles.smallMed12(context), + ), + if (isDesktop) + Text( + "Choose file location", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark3, ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Create your backup", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - if (!Platform.isAndroid) - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - onTap: Platform.isAndroid - ? null - : () async { - try { - await stackFileSystem.prepareStorage(); + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + if (!Platform.isAndroid) + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + onTap: Platform.isAndroid + ? null + : () async { + try { + await stackFileSystem.prepareStorage(); - if (mounted) { - await stackFileSystem.pickDir(context); - } - - if (mounted) { - setState(() { - fileLocationController.text = - stackFileSystem.dirPath ?? ""; - }); - } - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - } - }, - controller: fileLocationController, - style: STextStyles.field(context), - decoration: InputDecoration( - hintText: "Save to...", - hintStyle: STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - SvgPicture.asset( - Assets.svg.folder, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - key: const Key( - "createBackupSaveToFileLocationTextFieldKey"), - readOnly: true, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: false, - ), - onChanged: (newValue) {}, - ), - if (!Platform.isAndroid) - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPasswordFieldKey1"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Create passphrase", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - if (newValue.isEmpty) { - setState(() { - passwordFeedback = ""; - }); - return; - } - final result = zxcvbn.evaluate(newValue); - String suggestionsAndTips = ""; - for (var sug - in result.feedback.suggestions!.toSet()) { - suggestionsAndTips += "$sug\n"; - } - suggestionsAndTips += result.feedback.warning!; - String feedback = - // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" - suggestionsAndTips; - - passwordStrength = result.score! / 4; - - // hack fix to format back string returned from zxcvbn - if (feedback.contains("phrasesNo need")) { - feedback = feedback.replaceFirst( - "phrasesNo need", "phrases\nNo need"); - } - - if (feedback.endsWith("\n")) { - feedback = - feedback.substring(0, feedback.length - 2); - } + if (mounted) { + await stackFileSystem.pickDir(context); + } + if (mounted) { setState(() { - passwordFeedback = feedback; + fileLocationController.text = + stackFileSystem.dirPath ?? ""; + }); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + }, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + key: const Key("createBackupSaveToFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, + ), + if (isDesktop) + const SizedBox( + height: 24, + ), + if (isDesktop) + Text( + "Create a passphrase", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + textAlign: TextAlign.left, + ), + if (!Platform.isAndroid) + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Create passphrase", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; }); }, - ), - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: passwordFeedback.isNotEmpty ? 4 : 0, - ), - child: passwordFeedback.isNotEmpty - ? Text( - passwordFeedback, - style: STextStyles.infoSmall(context), - ) - : null, - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 12, - top: 10, - ), - child: ProgressBar( - key: const Key("createStackBackUpProgressBar"), - width: MediaQuery.of(context).size.width - 32 - 24, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension()! - .accentColorYellow - : Theme.of(context) - .extension()! - .accentColorGreen, - backgroundColor: Theme.of(context) + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) .extension()! - .buttonBackSecondary, - percent: - passwordStrength < 0.25 ? 0.03 : passwordStrength, + .textDark3, + width: 16, + height: 16, ), ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + width: 12, ), - child: TextField( - key: const Key("createBackupPasswordFieldKey2"), - focusNode: passwordRepeatFocusNode, - controller: passwordRepeatController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm passphrase", - passwordRepeatFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - setState(() {}); - // TODO: ? check if passwords match? - }, - ), - ), - const SizedBox( - height: 32, - ), - Text( - "Auto Backup frequency", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - Stack( - children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Positioned.fill( - child: RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => - const BackupFrequencyTypeSelectSheet(), - ); - }, - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - Format.prettyFrequencyType(ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.backupFrequencyType))), - style: STextStyles.itemSubtitle12(context), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: SvgPicture.asset( - Assets.svg.chevronDown, - color: Theme.of(context) - .extension()! - .textSubtitle2, - width: 12, - height: 6, - ), - ), - ], - ), - ), - ), - ) - ], - ), - const Spacer(), - const SizedBox( - height: 10, - ), - TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = - fileLocationController.text; - final String passphrase = passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result.feedback.suggestions!.toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; - if (pathToSave.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - ); - return; - } - if (!(await Directory(pathToSave).exists())) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - ); - return; - } - if (passphrase.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - ); - return; - } - if (passphrase != repeatPassphrase) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - ); - return; - } + passwordStrength = result.score! / 4; - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Updating Auto Backup", - message: "This shouldn't take long", - ), - ); - // make sure the dialog is able to be displayed for at least 1 second - final fut = Future.delayed( - const Duration(seconds: 1)); + // hack fix to format back string returned from zxcvbn + if (feedback.contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", "phrases\nNo need"); + } - String adkString; - int adkVersion; - try { - final adk = - await compute(generateAdk, passphrase); - adkString = Format.uint8listToString(adk.item2); - adkVersion = adk.item1; - } on Exception catch (e, s) { - String err = getErrorMessageFromSWBException(e); - Logging.instance - .log("$err\n$s", level: LogLevel.Error); - // pop encryption progress dialog - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: err, - context: context, - ); - return; - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - // pop encryption progress dialog - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: "$e", - context: context, - ); - return; - } + if (feedback.endsWith("\n")) { + feedback = feedback.substring(0, feedback.length - 2); + } - await secureStore.write( - key: "auto_adk_string", value: adkString); - await secureStore.write( - key: "auto_adk_version_string", - value: adkVersion.toString()); - - final DateTime now = DateTime.now(); - final String fileToSave = - createAutoBackupFilename(pathToSave, now); - - final backup = await SWB.createStackWalletJSON( - secureStorage: ref.read(secureStoreProvider), - ); - - bool result = await SWB.encryptStackWalletWithADK( - fileToSave, - adkString, - jsonEncode(backup), - adkVersion: adkVersion, - ); - - // this future should already be complete unless there was an error encrypting - await Future.wait([fut]); - - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); - - if (result) { - ref - .read(prefsChangeNotifierProvider) - .autoBackupLocation = pathToSave; - ref - .read(prefsChangeNotifierProvider) - .lastAutoBackup = now; - - ref - .read(prefsChangeNotifierProvider) - .isAutoBackupEnabled = true; - - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: - "Stack Auto Backup saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: "Stack Auto Backup saved"), - ); - if (mounted) { - passwordController.text = ""; - passwordRepeatController.text = ""; - - Navigator.of(context).popUntil( - ModalRoute.withName( - AutoBackupView.routeName)); - } - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Failed to update Auto Backup"), - ); - } - } - }, - child: Text( - "Save", - style: STextStyles.button(context), - ), + setState(() { + passwordFeedback = feedback; + }); + }, + ), + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall(context), ) - ], + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key("createStackBackUpProgressBar"), + width: isDesktop + ? 492 + : MediaQuery.of(context).size.width - 32 - 24, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context).extension()!.accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 ? 0.03 : passwordStrength, + ), + ), + SizedBox( + height: isDesktop ? 16 : 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm passphrase", + passwordRepeatFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + // TODO: ? check if passwords match? + }, + ), + ), + SizedBox( + height: isDesktop ? 24 : 32, + ), + Text( + "Auto Backup frequency", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ) + : STextStyles.smallMed12(context), + ), + const SizedBox( + height: 10, + ), + if (isDesktop) + DropdownButtonHideUnderline( + child: DropdownButton2( + offset: const Offset(0, -10), + isExpanded: true, + dropdownElevation: 0, + value: _currentDropDownValue, + items: [ + ..._dropDownItems.map( + (e) { + String message = ""; + switch (e) { + case BackupFrequencyType.everyTenMinutes: + message = "Every 10 minutes"; + break; + case BackupFrequencyType.everyAppStart: + message = "Every app startup"; + break; + case BackupFrequencyType.afterClosingAWallet: + message = "After closing a cryptocurrency wallet"; + break; + } + + return DropdownMenuItem( + value: e, + child: Text(message), + ); + }, + ), + ], + onChanged: (value) { + if (value is BackupFrequencyType) { + if (ref + .read(prefsChangeNotifierProvider) + .backupFrequencyType != + value) { + ref + .read(prefsChangeNotifierProvider) + .backupFrequencyType = value; + } + setState(() { + _currentDropDownValue = value; + }); + } + }, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), ), ), - ); - }), + if (!isDesktop) + Stack( + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + readOnly: true, + textInputAction: TextInputAction.none, + ), + Positioned.fill( + child: RawMaterialButton( + splashColor: + Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => const BackupFrequencyTypeSelectSheet(), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + Format.prettyFrequencyType(ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.backupFrequencyType))), + style: STextStyles.itemSubtitle12(context), + ), + Padding( + padding: const EdgeInsets.only(right: 4.0), + child: SvgPicture.asset( + Assets.svg.chevronDown, + color: Theme.of(context) + .extension()! + .textSubtitle2, + width: 12, + height: 6, + ), + ), + ], + ), + ), + ), + ) + ], + ), + if (!isDesktop) const Spacer(), + SizedBox( + height: isDesktop ? 24 : 10, + ), + if (isDesktop) + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + desktopMed: true, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + desktopMed: true, + enabled: shouldEnableCreate, + onPressed: onSavePressed, + ), + ), + ], + ), + if (!isDesktop) + TextButton( + style: shouldEnableCreate + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: !shouldEnableCreate ? null : onSavePressed, + child: Text( + "Save", + style: STextStyles.button(context), + ), + ) + ], ), ); } diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 53ce55424..663c3f975 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart'; @@ -105,6 +106,44 @@ class _BackupRestoreSettings extends ConsumerState { ); } + Future editAutoBackup() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Edit auto backup", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: EditAutoBackupView(), + ), + ], + ), + ), + ); + } + Future attemptDisable() async { final result = await showDialog( context: context, @@ -440,8 +479,7 @@ class _BackupRestoreSettings extends ConsumerState { width: 190, label: "Edit auto backup", onPressed: () { - // Navigator.of(context).pop(); - createAutoBackup(); + editAutoBackup(); }, ), ], diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index e383d6fe9..5e7e86fe1 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -383,7 +383,7 @@ class _CreateAutoBackup extends ConsumerState { ), ), const SizedBox( - height: 10, + height: 16, ), ClipRRect( borderRadius: BorderRadius.circular( From e053764554ef5ab32226110a0ed18a9d6ac1c5c5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 13:24:40 -0600 Subject: [PATCH 043/225] basic desktop change passphrase functionality --- .../home/settings_menu/security_settings.dart | 820 +++++++++--------- lib/utilities/desktop_password_service.dart | 42 + 2 files changed, 474 insertions(+), 388 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index 9ee9b5bfc..d752ece38 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -1,9 +1,13 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; -import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -47,6 +51,63 @@ class _SecuritySettings extends ConsumerState { String passwordFeedback = "Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters."; + Future attemptChangePW() async { + final String pw = passwordCurrentController.text; + final String pwNew = passwordController.text; + final String pwNewRepeat = passwordRepeatController.text; + + final verified = + await ref.read(storageCryptoHandlerProvider).verifyPassphrase(pw); + + if (verified) { + if (pwNew != pwNewRepeat) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "New passphrase does not match!", + context: context, + ), + ); + return false; + } else { + final success = + await ref.read(storageCryptoHandlerProvider).changePassphrase( + pw, + pwNew, + ); + + if (success) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Passphrase successfully changed", + context: context, + ), + ); + return true; + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase change failed", + context: context, + ), + ); + return false; + } + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Current passphrase is not valid!", + context: context, + ), + ); + return false; + } + } + @override void initState() { passwordCurrentController = TextEditingController(); @@ -78,411 +139,394 @@ class _SecuritySettings extends ConsumerState { debugPrint("BUILD: $runtimeType"); return Column( children: [ - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.circleLock, - width: 48, - height: 48, - ), - Center( - child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( - children: [ - TextSpan( - text: "Change Password", - style: STextStyles.desktopTextSmall(context), - ), - TextSpan( - text: - "\n\nProtect your Stack Wallet with a strong password. Stack Wallet does not store " - "your password, and is therefore NOT able to restore it. Keep your password safe and secure.", - style: - STextStyles.desktopTextExtraExtraSmall(context), - ), - ], - ), - ), - ), - ), - Column( + Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + radiusMultiplier: 2, + padding: const EdgeInsets.all(24), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all( - 10, - ), - child: changePassword - ? SizedBox( - width: 512, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Current password", - style: - STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + SvgPicture.asset( + Assets.svg.circleLock, + width: 48, + height: 48, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 16, + ), + Text( + "Change Password", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Protect your Stack Wallet with a strong password. Stack Wallet does not store " + "your password, and is therefore NOT able to restore it. Keep your password safe and secure.", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + changePassword + ? SizedBox( + width: 512, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Current password", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, ), - child: TextField( - key: const Key( - "desktopSecurityRestoreFromFilePasswordFieldKey"), - focusNode: passwordCurrentFocusNode, - controller: passwordCurrentController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter current password", - passwordCurrentFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onChanged: (newValue) {}, - ), - ), - const SizedBox(height: 16), - Text( - "New password", - style: - STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key( - "desktopSecurityCreateNewPasswordFieldKey1"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter new password", - passwordFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityCreateNewPasswordButtonKey1"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - if (newValue.isEmpty) { - setState(() { - passwordFeedback = ""; - }); - return; - } - final result = - zxcvbn.evaluate(newValue); - String suggestionsAndTips = ""; - for (var sug in result - .feedback.suggestions! - .toSet()) { - suggestionsAndTips += "$sug\n"; - } - suggestionsAndTips += - result.feedback.warning!; - String feedback = - // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" - suggestionsAndTips; - - passwordStrength = result.score! / 4; - - // hack fix to format back string returned from zxcvbn - if (feedback - .contains("phrasesNo need")) { - feedback = feedback.replaceFirst( - "phrasesNo need", - "phrases\nNo need"); - } - - if (feedback.endsWith("\n")) { - feedback = feedback.substring( - 0, feedback.length - 2); - } - - setState(() { - passwordFeedback = feedback; - }); - }, - ), - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: - passwordFeedback.isNotEmpty ? 4 : 0, - ), - child: passwordFeedback.isNotEmpty - ? Text( - passwordFeedback, - style: STextStyles.infoSmall( - context), - ) - : null, - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 12, - top: 10, - ), - child: ProgressBar( + child: TextField( key: const Key( - "desktopSecurityCreateStackBackUpProgressBar"), - width: 450, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension()! - .accentColorYellow - : Theme.of(context) - .extension()! - .accentColorGreen, - backgroundColor: Theme.of(context) - .extension()! - .buttonBackSecondary, - percent: passwordStrength < 0.25 - ? 0.03 - : passwordStrength, - ), - ), - const SizedBox(height: 16), - Text( - "Confirm new password", - style: - STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key( - "desktopSecurityCreateNewPasswordFieldKey2"), - focusNode: passwordRepeatFocusNode, - controller: passwordRepeatController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm new password", - passwordRepeatFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityCreateNewPasswordButtonKey2"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, + "desktopSecurityRestoreFromFilePasswordFieldKey"), + focusNode: passwordCurrentFocusNode, + controller: passwordCurrentController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter current password", + passwordCurrentFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( width: 16, - height: 16, ), - ), - const SizedBox( - width: 12, - ), - ], + GestureDetector( + key: const Key( + "desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension< + StackColors>()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), ), ), + onChanged: (newValue) { + setState(() {}); + }, ), - onChanged: (newValue) { - setState(() {}); - }, ), - ), - const SizedBox(height: 20), - PrimaryButton( - width: 142, - desktopMed: true, - enabled: shouldEnableSave, - label: "Save changes", - onPressed: () { - setState(() { - changePassword = false; - }); - }, - ) - ], + const SizedBox(height: 16), + Text( + "New password", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter new password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey1"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension< + StackColors>()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = + zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result + .feedback.suggestions! + .toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += + result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; + + passwordStrength = result.score! / 4; + + // hack fix to format back string returned from zxcvbn + if (feedback + .contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", + "phrases\nNo need"); + } + + if (feedback.endsWith("\n")) { + feedback = feedback.substring( + 0, feedback.length - 2); + } + + setState(() { + passwordFeedback = feedback; + }); + }, + ), + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: passwordFeedback.isNotEmpty + ? 4 + : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall( + context), + ) + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key( + "desktopSecurityCreateStackBackUpProgressBar"), + width: 450, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 + ? 0.03 + : passwordStrength, + ), + ), + const SizedBox(height: 16), + Text( + "Confirm new password", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm new password", + passwordRepeatFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey2"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension< + StackColors>()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 20), + PrimaryButton( + width: 160, + desktopMed: true, + enabled: shouldEnableSave, + label: "Save changes", + onPressed: () async { + final didChangePW = + await attemptChangePW(); + if (didChangePW) { + setState(() { + changePassword = false; + }); + } + }, + ) + ], + ), + ) + : PrimaryButton( + width: 210, + desktopMed: true, + enabled: true, + label: "Set up new password", + onPressed: () { + setState(() { + changePassword = true; + }); + }, ), - ) - : PrimaryButton( - width: 192, - desktopMed: true, - enabled: true, - label: "Set up new password", - onPressed: () { - setState(() { - changePassword = true; - }); - }, - ), + ], ), ], ), - ], + ), ), - ), + const SizedBox( + width: 40, + ), + ], ), ], ); } } - -class NewPasswordButton extends ConsumerWidget { - const NewPasswordButton({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () {}, - child: Text( - "Set up new password", - style: STextStyles.button(context), - ), - ), - ); - } -} diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index f20525873..9ef83932b 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -115,6 +115,48 @@ class DPS { } } + Future changePassphrase( + String passphraseOld, + String passphraseNew, + ) async { + final box = await Hive.openBox(DB.boxNameDesktopData); + final keyBlob = DB.instance.get( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + ); + await box.close(); + + if (keyBlob == null) { + // no passphrase key blob found so any passphrase is technically bad + return false; + } + + if (!(await verifyPassphrase(passphraseOld))) { + return false; + } + + try { + await _handler!.resetPassphrase(passphraseNew); + + final box = await Hive.openBox(DB.boxNameDesktopData); + await DB.instance.put( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + value: await _handler!.getKeyBlob(), + ); + await box.close(); + + // successfully updated passphrase + return true; + } catch (e, s) { + Logging.instance.log( + "${_getMessageFromException(e)}\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + Future hasPassword() async { final keyBlob = DB.instance.get( boxName: DB.boxNameDesktopData, From 9df0569bb16fb17583f97c83227d7b83b5cd0068 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 13:35:14 -0600 Subject: [PATCH 044/225] add passphrase check before displaying wallet seed --- .../unlock_wallet_keys_desktop.dart | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index e65820737..23360c98c 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -1,10 +1,15 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -196,36 +201,32 @@ class _UnlockWalletKeysDesktopState enabled: continueEnabled, onPressed: continueEnabled ? () async { - // todo: check password - // Navigator.of(context).pop(); - final words = await ref - .read(walletsChangeNotifierProvider) - .getManager(widget.walletId) - .mnemonic; + final verified = await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); - await Navigator.of(context).pushReplacementNamed( - WalletKeysDesktopPopup.routeName, - arguments: words, - ); - // - // await showDialog( - // context: context, - // barrierDismissible: false, - // builder: (context) => Navigator( - // initialRoute: WalletKeysDesktopPopup.routeName, - // onGenerateRoute: RouteGenerator.generateRoute, - // onGenerateInitialRoutes: (_, __) { - // return [ - // RouteGenerator.generateRoute( - // RouteSettings( - // name: WalletKeysDesktopPopup.routeName, - // arguments: words, - // ), - // ) - // ]; - // }, - // ), - // ); + if (verified) { + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + await Navigator.of(context) + .pushReplacementNamed( + WalletKeysDesktopPopup.routeName, + arguments: words, + ); + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase!", + context: context, + ), + ); + } } : null, ), From 9417d78c8170b64a27acf23d24347f2822eff138 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 13:29:43 -0700 Subject: [PATCH 045/225] wip: new contact emoji selection and crypto selection --- .../subviews/add_address_book_entry_view.dart | 858 ++++++++++-------- 1 file changed, 501 insertions(+), 357 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 74f3dfde8..588ca5b26 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -15,15 +15,17 @@ import 'package:stackwallet/utilities/clipboard_interface.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class AddAddressBookEntryView extends ConsumerStatefulWidget { const AddAddressBookEntryView({ Key? key, @@ -108,395 +110,537 @@ class _AddAddressBookEntryViewState Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "New contact", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addAddressBookEntryFavoriteButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _isFavorite - ? Theme.of(context) - .extension()! - .favoriteStarActive - : Theme.of(context) - .extension()! - .favoriteStarInactive, - width: 20, - height: 20, - ), - onPressed: () { - setState(() { - _isFavorite = !_isFavorite; - }); + final isDesktop = Util.isDesktop; + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } }, ), - ), - ), - ], - ), - body: LayoutBuilder( - builder: (context, constraint) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsets.only( - // top: 8, - left: 4, - right: 4, - bottom: 16, + title: Text( + "New contact", + style: STextStyles.navBarTitle(context), ), - child: ConstrainedBox( - constraints: BoxConstraints( - // subtract top and bottom padding set in parent - minHeight: constraint.maxHeight - 16, // - 8, - ), - child: IntrinsicHeight( - child: Column( - children: [ - const SizedBox( - height: 4, + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addAddressBookEntryFavoriteButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _isFavorite + ? Theme.of(context) + .extension()! + .favoriteStarActive + : Theme.of(context) + .extension()! + .favoriteStarInactive, + width: 20, + height: 20, ), - GestureDetector( - onTap: () { - if (_selectedEmoji != null) { + onPressed: () { + setState(() { + _isFavorite = !_isFavorite; + }); + }, + ), + ), + ), + ], + ), + body: child); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + Text( + "New contact", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + const SizedBox(width: 10), + AppBarIconButton( + key: + const Key("addAddressBookEntryFavoriteButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _isFavorite + ? Theme.of(context) + .extension()! + .favoriteStarActive + : Theme.of(context) + .extension()! + .favoriteStarInactive, + width: 20, + height: 20, + ), + onPressed: () { setState(() { - _selectedEmoji = null; + _isFavorite = !_isFavorite; }); - return; - } - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => const EmojiSelectSheet(), - ).then((value) { - if (value is Emoji) { + }, + ), + ], + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded(child: child), + ], + ); + }, + child: LayoutBuilder( + builder: (context, constraint) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.only( + // top: 8, + left: 4, + right: 4, + bottom: 16, + ), + child: ConstrainedBox( + constraints: BoxConstraints( + // subtract top and bottom padding set in parent + minHeight: constraint.maxHeight - 16, // - 8, + ), + child: IntrinsicHeight( + child: Column( + children: [ + const SizedBox( + height: 4, + ), + GestureDetector( + onTap: () { + if (_selectedEmoji != null) { setState(() { - _selectedEmoji = value; + _selectedEmoji = null; }); + return; } - }); - }, - child: SizedBox( - height: 48, - width: 48, - child: Stack( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, - ) - : Text( - _selectedEmoji!.char, - style: - STextStyles.pageTitleH1(context), + + ///TODO if desktop make dialog + !isDesktop + ? showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => const EmojiSelectSheet(), + ).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }) + : showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 700, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: + const EdgeInsets.all(32), + child: Text( + "Select emoji", + style: + STextStyles.desktopH3( + context), + textAlign: TextAlign.center, + ), + ), + ], + ), + Expanded( + child: LayoutBuilder( + builder: + (context, constraints) { + return SingleChildScrollView( + scrollDirection: + Axis.vertical, + child: ConstrainedBox( + constraints: + BoxConstraints( + minHeight: constraints + .maxHeight, + minWidth: constraints + .maxWidth, + ), + child: IntrinsicHeight( + child: Column( + children: const [ + Padding( + padding: EdgeInsets + .symmetric( + horizontal: + 32), + // child: + // EmojiSelectSheet(), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Container( - height: 14, - width: 14, + ); + }).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + }, + child: SizedBox( + height: 48, + width: 48, + child: Stack( + children: [ + Container( + height: 48, + width: 48, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - color: Theme.of(context) - .extension()! - .accentColorDark), + borderRadius: BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), child: Center( child: _selectedEmoji == null ? SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension()! - .textWhite, - width: 12, - height: 12, + Assets.svg.user, + height: 24, + width: 24, ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension()! - .textWhite, - width: 8, - height: 8, + : Text( + _selectedEmoji!.char, + style: STextStyles.pageTitleH1( + context), ), ), ), - ) - ], - ), - ), - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: nameController, - focusNode: nameFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter contact name", - nameFocusNode, - context, - ).copyWith( - suffixIcon: ref - .read(contactNameIsNotEmptyStateProvider - .state) - .state - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - nameController.text = ""; - }); - }, - ), - ], - ), + Align( + alignment: Alignment.bottomRight, + child: Container( + height: 14, + width: 14, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + color: Theme.of(context) + .extension()! + .accentColorDark), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .textWhite, + width: 12, + height: 12, + ) + : SvgPicture.asset( + Assets.svg.thickX, + color: Theme.of(context) + .extension()! + .textWhite, + width: 8, + height: 8, + ), ), - ) - : null, + ), + ) + ], + ), ), - onChanged: (newValue) { - ref - .read(contactNameIsNotEmptyStateProvider.state) - .state = newValue.isNotEmpty; - }, ), - ), - if (forms.length <= 1) const SizedBox( height: 8, ), - if (forms.length <= 1) forms[0], - if (forms.length > 1) - for (int i = 0; i < forms.length; i++) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 12, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Address ${i + 1}", - style: STextStyles.smallMed12(context), - ), - BlueTextButton( - onTap: () { - _removeForm(forms[i].id); - }, - text: "Remove", - ), - ], - ), - const SizedBox( - height: 8, - ), - forms[i], - ], + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - const SizedBox( - height: 16, - ), - BlueTextButton( - onTap: () { - _addForm(); - scrollController.animateTo( - scrollController.position.maxScrollExtent + 500, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); - }, - text: "+ Add another address", - ), - // GestureDetector( - // - // child: Text( - // "+ Add another address", - // style: STextStyles.largeMedium14(context), - // ), - // ), - const SizedBox( - height: 16, - ), - const Spacer(), - Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: nameController, + focusNode: nameFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter contact name", + nameFocusNode, + context, + ).copyWith( + suffixIcon: ref + .read(contactNameIsNotEmptyStateProvider + .state) + .state + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + nameController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, ), + onChanged: (newValue) { + ref + .read( + contactNameIsNotEmptyStateProvider.state) + .state = newValue.isNotEmpty; + }, ), + ), + if (forms.length <= 1) const SizedBox( - width: 16, + height: 8, ), - Expanded( - child: Builder( - builder: (context) { - bool nameExists = ref - .watch(contactNameIsNotEmptyStateProvider - .state) - .state; - - bool validForms = ref.watch( - validContactStateProvider(forms - .map((e) => e.id) - .toList(growable: false))); - - bool shouldEnableSave = - validForms && nameExists; - - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor( - context), - onPressed: shouldEnableSave - ? () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75), - ); - } - List entries = - []; - for (int i = 0; - i < forms.length; - i++) { - entries.add(ref - .read(addressEntryDataProvider( - forms[i].id)) - .buildAddressEntry()); - } - Contact contact = Contact( - emojiChar: _selectedEmoji?.char, - name: nameController.text, - addresses: entries, - isFavorite: _isFavorite, - ); - - if (await ref - .read(addressBookServiceProvider) - .addContact(contact)) { - if (mounted) { - Navigator.of(context).pop(); - } - // TODO show success notification - } else { - // TODO show error notification - } - } - : null, - child: Text( - "Save", - style: STextStyles.button(context).copyWith( - color: shouldEnableSave - ? Theme.of(context) - .extension()! - .buttonTextPrimary - : Theme.of(context) - .extension()! - .buttonTextPrimaryDisabled, + if (forms.length <= 1) forms[0], + if (forms.length > 1) + for (int i = 0; i < forms.length; i++) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address ${i + 1}", + style: STextStyles.smallMed12(context), ), - ), - ); - }, + BlueTextButton( + onTap: () { + _removeForm(forms[i].id); + }, + text: "Remove", + ), + ], + ), + const SizedBox( + height: 8, + ), + forms[i], + ], ), - ), - ], - ), - ], + const SizedBox( + height: 16, + ), + BlueTextButton( + onTap: () { + _addForm(); + scrollController.animateTo( + scrollController.position.maxScrollExtent + 500, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + }, + text: "+ Add another address", + ), + // GestureDetector( + // + // child: Text( + // "+ Add another address", + // style: STextStyles.largeMedium14(context), + // ), + // ), + const SizedBox( + height: 16, + ), + const Spacer(), + Row( + children: [ + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Builder( + builder: (context) { + bool nameExists = ref + .watch(contactNameIsNotEmptyStateProvider + .state) + .state; + + bool validForms = ref.watch( + validContactStateProvider(forms + .map((e) => e.id) + .toList(growable: false))); + + bool shouldEnableSave = + validForms && nameExists; + + return TextButton( + style: shouldEnableSave + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor( + context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor( + context), + onPressed: shouldEnableSave + ? () async { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75), + ); + } + List entries = + []; + for (int i = 0; + i < forms.length; + i++) { + entries.add(ref + .read( + addressEntryDataProvider( + forms[i].id)) + .buildAddressEntry()); + } + Contact contact = Contact( + emojiChar: _selectedEmoji?.char, + name: nameController.text, + addresses: entries, + isFavorite: _isFavorite, + ); + + if (await ref + .read( + addressBookServiceProvider) + .addContact(contact)) { + if (mounted) { + Navigator.of(context).pop(); + } + // TODO show success notification + } else { + // TODO show error notification + } + } + : null, + child: Text( + "Save", + style: + STextStyles.button(context).copyWith( + color: shouldEnableSave + ? Theme.of(context) + .extension()! + .buttonTextPrimary + : Theme.of(context) + .extension()! + .buttonTextPrimaryDisabled, + ), + ), + ); + }, + ), + ), + ], + ), + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } From e0555f53a437d37e8c5c951de2c9c7f90448b7eb Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 16:00:09 -0700 Subject: [PATCH 046/225] WIP: desktop address book displays contacts --- .../address_book_views/address_book_view.dart | 553 +++++++++--------- .../desktop_address_book.dart | 31 +- 2 files changed, 288 insertions(+), 296 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index c9dd72d72..08c548627 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -13,7 +13,9 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -21,8 +23,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class AddressBookView extends ConsumerStatefulWidget { const AddressBookView({Key? key, this.coin}) : super(key: key); @@ -103,288 +103,287 @@ class _AddressBookViewState extends ConsumerState { final addressBookEntriesFuture = ref.watch( addressBookServiceProvider.select((value) => value.addressBookEntries)); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Address book", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, + final isDesktop = Util.isDesktop; + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addressBookFilterViewButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.filter, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, + title: Text( + "Address book", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, ), - onPressed: () { - Navigator.of(context).pushNamed( - AddressBookFilterView.routeName, - ); - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addressBookAddNewContactViewButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - Navigator.of(context).pushNamed( - AddAddressBookEntryView.routeName, - ); - }, - ), - ), - ), - ], - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (value) { - setState(() { - _searchTerm = value; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 16, - ), - Text( - "Favorites", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - _cacheFav = snapshot.data!; - } - if (_cacheFav == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cacheFav!.isNotEmpty) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ..._cacheFav! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider - .select((value) => value - .coins - .contains(e.coin)))) - .isNotEmpty) - .where((e) => - e.isFavorite && - ref - .read( - addressBookServiceProvider) - .matches(_searchTerm, e)) - .where( - (element) => element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key( - "favContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), - ); - } - } - }, - ), - const SizedBox( - height: 16, - ), - Text( - "All contacts", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - _cache = snapshot.data!; - } - if (_cache == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cache!.isNotEmpty) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ..._cache! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider - .select((value) => value - .coins - .contains(e.coin)))) - .isNotEmpty) - .where((e) => ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where( - (element) => !element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key( - "contactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), - ); - } - } - }, - ), - ], + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addressBookFilterViewButton"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.filter, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, ), + onPressed: () { + Navigator.of(context).pushNamed( + AddressBookFilterView.routeName, + ); + }, ), ), ), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addressBookAddNewContactViewButton"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddAddressBookEntryView.routeName, + ); + }, + ), + ), + ), + ], + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ); - }, + child: !isDesktop + ? TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ) + : null, + ), + if (!isDesktop) const SizedBox(height: 16), + Text( + "Favorites", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: addressBookEntriesFuture, + builder: (_, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cacheFav = snapshot.data!; + } + if (_cacheFav == null) { + // TODO proper loading animation + return const LoadingIndicator(); + } else { + if (_cacheFav!.isNotEmpty) { + return RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ..._cacheFav! + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select((value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ); + } + } + }, + ), + const SizedBox( + height: 16, + ), + Text( + "All contacts", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: addressBookEntriesFuture, + builder: (_, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cache = snapshot.data!; + } + if (_cache == null) { + // TODO proper loading animation + return const LoadingIndicator(); + } else { + if (_cache!.isNotEmpty) { + return RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ..._cache! + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select((value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => !element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("desktopContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ); + } + } + }, + ), + ], ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index e375bbcc7..475650722 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; @@ -9,11 +10,11 @@ import 'package:stackwallet/utilities/assets.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/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -36,7 +37,7 @@ class _DesktopAddressBook extends ConsumerState { late bool hasContacts = false; - String filter = ""; + String _searchTerm = ""; Future selectCryptocurrency() async { await showDialog( @@ -123,25 +124,25 @@ class _DesktopAddressBook extends ConsumerState { Constants.size.circularBorderRadius, ), child: TextField( - autocorrect: false, - enableSuggestions: false, + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, controller: _searchController, focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => filter = newString); + onChanged: (value) { + setState(() { + _searchTerm = value; + }); }, style: STextStyles.field(context), decoration: standardInputDecoration( - "Search...", + "Search", _searchFocusNode, context, ).copyWith( - labelStyle: STextStyles.fieldLabel(context) - .copyWith(fontSize: 16), prefixIcon: Padding( padding: const EdgeInsets.symmetric( horizontal: 10, - vertical: 16, + vertical: 20, ), child: SvgPicture.asset( Assets.svg.search, @@ -160,7 +161,6 @@ class _DesktopAddressBook extends ConsumerState { onTap: () async { setState(() { _searchController.text = ""; - filter = ""; }); }, ), @@ -243,14 +243,7 @@ class _DesktopAddressBook extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 26), child: SizedBox( width: 489, - child: RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), - ), + child: AddressBookView(), ), ), ], From 3a7f1f9c49854c035e66c4254050a4c7dbfa67e7 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 19:27:36 -0700 Subject: [PATCH 047/225] layout fix for new contact --- .../subviews/add_address_book_entry_view.dart | 659 ++++++++++++------ .../new_contact_address_entry_form.dart | 6 +- 2 files changed, 464 insertions(+), 201 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 588ca5b26..5835c80cd 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -224,7 +224,11 @@ class _AddAddressBookEntryViewState const DesktopDialogCloseButton(), ], ), - Expanded(child: child), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: child, + )), ], ); }, @@ -248,216 +252,473 @@ class _AddAddressBookEntryViewState child: IntrinsicHeight( child: Column( children: [ - const SizedBox( - height: 4, - ), - GestureDetector( - onTap: () { - if (_selectedEmoji != null) { - setState(() { - _selectedEmoji = null; - }); - return; - } + if (!isDesktop) const SizedBox(height: 4), + isDesktop + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + if (_selectedEmoji != null) { + setState(() { + _selectedEmoji = null; + }); + return; + } - ///TODO if desktop make dialog - !isDesktop - ? showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => const EmojiSelectSheet(), - ).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }) - : showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 700, - maxWidth: 700, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: - const EdgeInsets.all(32), - child: Text( - "Select emoji", - style: - STextStyles.desktopH3( - context), - textAlign: TextAlign.center, - ), + ///TODO if desktop make dialog + !isDesktop + ? showModalBottomSheet( + backgroundColor: + Colors.transparent, + context: context, + shape: + const RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical( + top: Radius.circular(20), ), - ], - ), - Expanded( - child: LayoutBuilder( - builder: - (context, constraints) { - return SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: ConstrainedBox( - constraints: - BoxConstraints( - minHeight: constraints - .maxHeight, - minWidth: constraints - .maxWidth, - ), - child: IntrinsicHeight( - child: Column( - children: const [ - Padding( - padding: EdgeInsets - .symmetric( - horizontal: - 32), - // child: - // EmojiSelectSheet(), + ), + builder: (_) => + const EmojiSelectSheet(), + ).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }) + : showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 700, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: + const EdgeInsets + .all(32), + child: Text( + "Select emoji", + style: STextStyles + .desktopH3( + context), + textAlign: + TextAlign + .center, ), - ], + ), + ], + ), + Expanded( + child: LayoutBuilder( + builder: (context, + constraints) { + return SingleChildScrollView( + scrollDirection: + Axis.vertical, + child: + ConstrainedBox( + constraints: + BoxConstraints( + minHeight: + constraints + .maxHeight, + minWidth: + constraints + .maxWidth, + ), + child: + IntrinsicHeight( + child: Column( + children: const [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: 32), + // child: + // EmojiSelectSheet(), + ), + ], + ), + ), + ), + ); + }, ), ), + ], + ), + ); + }).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + }, + child: SizedBox( + height: 56, + width: 56, + child: Stack( + children: [ + Container( + height: 56, + width: 56, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.user, + height: 30, + width: 30, + ) + : Text( + _selectedEmoji!.char, + style: STextStyles + .pageTitleH1(context), ), - ); - }, + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Container( + height: 14, + width: 14, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(14), + color: Theme.of(context) + .extension()! + .accentColorDark), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + width: 12, + height: 12, + ) + : SvgPicture.asset( + Assets.svg.thickX, + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + width: 8, + height: 8, + ), ), ), - ], - ), - ); - }).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); - }, - child: SizedBox( - height: 48, - width: 48, - child: Stack( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, ) - : Text( - _selectedEmoji!.char, - style: STextStyles.pageTitleH1( - context), - ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Container( - height: 14, - width: 14, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - color: Theme.of(context) - .extension()! - .accentColorDark), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension()! - .textWhite, - width: 12, - height: 12, - ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension()! - .textWhite, - width: 8, - height: 8, - ), + ], + ), ), ), - ) - ], - ), - ), - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: nameController, - focusNode: nameFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter contact name", - nameFocusNode, - context, - ).copyWith( - suffixIcon: ref - .read(contactNameIsNotEmptyStateProvider - .state) - .state - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - nameController.text = ""; - }); - }, - ), - ], - ), + const SizedBox(width: 8), + SizedBox( + width: isDesktop ? 450 : null, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ) - : null, - ), - onChanged: (newValue) { - ref - .read( - contactNameIsNotEmptyStateProvider.state) - .state = newValue.isNotEmpty; - }, - ), - ), + child: TextField( + autocorrect: + Util.isDesktop ? false : true, + enableSuggestions: + Util.isDesktop ? false : true, + controller: nameController, + focusNode: nameFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter contact name", + nameFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: ref + .read( + contactNameIsNotEmptyStateProvider + .state) + .state + ? Padding( + padding: + const EdgeInsets.only( + right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + nameController + .text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + onChanged: (newValue) { + ref + .read( + contactNameIsNotEmptyStateProvider + .state) + .state = newValue.isNotEmpty; + }, + ), + ), + ), + ], + ) + : Column( + children: [ + GestureDetector( + onTap: () { + if (_selectedEmoji != null) { + setState(() { + _selectedEmoji = null; + }); + return; + } + + ///TODO if desktop make dialog + !isDesktop + ? showModalBottomSheet( + backgroundColor: + Colors.transparent, + context: context, + shape: + const RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + const EmojiSelectSheet(), + ).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }) + : showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 700, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: + const EdgeInsets + .all(32), + child: Text( + "Select emoji", + style: STextStyles + .desktopH3( + context), + textAlign: + TextAlign + .center, + ), + ), + ], + ), + Expanded( + child: LayoutBuilder( + builder: (context, + constraints) { + return SingleChildScrollView( + scrollDirection: + Axis.vertical, + child: + ConstrainedBox( + constraints: + BoxConstraints( + minHeight: + constraints + .maxHeight, + minWidth: + constraints + .maxWidth, + ), + child: + IntrinsicHeight( + child: Column( + children: const [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: 32), + // child: + // EmojiSelectSheet(), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], + ), + ); + }).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + }, + child: SizedBox( + height: 48, + width: 48, + child: Stack( + children: [ + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.user, + height: 24, + width: 24, + ) + : Text( + _selectedEmoji!.char, + style: STextStyles + .pageTitleH1(context), + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Container( + height: 14, + width: 14, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(14), + color: Theme.of(context) + .extension()! + .accentColorDark), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + width: 12, + height: 12, + ) + : SvgPicture.asset( + Assets.svg.thickX, + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + width: 8, + height: 8, + ), + ), + ), + ) + ], + ), + ), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: + Util.isDesktop ? false : true, + enableSuggestions: + Util.isDesktop ? false : true, + controller: nameController, + focusNode: nameFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter contact name", + nameFocusNode, + context, + ).copyWith( + suffixIcon: ref + .read( + contactNameIsNotEmptyStateProvider + .state) + .state + ? Padding( + padding: const EdgeInsets.only( + right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + nameController + .text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + onChanged: (newValue) { + ref + .read( + contactNameIsNotEmptyStateProvider + .state) + .state = newValue.isNotEmpty; + }, + ), + ), + ], + ), + if (!isDesktop) const SizedBox(height: 8), if (forms.length <= 1) const SizedBox( height: 8, diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index ce98cee10..b6cf0aad4 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -14,14 +14,13 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class NewContactAddressEntryForm extends ConsumerStatefulWidget { const NewContactAddressEntryForm({ Key? key, @@ -70,6 +69,7 @@ class _NewContactAddressEntryFormState @override Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; return Column( children: [ TextField( @@ -168,6 +168,7 @@ class _NewContactAddressEntryFormState addressLabelFocusNode, context, ).copyWith( + labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: addressLabelController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), @@ -212,6 +213,7 @@ class _NewContactAddressEntryFormState addressFocusNode, context, ).copyWith( + labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ From e372db770860164485ae2ffd2c34fc2e14fc5ec6 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 14 Nov 2022 20:55:11 -0700 Subject: [PATCH 048/225] height size issue --- .../address_book_views/address_book_view.dart | 351 +++++++++--------- 1 file changed, 183 insertions(+), 168 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 08c548627..50e51110b 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -209,181 +209,196 @@ class _AddressBookViewState extends ConsumerState { ), ); }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: !isDesktop - ? TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (value) { - setState(() { - _searchTerm = value; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height - 271, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: !isDesktop + ? TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], + ) + : null, + ), + if (!isDesktop) const SizedBox(height: 16), + Text( + "Favorites", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: addressBookEntriesFuture, + builder: (_, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cacheFav = snapshot.data!; + } + if (_cacheFav == null) { + // TODO proper loading animation + return const LoadingIndicator(); + } else { + if (_cacheFav!.isNotEmpty) { + return RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ..._cacheFav! + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select( + (value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, ), ), - ) - : null, - ), - ) - : null, - ), - if (!isDesktop) const SizedBox(height: 16), - Text( - "Favorites", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - _cacheFav = snapshot.data!; - } - if (_cacheFav == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cacheFav!.isNotEmpty) { - return RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Column( - children: [ - ..._cacheFav! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select((value) => - value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => - e.isFavorite && - ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), + ], ), - ), - ); - } - } - }, - ), - const SizedBox( - height: 16, - ), - Text( - "All contacts", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - _cache = snapshot.data!; - } - if (_cache == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cache!.isNotEmpty) { - return RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Column( - children: [ - ..._cache! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select((value) => - value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => !element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("desktopContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), ), - ), - ); + ); + } } - } - }, - ), - ], + }, + ), + const SizedBox( + height: 16, + ), + Text( + "All contacts", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: addressBookEntriesFuture, + builder: (_, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cache = snapshot.data!; + } + if (_cache == null) { + // TODO proper loading animation + return const LoadingIndicator(); + } else { + if (_cache!.isNotEmpty) { + return Column( + children: [ + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + ..._cache! + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select( + (value) => value.coins + .contains(e.coin)))) + .isNotEmpty) + .where((e) => ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => !element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key( + "desktopContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ), + ), + ], + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ); + } + } + }, + ), + ], + ), ), ); } From 591edeca63a2b7899e908ef9d493d2dc3ef09a6c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 14 Nov 2022 20:40:44 -0800 Subject: [PATCH 049/225] Fix RFC6068 mailto link for support on desktop plaftorm. --- .../settings_views/global_settings_view/support_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 20aeedf61..6bf3544ae 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -330,7 +330,7 @@ class SupportView extends StatelessWidget { onPressed: () { if (!isDesktop) { launchUrl( - Uri.parse("mailto://support@stackwallet.com"), + Uri.parse("mailto:support@stackwallet.com"), mode: LaunchMode.externalApplication, ); } @@ -367,7 +367,7 @@ class SupportView extends StatelessWidget { text: isDesktop ? "support@stackwallet.com" : "", onTap: () { launchUrl( - Uri.parse("mailto://support@stackwallet.com"), + Uri.parse("mailto:support@stackwallet.com"), mode: LaunchMode.externalApplication, ); }, From 4a908564980fc4e426df61479a36192447aaa9e1 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 12:12:38 -0700 Subject: [PATCH 050/225] WIP: node settings scrollable --- .../home/settings_menu/nodes_settings.dart | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart index 1d0317037..2343e3990 100644 --- a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart @@ -11,8 +11,10 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; class NodesSettings extends ConsumerStatefulWidget { const NodesSettings({Key? key}) : super(key: key); @@ -29,6 +31,8 @@ class _NodesSettings extends ConsumerState { late final TextEditingController searchNodeController; late final FocusNode searchNodeFocusNode; + late final ScrollController nodeScrollController; + String filter = ""; @override @@ -39,6 +43,8 @@ class _NodesSettings extends ConsumerState { searchNodeController = TextEditingController(); searchNodeFocusNode = FocusNode(); + nodeScrollController = ScrollController(); + super.initState(); } @@ -134,6 +140,26 @@ class _NodesSettings extends ConsumerState { height: 16, ), ), + suffixIcon: searchNodeController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + searchNodeController.text = ""; + filter = ""; + }); + }, + ), + ], + ), + ), + ) + : null, ), ), ), @@ -145,6 +171,9 @@ class _NodesSettings extends ConsumerState { borderColor: Theme.of(context).extension()!.background, child: ListView.separated( + controller: nodeScrollController, + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.vertical, primary: false, shrinkWrap: true, itemBuilder: (context, index) { From 8adec7ba5cba884d14181b78ad7056d5714cf21b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 12:29:08 -0700 Subject: [PATCH 051/225] auto frequency dark mode text fix --- .../stack_backup_views/edit_auto_backup_view.dart | 8 +++++++- .../backup_and_restore/create_auto_backup.dart | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 3b6d87b52..76d280980 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -424,6 +424,7 @@ class _EditAutoBackupViewState extends ConsumerState { passwordFocusNode, context, ).copyWith( + labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ @@ -555,6 +556,7 @@ class _EditAutoBackupViewState extends ConsumerState { passwordRepeatFocusNode, context, ).copyWith( + labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null, suffixIcon: UnconstrainedBox( child: Row( children: [ @@ -631,7 +633,11 @@ class _EditAutoBackupViewState extends ConsumerState { return DropdownMenuItem( value: e, - child: Text(message), + child: Text( + message, + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), ); }, ), diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 5e7e86fe1..663136dba 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -492,7 +492,11 @@ class _CreateAutoBackup extends ConsumerState { return DropdownMenuItem( value: e, - child: Text(message), + child: Text( + message, + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), ); }, ), From a5d925fb98e8ab0c00cf37cafec6d20ba4f4d214 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 13:42:48 -0700 Subject: [PATCH 052/225] WIP: manual backup nav route error --- .../create_backup_view.dart | 121 ++++++++++++++++-- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index b7ee6b4be..821504bf8 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; @@ -523,7 +524,7 @@ class _RestoreFromFileViewState extends State { if (mounted) { // pop encryption progress dialog - Navigator.of(context).pop(); + if (!isDesktop) Navigator.of(context).pop(); if (result) { await showDialog( @@ -607,14 +608,52 @@ class _RestoreFromFileViewState extends State { return; } - unawaited(showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting backup", - message: "This shouldn't take long", + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) { + if (Util.isDesktop) { + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all( + 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Encrypting initial backup", + style: + STextStyles.desktopH3( + context), + ), + const SizedBox( + height: 40, + ), + Text( + "This shouldn't take long", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ], + ), + ), + ); + } else { + return const StackDialog( + title: "Encrypting initial backup", + message: "This shouldn't take long", + ); + } + }, ), - )); + ); // make sure the dialog is able to be displayed for at least 1 second await Future.delayed( const Duration(seconds: 1)); @@ -637,7 +676,7 @@ class _RestoreFromFileViewState extends State { if (mounted) { // pop encryption progress dialog - Navigator.of(context).pop(); + if (!isDesktop) Navigator.of(context).pop(); if (result) { await showDialog( @@ -648,9 +687,67 @@ class _RestoreFromFileViewState extends State { title: "Backup saved to:", message: fileToSave, ) - : const StackOkDialog( - title: - "Backup creation succeeded"), + : !isDesktop + ? const StackOkDialog( + title: + "Backup creation succeeded") + : DesktopDialog( + maxHeight: double.infinity, + maxWidth: 500, + child: Padding( + padding: + const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + const SizedBox( + height: 26), + Text( + "Stack backup saved to: \n", + style: STextStyles + .desktopH3( + context), + ), + Text( + fileToSave, + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + const SizedBox( + height: 40, + ), + Row( + children: [ + // const Spacer(), + Expanded( + child: + PrimaryButton( + label: "Ok", + desktopMed: + true, + onPressed: + () { + // Navigator.of( + // context) + // .pop(); + }, + ), + ), + ], + ) + ], + ), + ), + ), ); passwordController.text = ""; passwordRepeatController.text = ""; From 2ec1bda6f2ff3b51f893ed8d4793249a20f1a715 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 13:45:06 -0700 Subject: [PATCH 053/225] desktop address book contact buttons --- lib/widgets/address_book_card.dart | 180 ++++++++++++++--------------- 1 file changed, 88 insertions(+), 92 deletions(-) diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index cebcf166f..c9ac86052 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -9,7 +9,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class AddressBookCard extends ConsumerStatefulWidget { @@ -59,111 +58,108 @@ class _AddressBookCardState extends ConsumerState { } } - return ConditionalParent( - condition: !isDesktop, - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: contact.id == "default" - ? Theme.of(context) - .extension()! - .myStackContactIconBG - : Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(32), + return RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => ContactPopUp( + contactId: contact.id, ), - child: contact.id == "default" - ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 20, - ), - ) - : contact.emojiChar != null + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Theme.of(context) + .extension()! + .myStackContactIconBG + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), + ), + child: contact.id == "default" ? Center( - child: Text(contact.emojiChar!), - ) - : Center( child: SvgPicture.asset( - Assets.svg.user, - width: 18, + Assets.svg.stackIcon(context), + width: 20, ), - ), - ), - const SizedBox( - width: 12, - ), - if (isDesktop) - Text( - contact.name, - style: STextStyles.itemSubtitle12(context), - ), - if (isDesktop) - const SizedBox( - width: 16, - ), - if (isDesktop) - Text( - coinsString, - style: STextStyles.label(context), - ), - if (!isDesktop) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + ) + : contact.emojiChar != null + ? Center( + child: Text(contact.emojiChar!), + ) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 12, + ), + if (isDesktop) Text( contact.name, style: STextStyles.itemSubtitle12(context), ), + if (isDesktop) const SizedBox( - height: 4, + width: 16, ), + if (isDesktop) Text( coinsString, style: STextStyles.label(context), ), - ], - ), - if (isDesktop) const Spacer(), - if (isDesktop) - SvgPicture.asset( - widget.indicatorDown == true - ? Assets.svg.chevronDown - : Assets.svg.chevronUp, - width: 10, - height: 5, - color: Theme.of(context).extension()!.textSubtitle2, - ), - ], - ), - builder: (child) => RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => ContactPopUp( - contactId: contact.id, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: child, + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + contact.name, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 4, + ), + Text( + coinsString, + style: STextStyles.label(context), + ), + ], + ), + if (isDesktop) const Spacer(), + // if (isDesktop) + // SvgPicture.asset( + // widget.indicatorDown == true + // ? Assets.svg.chevronDown + // : Assets.svg.chevronUp, + // width: 10, + // height: 5, + // color: + // Theme.of(context).extension()!.textSubtitle2, + // ), + ], ), ), ), From d326c10f4236a071e55a7bb8eb63873a57103f06 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 11:59:53 -0600 Subject: [PATCH 054/225] desktop login loading indicator --- .../desktop_login_view.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index 93a281bf8..363c1fb0d 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; class DesktopLoginView extends ConsumerStatefulWidget { @@ -45,6 +46,15 @@ class _DesktopLoginViewState extends ConsumerState { Future login() async { try { + unawaited( + showDialog( + context: context, + builder: (context) => const LoadingIndicator( + width: 200, + ), + ), + ); + await ref .read(storageCryptoHandlerProvider) .initFromExisting(passwordController.text); @@ -55,6 +65,9 @@ class _DesktopLoginViewState extends ConsumerState { // if no errors passphrase is correct if (mounted) { + // pop loading indicator + Navigator.of(context).pop(); + unawaited( Navigator.of(context).pushNamedAndRemoveUntil( DesktopHomeView.routeName, @@ -63,6 +76,9 @@ class _DesktopLoginViewState extends ConsumerState { ); } } catch (e) { + // pop loading indicator + Navigator.of(context).pop(); + await showFloatingFlushBar( type: FlushBarType.warning, message: e.toString(), From a48975940d2b1eeef817c4353718c246663c477f Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 12:16:12 -0600 Subject: [PATCH 055/225] desktop wallet keys and net info button highlight --- .../wallet_view/desktop_wallet_view.dart | 4 +- .../sub_widgets/network_info_button.dart | 39 +++++++++++++------ .../sub_widgets/wallet_keys_button.dart | 22 +++++------ 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 97efb2f68..81b531632 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -292,13 +292,13 @@ class _DesktopWalletViewState extends ConsumerState { eventBus: eventBus, ), const SizedBox( - width: 32, + width: 2, ), WalletKeysButton( walletId: walletId, ), const SizedBox( - width: 32, + width: 12, ), ], ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart index c001f9bf3..59d20a9df 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart @@ -98,25 +98,26 @@ class _NetworkInfoButtonState extends ConsumerState { Widget _buildNetworkIcon(WalletSyncStatus status, BuildContext context) { const size = 24.0; + final color = _getColor(status, context); switch (status) { case WalletSyncStatus.unableToSync: return SvgPicture.asset( Assets.svg.radioProblem, - color: Theme.of(context).extension()!.accentColorRed, + color: color, width: size, height: size, ); case WalletSyncStatus.synced: return SvgPicture.asset( Assets.svg.radio, - color: Theme.of(context).extension()!.accentColorGreen, + color: color, width: size, height: size, ); case WalletSyncStatus.syncing: return SvgPicture.asset( Assets.svg.radioSyncing, - color: Theme.of(context).extension()!.accentColorYellow, + color: color, width: size, height: size, ); @@ -125,35 +126,46 @@ class _NetworkInfoButtonState extends ConsumerState { Widget _buildText(WalletSyncStatus status, BuildContext context) { String label; - Color color; switch (status) { case WalletSyncStatus.unableToSync: label = "Unable to sync"; - color = Theme.of(context).extension()!.accentColorRed; break; case WalletSyncStatus.synced: label = "Synchronised"; - color = Theme.of(context).extension()!.accentColorGreen; break; case WalletSyncStatus.syncing: label = "Synchronising"; - color = Theme.of(context).extension()!.accentColorYellow; break; } return Text( label, style: STextStyles.desktopMenuItemSelected(context).copyWith( - color: color, + color: _getColor(status, context), ), ); } + Color _getColor(WalletSyncStatus status, BuildContext context) { + switch (status) { + case WalletSyncStatus.unableToSync: + return Theme.of(context).extension()!.accentColorRed; + case WalletSyncStatus.synced: + return Theme.of(context).extension()!.accentColorGreen; + case WalletSyncStatus.syncing: + return Theme.of(context).extension()!.accentColorYellow; + } + } + @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { + return RawMaterialButton( + hoverColor: _getColor(_currentSyncStatus, context).withOpacity(0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: () { if (Util.isDesktop) { // showDialog( // context: context, @@ -265,8 +277,11 @@ class _NetworkInfoButtonState extends ConsumerState { ); } }, - child: Container( - color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 32, + ), child: Row( children: [ _buildNetworkIcon(_currentSyncStatus, context), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart index 649433d52..32994dcb9 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart @@ -16,8 +16,11 @@ class WalletKeysButton extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { + return RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: () { showDialog( context: context, barrierDismissible: false, @@ -36,17 +39,12 @@ class WalletKeysButton extends StatelessWidget { }, ), ); - - // showDialog( - // context: context, - // barrierDismissible: false, - // builder: (context) => UnlockWalletKeysDesktop( - // walletId: walletId, - // ), - // ); }, - child: Container( - color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 19, + horizontal: 32, + ), child: Row( children: [ SvgPicture.asset( From 5211617082c1c458c7cf85ce19d2f64697a53cf5 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 14:56:05 -0600 Subject: [PATCH 056/225] WIP desktop fee dropdown --- .../sub_widgets/desktop_fee_dropdown.dart | 369 ++++++++++++++++++ .../wallet_view/sub_widgets/desktop_send.dart | 72 ++-- 2 files changed, 407 insertions(+), 34 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart new file mode 100644 index 000000000..9acb3a6f9 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart @@ -0,0 +1,369 @@ +import 'package:decimal/decimal.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; +import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_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/animated_text.dart'; + +class DesktopFeeDropDown extends ConsumerStatefulWidget { + const DesktopFeeDropDown({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => _DesktopFeeDropDownState(); +} + +class _DesktopFeeDropDownState extends ConsumerState { + late final String walletId; + + FeeObject? feeObject; + FeeRateType feeRateType = FeeRateType.average; + + final stringsToLoopThrough = [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ]; + + Future feeFor({ + required int amount, + required FeeRateType feeRateType, + required int feeRate, + required Coin coin, + }) async { + switch (feeRateType) { + case FeeRateType.fast: + if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + Format.satoshisToAmount(await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate)); + } else { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + Format.satoshisToAmount( + await manager.estimateFeeFor(amount, feeRate)); + } + } + return ref.read(feeSheetSessionCacheProvider).fast[amount]!; + + case FeeRateType.average: + if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).average[amount] = + Format.satoshisToAmount(await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate)); + } else { + ref.read(feeSheetSessionCacheProvider).average[amount] = + Format.satoshisToAmount( + await manager.estimateFeeFor(amount, feeRate)); + } + } + return ref.read(feeSheetSessionCacheProvider).average[amount]!; + + case FeeRateType.slow: + if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + Format.satoshisToAmount(await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate)); + } else { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + Format.satoshisToAmount( + await manager.estimateFeeFor(amount, feeRate)); + } + } + return ref.read(feeSheetSessionCacheProvider).slow[amount]!; + } + } + + String estimatedTimeToBeIncludedInNextBlock( + int targetBlockTime, int estimatedNumberOfBlocks) { + int time = targetBlockTime * estimatedNumberOfBlocks; + + int hours = (time / 3600).floor(); + if (hours > 1) { + return "~$hours hours"; + } else if (hours == 1) { + return "~$hours hour"; + } + + // less than an hour + + final string = (time / 60).toStringAsFixed(1); + + if (string == "1.0") { + return "~1 minute"; + } else { + if (string.endsWith(".0")) { + return "~${(time / 60).floor()} minutes"; + } + return "~$string minutes"; + } + } + + @override + void initState() { + walletId = widget.walletId; + super.initState(); + } + + String? labelSlow; + String? labelAverage; + String? labelFast; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))); + + return FutureBuilder( + future: manager.fees, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + feeObject = snapshot.data!; + } + return DropdownButtonHideUnderline( + child: DropdownButton2( + offset: const Offset(0, -10), + isExpanded: true, + dropdownElevation: 0, + value: ref.watch(feeRateTypeStateProvider.state).state, + // selectedItemBuilder: (s) { + // return [ + // ...FeeRateType.values.map( + // (e) => DropdownMenuItem( + // value: e, + // child: FeeDropDownChild( + // feeObject: feeObject, + // feeRateType: e, + // walletId: walletId, + // amount: amount, + // feeFor: feeFor, + // isSelected: true, + // ), + // ), + // ), + // ]; + // }, + items: [ + ...FeeRateType.values.map( + (e) => DropdownMenuItem( + value: e, + child: FeeDropDownChild( + feeObject: feeObject, + feeRateType: e, + walletId: walletId, + feeFor: feeFor, + isSelected: false, + ), + ), + ), + ], + onChanged: (newRateType) { + if (newRateType is FeeRateType) { + ref.read(feeRateTypeStateProvider.state).state = newRateType; + } + }, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context).extension()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ); + }); + } +} + +final sendAmountProvider = + StateProvider.autoDispose((_) => Decimal.zero); + +class FeeDropDownChild extends ConsumerWidget { + const FeeDropDownChild({ + Key? key, + required this.feeObject, + required this.feeRateType, + required this.walletId, + required this.feeFor, + required this.isSelected, + }) : super(key: key); + + final FeeObject? feeObject; + final FeeRateType feeRateType; + final String walletId; + final Future Function({ + required int amount, + required FeeRateType feeRateType, + required int feeRate, + required Coin coin, + }) feeFor; + final bool isSelected; + + static const stringsToLoopThrough = [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ]; + + String estimatedTimeToBeIncludedInNextBlock( + int targetBlockTime, int estimatedNumberOfBlocks) { + int time = targetBlockTime * estimatedNumberOfBlocks; + + int hours = (time / 3600).floor(); + if (hours > 1) { + return "~$hours hours"; + } else if (hours == 1) { + return "~$hours hour"; + } + + // less than an hour + + final string = (time / 60).toStringAsFixed(1); + + if (string == "1.0") { + return "~1 minute"; + } else { + if (string.endsWith(".0")) { + return "~${(time / 60).floor()} minutes"; + } + return "~$string minutes"; + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + debugPrint("BUILD: $runtimeType : $feeRateType"); + + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))); + + if (feeObject == null) { + return AnimatedText( + stringsToLoopThrough: stringsToLoopThrough, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textFieldActiveText, + ), + ); + } else { + return FutureBuilder( + future: feeFor( + coin: manager.coin, + feeRateType: FeeRateType.fast, + feeRate: feeRateType == FeeRateType.fast + ? feeObject!.fast + : feeRateType == FeeRateType.slow + ? feeObject!.slow + : feeObject!.medium, + amount: Format.decimalAmountToSatoshis( + ref.watch(sendAmountProvider.state).state, + ), + ), + builder: (_, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${feeRateType.prettyName} (~${snapshot.data!} ${manager.coin.ticker})", + style: + STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ), + textAlign: TextAlign.left, + ), + if (feeObject != null) + Text( + estimatedTimeToBeIncludedInNextBlock( + Constants.targetBlockTimeInSeconds(manager.coin), + feeRateType == FeeRateType.fast + ? feeObject!.numberOfBlocksFast + : feeRateType == FeeRateType.slow + ? feeObject!.numberOfBlocksSlow + : feeObject!.numberOfBlocksAverage, + ), + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ), + ], + ); + } else { + return AnimatedText( + stringsToLoopThrough: stringsToLoopThrough, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ), + ); + } + }, + ); + } + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 071122def..cd19c53f8 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dia import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; @@ -94,11 +95,6 @@ class _DesktopSendState extends ConsumerState { late VoidCallback onCryptoAmountChanged; Future previewSend() async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); @@ -794,35 +790,25 @@ class _DesktopSendState extends ConsumerState { _addressToggleFlag = true; } - // _cryptoFocus.addListener(() { - // if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - // if (_amountToSend == null) { - // setState(() { - // _calculateFeesFuture = calculateFees(0); - // }); - // } else { - // setState(() { - // _calculateFeesFuture = - // calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); - // }); - // } - // } - // }); - // - // _baseFocus.addListener(() { - // if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - // if (_amountToSend == null) { - // setState(() { - // _calculateFeesFuture = calculateFees(0); - // }); - // } else { - // setState(() { - // _calculateFeesFuture = - // calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); - // }); - // } - // } - // }); + _cryptoFocus.addListener(() { + if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + if (_amountToSend == null) { + ref.refresh(sendAmountProvider); + } else { + ref.read(sendAmountProvider.state).state = _amountToSend!; + } + } + }); + + _baseFocus.addListener(() { + if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + if (_amountToSend == null) { + ref.refresh(sendAmountProvider); + } else { + ref.read(sendAmountProvider.state).state = _amountToSend!; + } + } + }); super.initState(); } @@ -1371,6 +1357,24 @@ class _DesktopSendState extends ConsumerState { ), ), ), + const SizedBox( + height: 20, + ), + Text( + "Transaction fee (estimated)", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + DesktopFeeDropDown( + walletId: walletId, + ), const SizedBox( height: 36, ), From aead30504e36b2ceb7ad42dbb56ab50bb6945f14 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 14:12:55 -0700 Subject: [PATCH 057/225] alignment for settings pages icons --- assets/svg/configuration.svg | 11 + .../desktop_address_book.dart | 4 +- .../advanced_settings/advanced_settings.dart | 11 +- .../settings_menu/appearance_settings.dart | 11 +- .../currency_settings/currency_settings.dart | 25 +- .../language_settings/language_settings.dart | 11 +- .../home/settings_menu/nodes_settings.dart | 11 +- .../home/settings_menu/security_settings.dart | 765 +++++++++--------- .../syncing_preferences_settings.dart | 14 +- lib/utilities/assets.dart | 1 + pubspec.yaml | 1 + 11 files changed, 442 insertions(+), 423 deletions(-) create mode 100644 assets/svg/configuration.svg diff --git a/assets/svg/configuration.svg b/assets/svg/configuration.svg new file mode 100644 index 000000000..516bbf320 --- /dev/null +++ b/assets/svg/configuration.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 475650722..407ed9897 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -239,8 +239,8 @@ class _DesktopAddressBook extends ConsumerState { ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 26), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 26), child: SizedBox( width: 489, child: AddressBookView(), diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart index 8e89e3f67..0991a14ca 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart @@ -35,10 +35,13 @@ class _AdvancedSettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.circleLanguage, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleSliders, + width: 48, + height: 48, + ), ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index bf6fc81e2..27a10ca80 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -52,10 +52,13 @@ class _AppearanceOptionSettings child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.circleSun, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleSun, + width: 48, + height: 48, + ), ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index dab613f63..b1581d971 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -32,10 +32,13 @@ class _CurrencySettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.circleDollarSign, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleDollarSign, + width: 48, + height: 48, + ), ), Center( child: Padding( @@ -67,7 +70,7 @@ class _CurrencySettings extends ConsumerState { padding: EdgeInsets.all( 10, ), - child: NewPasswordButton(), + child: changeCurrency(), ), ], ), @@ -80,19 +83,11 @@ class _CurrencySettings extends ConsumerState { } } -class NewPasswordButton extends ConsumerWidget { - const NewPasswordButton({ +class changeCurrency extends ConsumerWidget { + const changeCurrency({ Key? key, }) : super(key: key); Future chooseCurrency(BuildContext context) async { - // await showDialog( - // context: context, - // useSafeArea: false, - // barrierDismissible: true, - // builder: (context) { - // return CurrencyDialog(); - // }, - // ); await showDialog( context: context, useSafeArea: false, diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index 0f66d7dd5..2047d4eff 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -31,10 +31,13 @@ class _LanguageOptionSettings extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.circleLanguage, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleLanguage, + width: 48, + height: 48, + ), ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart index 2343e3990..0f539f9f3 100644 --- a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart @@ -79,10 +79,13 @@ class _NodesSettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - Assets.svg.circleNode, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleNode, + width: 48, + height: 48, + ), ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index d752ece38..01a6c9c9b 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -139,392 +139,389 @@ class _SecuritySettings extends ConsumerState { debugPrint("BUILD: $runtimeType"); return Column( children: [ - Row( - children: [ - Expanded( - child: RoundedWhiteContainer( - radiusMultiplier: 2, - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.circleLock, - width: 48, - height: 48, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 16, - ), - Text( - "Change Password", - style: STextStyles.desktopTextSmall(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Protect your Stack Wallet with a strong password. Stack Wallet does not store " - "your password, and is therefore NOT able to restore it. Keep your password safe and secure.", - style: - STextStyles.desktopTextExtraExtraSmall(context), - ), - const SizedBox( - height: 20, - ), - changePassword - ? SizedBox( - width: 512, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Current password", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key( - "desktopSecurityRestoreFromFilePasswordFieldKey"), - focusNode: passwordCurrentFocusNode, - controller: passwordCurrentController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter current password", - passwordCurrentFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension< - StackColors>()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - setState(() {}); - }, - ), - ), - const SizedBox(height: 16), - Text( - "New password", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key( - "desktopSecurityCreateNewPasswordFieldKey1"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter new password", - passwordFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityCreateNewPasswordButtonKey1"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension< - StackColors>()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - if (newValue.isEmpty) { - setState(() { - passwordFeedback = ""; - }); - return; - } - final result = - zxcvbn.evaluate(newValue); - String suggestionsAndTips = ""; - for (var sug in result - .feedback.suggestions! - .toSet()) { - suggestionsAndTips += "$sug\n"; - } - suggestionsAndTips += - result.feedback.warning!; - String feedback = - // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" - suggestionsAndTips; - - passwordStrength = result.score! / 4; - - // hack fix to format back string returned from zxcvbn - if (feedback - .contains("phrasesNo need")) { - feedback = feedback.replaceFirst( - "phrasesNo need", - "phrases\nNo need"); - } - - if (feedback.endsWith("\n")) { - feedback = feedback.substring( - 0, feedback.length - 2); - } - - setState(() { - passwordFeedback = feedback; - }); - }, - ), - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: passwordFeedback.isNotEmpty - ? 4 - : 0, - ), - child: passwordFeedback.isNotEmpty - ? Text( - passwordFeedback, - style: STextStyles.infoSmall( - context), - ) - : null, - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 12, - top: 10, - ), - child: ProgressBar( - key: const Key( - "desktopSecurityCreateStackBackUpProgressBar"), - width: 450, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension()! - .accentColorYellow - : Theme.of(context) - .extension()! - .accentColorGreen, - backgroundColor: Theme.of(context) - .extension()! - .buttonBackSecondary, - percent: passwordStrength < 0.25 - ? 0.03 - : passwordStrength, - ), - ), - const SizedBox(height: 16), - Text( - "Confirm new password", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3), - textAlign: TextAlign.left, - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key( - "desktopSecurityCreateNewPasswordFieldKey2"), - focusNode: passwordRepeatFocusNode, - controller: passwordRepeatController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm new password", - passwordRepeatFocusNode, - context, - ).copyWith( - labelStyle: - STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "desktopSecurityCreateNewPasswordButtonKey2"), - onTap: () async { - setState(() { - hidePassword = - !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension< - StackColors>()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - setState(() {}); - }, - ), - ), - const SizedBox(height: 20), - PrimaryButton( - width: 160, - desktopMed: true, - enabled: shouldEnableSave, - label: "Save changes", - onPressed: () async { - final didChangePW = - await attemptChangePW(); - if (didChangePW) { - setState(() { - changePassword = false; - }); - } - }, - ) - ], - ), - ) - : PrimaryButton( - width: 210, - desktopMed: true, - enabled: true, - label: "Set up new password", - onPressed: () { - setState(() { - changePassword = true; - }); - }, - ), - ], - ), - ], + Padding( + padding: const EdgeInsets.only( + right: 30, + ), + child: RoundedWhiteContainer( + // radiusMultiplier: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleLock, + width: 48, + height: 48, + ), ), - ), + Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 16, + ), + Text( + "Change Password", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Protect your Stack Wallet with a strong password. Stack Wallet does not store " + "your password, and is therefore NOT able to restore it. Keep your password safe and secure.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + changePassword + ? SizedBox( + width: 512, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Current password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityRestoreFromFilePasswordFieldKey"), + focusNode: passwordCurrentFocusNode, + controller: passwordCurrentController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter current password", + passwordCurrentFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 16), + Text( + "New password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter new password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey1"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = + zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug in result + .feedback.suggestions! + .toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += + result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; + + passwordStrength = result.score! / 4; + + // hack fix to format back string returned from zxcvbn + if (feedback + .contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", + "phrases\nNo need"); + } + + if (feedback.endsWith("\n")) { + feedback = feedback.substring( + 0, feedback.length - 2); + } + + setState(() { + passwordFeedback = feedback; + }); + }, + ), + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: + passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall( + context), + ) + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key( + "desktopSecurityCreateStackBackUpProgressBar"), + width: 450, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 + ? 0.03 + : passwordStrength, + ), + ), + const SizedBox(height: 16), + Text( + "Confirm new password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key( + "desktopSecurityCreateNewPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm new password", + passwordRepeatFocusNode, + context, + ).copyWith( + labelStyle: + STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "desktopSecurityCreateNewPasswordButtonKey2"), + onTap: () async { + setState(() { + hidePassword = + !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + }, + ), + ), + const SizedBox(height: 20), + PrimaryButton( + width: 160, + desktopMed: true, + enabled: shouldEnableSave, + label: "Save changes", + onPressed: () async { + final didChangePW = + await attemptChangePW(); + if (didChangePW) { + setState(() { + changePassword = false; + }); + } + }, + ) + ], + ), + ) + : PrimaryButton( + width: 210, + desktopMed: true, + enabled: true, + label: "Set up new password", + onPressed: () { + setState(() { + changePassword = true; + }); + }, + ), + ], + ), + ), + ], ), - const SizedBox( - width: 40, - ), - ], + ), ), ], ); diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 7f6aac260..618ee56da 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -2,13 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import '../../../pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; - class SyncingPreferencesSettings extends ConsumerStatefulWidget { const SyncingPreferencesSettings({Key? key}) : super(key: key); @@ -34,10 +33,13 @@ class _SyncingPreferencesSettings child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.svg.circleArrowRotate, - width: 48, - height: 48, + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleArrowRotate, + width: 48, + height: 48, + ), ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index e76a17c12..38d720969 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -59,6 +59,7 @@ class _SVG { String txExchangeFailed(BuildContext context) => "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; + String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 10a00ff2e..bba5f6ed2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -315,6 +315,7 @@ flutter: - assets/svg/keys.svg - assets/svg/arrow-down.svg - assets/svg/plus-circle.svg + - assets/svg/configuration.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Litecoin.svg From f11119119affc02e8a427dadac434f613669542d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 14:32:44 -0700 Subject: [PATCH 058/225] changed all settings buttons to PrimaryButton --- .../currency_settings/currency_settings.dart | 108 ++++++++---------- .../language_settings/language_settings.dart | 62 ++++------ .../home/settings_menu/security_settings.dart | 8 +- .../syncing_preferences_settings.dart | 35 ++---- 4 files changed, 79 insertions(+), 134 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index b1581d971..7ad8b38b9 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class CurrencySettings extends ConsumerStatefulWidget { @@ -18,6 +19,41 @@ class CurrencySettings extends ConsumerStatefulWidget { ConsumerState createState() => _CurrencySettings(); } +Future chooseCurrency(BuildContext context) async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return DesktopDialog( + maxHeight: 800, + maxWidth: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Select currency", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const Expanded( + child: BaseCurrencySettingsView(), + ), + ], + ), + ); + }, + ); +} + class _CurrencySettings extends ConsumerState { @override Widget build(BuildContext context) { @@ -65,12 +101,20 @@ class _CurrencySettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: changeCurrency(), + child: PrimaryButton( + width: 210, + desktopMed: true, + enabled: true, + label: "Change currency", + onPressed: () { + chooseCurrency(context); + }, + ), ), ], ), @@ -82,63 +126,3 @@ class _CurrencySettings extends ConsumerState { ); } } - -class changeCurrency extends ConsumerWidget { - const changeCurrency({ - Key? key, - }) : super(key: key); - Future chooseCurrency(BuildContext context) async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return DesktopDialog( - maxHeight: 800, - maxWidth: 600, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(32), - child: Text( - "Select currency", - style: STextStyles.desktopH3(context), - textAlign: TextAlign.center, - ), - ), - const DesktopDialogCloseButton(), - ], - ), - const Expanded( - child: BaseCurrencySettingsView(), - ), - ], - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - chooseCurrency(context); - }, - child: Text( - "Change currency", - style: STextStyles.button(context), - ), - ), - ); - } -} diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index 2047d4eff..96ce5cf61 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -4,7 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/language_settings/language_dialog.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class LanguageOptionSettings extends ConsumerStatefulWidget { @@ -17,6 +17,17 @@ class LanguageOptionSettings extends ConsumerStatefulWidget { _LanguageOptionSettings(); } +Future chooseLanguage(BuildContext context) async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const LanguageDialog(); + }, + ); +} + class _LanguageOptionSettings extends ConsumerState { @override Widget build(BuildContext context) { @@ -66,12 +77,20 @@ class _LanguageOptionSettings extends ConsumerState { ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: ChangeLanguageButton(), + child: PrimaryButton( + width: 210, + desktopMed: true, + enabled: true, + label: "Change language", + onPressed: () { + chooseLanguage(context); + }, + ), ), ], ), @@ -83,40 +102,3 @@ class _LanguageOptionSettings extends ConsumerState { ); } } - -class ChangeLanguageButton extends ConsumerWidget { - const ChangeLanguageButton({ - Key? key, - }) : super(key: key); - - Future chooseLanguage(BuildContext context) async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const LanguageDialog(); - }, - ); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - chooseLanguage(context); - }, - child: Text( - "Change language", - style: STextStyles.button(context), - ), - ), - ); - } -} diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index 01a6c9c9b..de3505ace 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -157,20 +157,16 @@ class _SecuritySettings extends ConsumerState { ), ), Padding( - padding: - const EdgeInsets.only(left: 10, right: 10, bottom: 10), + padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( - height: 16, - ), Text( "Change Password", style: STextStyles.desktopTextSmall(context), ), const SizedBox( - height: 8, + height: 16, ), Text( "Protect your Stack Wallet with a strong password. Stack Wallet does not store " diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 618ee56da..b245f8d43 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -5,7 +5,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class SyncingPreferencesSettings extends ConsumerStatefulWidget { @@ -75,12 +75,18 @@ class _SyncingPreferencesSettings ), Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Padding( padding: EdgeInsets.all( 10, ), - child: ChangePrefButton(), + child: PrimaryButton( + width: 210, + desktopMed: true, + enabled: true, + label: "Change preferences", + onPressed: () {}, + ), ), ], ), @@ -92,26 +98,3 @@ class _SyncingPreferencesSettings ); } } - -class ChangePrefButton extends ConsumerWidget { - const ChangePrefButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - return SizedBox( - width: 200, - height: 48, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () {}, - child: Text( - "Change preferences", - style: STextStyles.button(context), - ), - ), - ); - } -} From 1afc468d28b0ed4b8775a9692a848728c4a4e401 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 15:41:55 -0600 Subject: [PATCH 059/225] desktop nodes scroll layout fix --- .../home/settings_menu/nodes_settings.dart | 395 +++++++++--------- 1 file changed, 198 insertions(+), 197 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart index 0f539f9f3..e9417d2a7 100644 --- a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; -import 'package:stackwallet/providers/global/node_service_provider.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -52,12 +51,15 @@ class _NodesSettings extends ConsumerState { void dispose() { searchNodeController.dispose(); searchNodeFocusNode.dispose(); + nodeScrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + bool showTestNet = ref.watch( prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), ); @@ -66,217 +68,216 @@ class _NodesSettings extends ConsumerState { ? _coins : _coins.sublist(0, _coins.length - kTestNetCoinCount); - debugPrint("BUILD: $runtimeType"); - return Column( - mainAxisSize: MainAxisSize.min, + return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - Assets.svg.circleNode, - width: 48, - height: 48, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.start, - text: TextSpan( - children: [ - TextSpan( - text: "Nodes", - style: STextStyles.desktopTextSmall(context), - ), - TextSpan( - text: "\n\nSelect a coin to see nodes", - style: STextStyles.desktopTextExtraExtraSmall( - context), - ), - ], - ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + right: 32, + bottom: 32, + ), + child: RoundedWhiteContainer( + radiusMultiplier: 2, + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.circleNode, + width: 48, + height: 48, ), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: searchNodeController, - focusNode: searchNodeFocusNode, - onChanged: (newString) { - setState(() => filter = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - searchNodeFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: searchNodeController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - searchNodeController.text = ""; - filter = ""; - }); - }, - ), - ], - ), - ), - ) - : null, + const SizedBox( + height: 16, ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - borderColor: - Theme.of(context).extension()!.background, - child: ListView.separated( - controller: nodeScrollController, - physics: const AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.vertical, - primary: false, - shrinkWrap: true, - itemBuilder: (context, index) { - final coin = coins[index]; - final count = ref - .watch(nodeServiceChangeNotifierProvider - .select((value) => value.getNodesFor(coin))) - .length; - - return Padding( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + Text( + "Nodes", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Select a coin to see nodes", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: searchNodeController, + focusNode: searchNodeFocusNode, + onChanged: (newString) { + setState(() => filter = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + searchNodeFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, ), ), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - onPressed: () { - showDialog( - context: context, - builder: (context) => Navigator( - initialRoute: CoinNodesView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - CoinNodesView( - coin: coin, - rootNavigator: true, - ), - const RouteSettings( - name: CoinNodesView.routeName, - ), - ), - ]; - }, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.all( - 12.0, - ), - child: Row( - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 24, - height: 24, - ), - const SizedBox( - width: 12, - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, + suffixIcon: searchNodeController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( children: [ - Text( - "${coin.prettyName} nodes", - style: STextStyles.titleBold12( - context), - ), - Text( - count > 1 - ? "$count nodes" - : "Default", - style: STextStyles.label(context), + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + searchNodeController.text = ""; + filter = ""; + }); + }, ), ], ), - ], - ), - Expanded( - child: SvgPicture.asset( - Assets.svg.chevronRight, - alignment: Alignment.centerRight, ), + ) + : null, + ), + ), + ), + ], + ), + const SizedBox( + height: 20, + ), + Flexible( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: ListView.separated( + controller: nodeScrollController, + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + primary: false, + shrinkWrap: true, + itemBuilder: (context, index) { + final coin = coins[index]; + final count = ref + .watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))) + .length; + + return Padding( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + onPressed: () { + showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: CoinNodesView.routeName, + onGenerateRoute: + RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + CoinNodesView( + coin: coin, + rootNavigator: true, + ), + const RouteSettings( + name: CoinNodesView.routeName, + ), + ), + ]; + }, ), - ], + ); + }, + child: Padding( + padding: const EdgeInsets.all( + 12.0, + ), + child: Row( + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "${coin.prettyName} nodes", + style: STextStyles.titleBold12( + context), + ), + Text( + count > 1 + ? "$count nodes" + : "Default", + style: STextStyles.label(context), + ), + ], + ), + ], + ), + Expanded( + child: SvgPicture.asset( + Assets.svg.chevronRight, + alignment: Alignment.centerRight, + ), + ), + ], + ), ), ), - ), - ); - }, - separatorBuilder: (context, index) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .background, + ); + }, + separatorBuilder: (context, index) => Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ), + itemCount: coins.length, ), - itemCount: coins.length, ), ), - ), - ], + ], + ), ), ), ), From 6cfbeb180e69db1e46444211d0764efe26090105 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 15:51:53 -0600 Subject: [PATCH 060/225] desktop nodes search --- .../home/settings_menu/nodes_settings.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart index e9417d2a7..012f7b47a 100644 --- a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart @@ -34,6 +34,18 @@ class _NodesSettings extends ConsumerState { String filter = ""; + List _search(String filter, List coins) { + if (filter.isEmpty) { + return coins; + } + return coins + .where((coin) => + coin.prettyName.contains(filter) || + coin.name.contains(filter) || + coin.ticker.toLowerCase().contains(filter.toLowerCase())) + .toList(); + } + @override void initState() { _coins = _coins.toList(); @@ -68,6 +80,8 @@ class _NodesSettings extends ConsumerState { ? _coins : _coins.sublist(0, _coins.length - kTestNetCoinCount); + coins = _search(filter, coins); + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ From a57651a766a2a5e77b3b1f5ca3ffcacd3f4dfb1d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 14:53:27 -0700 Subject: [PATCH 061/225] layout fix + correct currency description --- .../settings_menu/currency_settings/currency_settings.dart | 5 ++--- .../home/settings_menu/syncing_preferences_settings.dart | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index 7ad8b38b9..ecc19ebb7 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -89,8 +88,8 @@ class _CurrencySettings extends ConsumerState { ), TextSpan( text: - "\n\nProtect your Stack Wallet with a strong password. Stack Wallet does not store " - "your password, and is therefore NOT able to restore it. Keep your password safe and secure.", + "\n\nSelect a fiat currency to evaluate your crypto assets. We use CoinGecko conversion rates " + "when displaying your balance and transaction amounts.", style: STextStyles.desktopTextExtraExtraSmall(context), ), diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index b245f8d43..3d8bebb81 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -56,7 +56,7 @@ class _SyncingPreferencesSettings ), TextSpan( text: - "\nSet up your syncing preferences for all wallets in your Stack.", + "\n\nSet up your syncing preferences for all wallets in your Stack.", style: STextStyles.desktopTextExtraExtraSmall( context), ), @@ -69,7 +69,7 @@ class _SyncingPreferencesSettings ///TODO: ONLY SHOW SYNC OPTIONS ON BUTTON PRESS Column( - children: [ + children: const [ SyncingOptionsView(), ], ), @@ -77,7 +77,7 @@ class _SyncingPreferencesSettings crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: PrimaryButton( From 824d8036789f0cbc1ba0b6ae5090554cf4ff2561 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 14:54:12 -0700 Subject: [PATCH 062/225] cursor on hover of wallet --- .../home/my_stack_view/my_wallets.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart index 550db293e..13170d07e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart @@ -53,7 +53,10 @@ class _MyWalletsState extends ConsumerState { height: 20, ), const Expanded( - child: WalletSummaryTable(), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: WalletSummaryTable(), + ), ), ], ), From 19d070c61832659d7971aa1b860c41df400f4a7b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 15:00:07 -0700 Subject: [PATCH 063/225] send/receive pointer finger on hover --- .../sub_widgets/send_receive_tab_menu.dart | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart index 54dca9a4c..251e4f301 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart @@ -48,16 +48,20 @@ class _SendReceiveTabMenuState extends State { const SizedBox( height: 16, ), - Text( - "Send", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: _selectedIndex == 0 - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .textSubtitle1, + MouseRegion( + cursor: SystemMouseCursors.click, + child: Text( + "Send", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: _selectedIndex == 0 + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .textSubtitle1, + ), ), ), const SizedBox( @@ -90,16 +94,20 @@ class _SendReceiveTabMenuState extends State { const SizedBox( height: 16, ), - Text( - "Receive", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: _selectedIndex == 1 - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .textSubtitle1, + MouseRegion( + cursor: SystemMouseCursors.click, + child: Text( + "Receive", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: _selectedIndex == 1 + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .textSubtitle1, + ), ), ), const SizedBox( From 15c51e32698c0436c672c832ac5c8b6adec609b8 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 16:21:52 -0600 Subject: [PATCH 064/225] support view buttons --- .../global_settings_view/support_view.dart | 444 ++++++------------ 1 file changed, 145 insertions(+), 299 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 20aeedf61..9c3c41296 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -17,14 +17,13 @@ class SupportView extends StatelessWidget { }) : super(key: key); static const String routeName = "/support"; - final double iconSize = 28; @override Widget build(BuildContext context) { - final isDesktop = Util.isDesktop; - debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + return ConditionalParent( condition: !isDesktop, builder: (child) { @@ -64,321 +63,168 @@ class SupportView extends StatelessWidget { : const SizedBox( height: 12, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - if (!isDesktop) { - launchUrl( - Uri.parse("https://t.me/stackwallet"), - mode: LaunchMode.externalApplication, - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.socials.telegram, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Telegram", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), - BlueTextButton( - text: isDesktop ? "@stackwallet" : "", - onTap: () { - launchUrl( - Uri.parse("https://t.me/stackwallet"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - ), + AboutItem( + linkUrl: "https://t.me/stackwallet", + label: "Telegram", + buttonText: "@stackwallet", + iconAsset: Assets.socials.telegram, + isDesktop: isDesktop, ), const SizedBox( height: 8, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - if (!isDesktop) { - launchUrl( - Uri.parse("https://discord.gg/RZMG3yUm"), - mode: LaunchMode.externalApplication, - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.socials.discord, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Discord", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), - BlueTextButton( - text: isDesktop ? "Stack Wallet" : "", - onTap: () { - launchUrl( - Uri.parse( - "https://discord.gg/RZMG3yUm"), //expired link? - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - ), + AboutItem( + linkUrl: "https://discord.gg/RZMG3yUm", + label: "Discord", + buttonText: "Stack Wallet", + iconAsset: Assets.socials.discord, + isDesktop: isDesktop, ), const SizedBox( height: 8, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - if (!isDesktop) { - launchUrl( - Uri.parse("https://www.reddit.com/r/stackwallet/"), - mode: LaunchMode.externalApplication, - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.socials.reddit, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Reddit", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), - BlueTextButton( - text: isDesktop ? "r/stackwallet" : "", - onTap: () { - launchUrl( - Uri.parse("https://www.reddit.com/r/stackwallet/"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - ), + AboutItem( + linkUrl: "https://www.reddit.com/r/stackwallet/", + label: "Reddit", + buttonText: "r/stackwallet", + iconAsset: Assets.socials.reddit, + isDesktop: isDesktop, ), const SizedBox( height: 8, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - if (!isDesktop) { - launchUrl( - Uri.parse("https://twitter.com/stack_wallet"), - mode: LaunchMode.externalApplication, - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.socials.twitter, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Twitter", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), - BlueTextButton( - text: isDesktop ? "@stack_wallet" : "", - onTap: () { - launchUrl( - Uri.parse("https://twitter.com/stack_wallet"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - ), + AboutItem( + linkUrl: "https://twitter.com/stack_wallet", + label: "Twitter", + buttonText: "@stack_wallet", + iconAsset: Assets.socials.twitter, + isDesktop: isDesktop, ), const SizedBox( height: 8, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - if (!isDesktop) { - launchUrl( - Uri.parse("mailto://support@stackwallet.com"), - mode: LaunchMode.externalApplication, - ); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.svg.envelope, - width: iconSize, - height: iconSize, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - const SizedBox( - width: 12, - ), - Text( - "Email", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], - ), - BlueTextButton( - text: isDesktop ? "support@stackwallet.com" : "", - onTap: () { - launchUrl( - Uri.parse("mailto://support@stackwallet.com"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - ), + AboutItem( + linkUrl: "mailto://support@stackwallet.com", + label: "Email", + buttonText: "support@stackwallet.com", + iconAsset: Assets.svg.envelope, + isDesktop: isDesktop, ), ], ), ); } } + +class AboutItem extends StatelessWidget { + const AboutItem({ + Key? key, + required this.linkUrl, + required this.label, + required this.buttonText, + required this.iconAsset, + required this.isDesktop, + }) : super(key: key); + + final String linkUrl; + final String label; + final String buttonText; + final String iconAsset; + final bool isDesktop; + + @override + Widget build(BuildContext context) { + final double iconSize = isDesktop ? 20 : 28; + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + launchUrl( + Uri.parse(linkUrl), + mode: LaunchMode.externalApplication, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: child, + ), + ), + child: Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 20, + vertical: 15, + ) + : const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ConditionalParent( + condition: isDesktop, + builder: (child) => Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10000), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + ), + child: Center( + child: child, + ), + ), + child: SvgPicture.asset( + iconAsset, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .bottomNavIconIcon, + ), + ), + const SizedBox( + width: 12, + ), + Text( + label, + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + if (isDesktop) + BlueTextButton( + text: buttonText, + onTap: () { + launchUrl( + Uri.parse(linkUrl), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ), + ); + } +} From 3eae7d0fabccca7e96715eeebf23567b9084e6ac Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 15:22:45 -0700 Subject: [PATCH 065/225] password eye icon pointer cursor --- .../create_password/create_password_view.dart | 56 ++++++++++--------- .../desktop_login_view.dart | 21 ++++--- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index c29fc3de6..8e752f508 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -203,15 +203,18 @@ class _CreatePasswordViewState extends ConsumerState { height: 32, width: 32, child: Center( - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 19, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 19, + ), ), ), ), @@ -354,22 +357,25 @@ class _CreatePasswordViewState extends ConsumerState { height: 32, width: 32, child: Center( - child: SvgPicture.asset( - fieldsMatch && passwordStrength == 1 - ? Assets.svg.checkCircle - : hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: fieldsMatch && - passwordStrength == 1 - ? Theme.of(context) - .extension()! - .accentColorGreen - : Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 19, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + fieldsMatch && passwordStrength == 1 + ? Assets.svg.checkCircle + : hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: fieldsMatch && + passwordStrength == 1 + ? Theme.of(context) + .extension()! + .accentColorGreen + : Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 19, + ), ), ), ), diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index 363c1fb0d..ebd1c334b 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -175,15 +175,18 @@ class _DesktopLoginViewState extends ConsumerState { hidePassword = !hidePassword; }); }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 24, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), ), ), const SizedBox( From 1818b00ac66c25cb099cb0d3a72a3cfce6c7dcd1 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 16:29:12 -0600 Subject: [PATCH 066/225] slightly adjust mouse region --- .../sub_widgets/send_receive_tab_menu.dart | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart index 251e4f301..b2f6156c2 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart @@ -39,18 +39,18 @@ class _SendReceiveTabMenuState extends State { return Row( children: [ Expanded( - child: GestureDetector( - onTap: () => _onChanged(0), - child: Container( - color: Colors.transparent, - child: Column( - children: [ - const SizedBox( - height: 16, - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: Text( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => _onChanged(0), + child: Container( + color: Colors.transparent, + child: Column( + children: [ + const SizedBox( + height: 16, + ), + Text( "Send", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -63,40 +63,40 @@ class _SendReceiveTabMenuState extends State { .textSubtitle1, ), ), - ), - const SizedBox( - height: 19, - ), - Container( - height: 2, - decoration: BoxDecoration( - color: _selectedIndex == 0 - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .background, + const SizedBox( + height: 19, ), - ), - ], + Container( + height: 2, + decoration: BoxDecoration( + color: _selectedIndex == 0 + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .background, + ), + ), + ], + ), ), ), ), ), Expanded( - child: GestureDetector( - onTap: () => _onChanged(1), - child: Container( - color: Colors.transparent, - child: Column( - children: [ - const SizedBox( - height: 16, - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: Text( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => _onChanged(1), + child: Container( + color: Colors.transparent, + child: Column( + children: [ + const SizedBox( + height: 16, + ), + Text( "Receive", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -109,23 +109,23 @@ class _SendReceiveTabMenuState extends State { .textSubtitle1, ), ), - ), - const SizedBox( - height: 19, - ), - Container( - height: 2, - decoration: BoxDecoration( - color: _selectedIndex == 1 - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .background, + const SizedBox( + height: 19, ), - ), - ], + Container( + height: 2, + decoration: BoxDecoration( + color: _selectedIndex == 1 + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .background, + ), + ), + ], + ), ), ), ), From b4488fceed20ab882992c2a331025a39b4174de7 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 15 Nov 2022 15:41:58 -0700 Subject: [PATCH 067/225] US spelling adjustment --- .../wallet_view/sub_widgets/network_info_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart index 59d20a9df..5195a4b9b 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart @@ -132,10 +132,10 @@ class _NetworkInfoButtonState extends ConsumerState { label = "Unable to sync"; break; case WalletSyncStatus.synced: - label = "Synchronised"; + label = "Synchronized"; break; case WalletSyncStatus.syncing: - label = "Synchronising"; + label = "Synchronizing"; break; } From 07cf1f3f92a304aa2ecd1c931454f0655ae7b951 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 16:44:23 -0600 Subject: [PATCH 068/225] Add MouseRegion to Expandable widget and clean up duplications --- .../home/my_stack_view/my_wallets.dart | 5 +- lib/widgets/expandable.dart | 13 ++- lib/widgets/table_view/table_view.dart | 3 +- .../wallet_info_row/wallet_info_row.dart | 93 ++++++++++--------- 4 files changed, 59 insertions(+), 55 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart index 13170d07e..550db293e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart @@ -53,10 +53,7 @@ class _MyWalletsState extends ConsumerState { height: 20, ), const Expanded( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: WalletSummaryTable(), - ), + child: WalletSummaryTable(), ), ], ), diff --git a/lib/widgets/expandable.dart b/lib/widgets/expandable.dart index a0c2be5a4..47726d6d6 100644 --- a/lib/widgets/expandable.dart +++ b/lib/widgets/expandable.dart @@ -89,11 +89,14 @@ class _ExpandableState extends State with TickerProviderStateMixin { return Column( mainAxisSize: MainAxisSize.min, children: [ - GestureDetector( - onTap: toggle, - child: Container( - color: Colors.transparent, - child: widget.header, + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: toggle, + child: Container( + color: Colors.transparent, + child: widget.header, + ), ), ), SizeTransition( diff --git a/lib/widgets/table_view/table_view.dart b/lib/widgets/table_view/table_view.dart index 7e8693f0d..8c2d470bd 100644 --- a/lib/widgets/table_view/table_view.dart +++ b/lib/widgets/table_view/table_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/widgets/table_view/table_view_row.dart'; class TableView extends StatefulWidget { const TableView({ @@ -9,7 +8,7 @@ class TableView extends StatefulWidget { this.shrinkWrap = false, }) : super(key: key); - final List rows; + final List rows; final double rowSpacing; final bool shrinkWrap; diff --git a/lib/widgets/wallet_info_row/wallet_info_row.dart b/lib/widgets/wallet_info_row/wallet_info_row.dart index d5e42e814..fe006a67b 100644 --- a/lib/widgets/wallet_info_row/wallet_info_row.dart +++ b/lib/widgets/wallet_info_row/wallet_info_row.dart @@ -26,53 +26,58 @@ class WalletInfoRow extends ConsumerWidget { .getManagerProvider(walletId)); if (Util.isDesktop) { - return GestureDetector( - onTap: onPressed, - child: Container( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 4, - child: Row( - children: [ - WalletInfoCoinIcon(coin: manager.coin), - const SizedBox( - width: 12, - ), - Text( - manager.walletName, - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark, + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onPressed, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 4, + child: Row( + children: [ + WalletInfoCoinIcon(coin: manager.coin), + const SizedBox( + width: 12, ), - ), - ], + Text( + manager.walletName, + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), ), - ), - Expanded( - flex: 4, - child: WalletInfoRowBalanceFuture( - walletId: walletId, + Expanded( + flex: 4, + child: WalletInfoRowBalanceFuture( + walletId: walletId, + ), ), - ), - Expanded( - flex: 6, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SvgPicture.asset( - Assets.svg.chevronRight, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - ], - ), - ) - ], + Expanded( + flex: 6, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SvgPicture.asset( + Assets.svg.chevronRight, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + ], + ), + ) + ], + ), ), ), ); From cebbfcf82d3a54b3ec4b740878afbd653a42abfc Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 17:45:16 -0600 Subject: [PATCH 069/225] desktop currency update on save only --- .../global_settings_view/currency_view.dart | 76 +++++++++++++------ .../currency_settings/currency_settings.dart | 2 +- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/currency_view.dart b/lib/pages/settings_views/global_settings_view/currency_view.dart index 4f2c3258c..4e8fd5f6e 100644 --- a/lib/pages/settings_views/global_settings_view/currency_view.dart +++ b/lib/pages/settings_views/global_settings_view/currency_view.dart @@ -14,11 +14,10 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import '../../../widgets/rounded_white_container.dart'; - class BaseCurrencySettingsView extends ConsumerStatefulWidget { const BaseCurrencySettingsView({Key? key}) : super(key: key); @@ -37,14 +36,20 @@ class _CurrencyViewState extends ConsumerState { final _searchFocusNode = FocusNode(); void onTap(int index) { - if (currenciesWithoutSelected[index] == current || current.isEmpty) { - // ignore if already selected currency - return; + if (Util.isDesktop) { + setState(() { + current = currenciesWithoutSelected[index]; + }); + } else { + if (currenciesWithoutSelected[index] == current || current.isEmpty) { + // ignore if already selected currency + return; + } + current = currenciesWithoutSelected[index]; + currenciesWithoutSelected.remove(current); + currenciesWithoutSelected.insert(0, current); + ref.read(prefsChangeNotifierProvider).currency = current; } - current = currenciesWithoutSelected[index]; - currenciesWithoutSelected.remove(current); - currenciesWithoutSelected.insert(0, current); - ref.read(prefsChangeNotifierProvider).currency = current; } BorderRadius? _borderRadius(int index) { @@ -82,6 +87,15 @@ class _CurrencyViewState extends ConsumerState { @override void initState() { _searchController = TextEditingController(); + if (Util.isDesktop) { + currenciesWithoutSelected = + ref.read(baseCurrenciesProvider).map.keys.toList(); + current = ref.read(prefsChangeNotifierProvider).currency; + if (current.isNotEmpty) { + currenciesWithoutSelected.remove(current); + currenciesWithoutSelected.insert(0, current); + } + } super.initState(); } @@ -94,20 +108,25 @@ class _CurrencyViewState extends ConsumerState { @override Widget build(BuildContext context) { - current = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); - - currenciesWithoutSelected = ref - .watch(baseCurrenciesProvider.select((value) => value.map)) - .keys - .toList(); - if (current.isNotEmpty) { - currenciesWithoutSelected.remove(current); - currenciesWithoutSelected.insert(0, current); - } - currenciesWithoutSelected = _filtered(); final isDesktop = Util.isDesktop; + if (!isDesktop) { + current = ref + .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + + currenciesWithoutSelected = ref + .watch(baseCurrenciesProvider.select((value) => value.map)) + .keys + .toList(); + + if (current.isNotEmpty) { + currenciesWithoutSelected.remove(current); + currenciesWithoutSelected.insert(0, current); + } + } + + currenciesWithoutSelected = _filtered(); + return ConditionalParent( condition: !isDesktop, builder: (child) { @@ -181,7 +200,20 @@ class _CurrencyViewState extends ConsumerState { child: PrimaryButton( label: "Save changes", desktopMed: true, - onPressed: Navigator.of(context).pop, + onPressed: () { + ref.read(prefsChangeNotifierProvider).currency = + current; + + if (ref + .read(prefsChangeNotifierProvider) + .externalCalls) { + ref + .read(priceAnd24hChangeNotifierProvider) + .updatePrice(); + } + + Navigator.of(context).pop(); + }, ), ), ], diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index ecc19ebb7..ad7d749ee 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -102,7 +102,7 @@ class _CurrencySettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: PrimaryButton( From f7b5462029f7bf9c3a19e4a2d5d4ac443a9b5cfb Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 17:55:34 -0600 Subject: [PATCH 070/225] increase min window height for desktop --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 21abd9df7..b1f917f58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,7 +76,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1200, 900)); + setWindowMinSize(const Size(1200, 1100)); setWindowMaxSize(Size.infinite); } From 1b5ced50613c05c02cdb0bd0bde7208760b94b44 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 19:27:08 -0600 Subject: [PATCH 071/225] desktop save receiving qr image to file --- .../generate_receiving_uri_qr_code_view.dart | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index ae615bd96..75d0deba0 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -1,9 +1,11 @@ +import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; // import 'package:document_file_save_plus/document_file_save_plus.dart'; import 'package:decimal/decimal.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_svg/svg.dart'; @@ -71,19 +73,53 @@ class _GenerateUriQrCodeViewState extends State { await image.toByteData(format: ui.ImageByteFormat.png); Uint8List pngBytes = byteData!.buffer.asUint8List(); - // if (shouldSaveInsteadOfShare) { - // await DocumentFileSavePlus.saveFile( - // pngBytes, - // "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", - // "image/png"); - // } else { - final tempDir = await getTemporaryDirectory(); - final file = await File("${tempDir.path}/qrcode.png").create(); - await file.writeAsBytes(pngBytes); + if (shouldSaveInsteadOfShare) { + if (Util.isDesktop) { + final dir = Directory("${Platform.environment['HOME']}"); + if (!dir.existsSync()) { + throw Exception( + "Home dir not found while trying to open filepicker on QR image save"); + } + final path = await FilePicker.platform.saveFile( + fileName: "qrcode.png", + initialDirectory: dir.path, + ); - await Share.shareFiles(["${tempDir.path}/qrcode.png"], - text: "Receive URI QR Code"); - // } + if (path != null) { + final file = File(path); + if (file.existsSync()) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$path already exists!", + context: context, + ), + ); + } else { + await file.writeAsBytes(pngBytes); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "$path saved!", + context: context, + ), + ); + } + } + } else { + // await DocumentFileSavePlus.saveFile( + // pngBytes, + // "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", + // "image/png"); + } + } else { + final tempDir = await getTemporaryDirectory(); + final file = await File("${tempDir.path}/qrcode.png").create(); + await file.writeAsBytes(pngBytes); + + await Share.shareFiles(["${tempDir.path}/qrcode.png"], + text: "Receive URI QR Code"); + } } catch (e) { debugPrint(e.toString()); } @@ -567,6 +603,7 @@ class _GenerateUriQrCodeViewState extends State { desktopMed: true, onPressed: () async { // TODO: add save functionality instead of share + // save works on linux at the moment await _capturePng(true); }, label: "Save", From 11bae1740db7eb132bb755717d45dee6ee4c80b1 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 19:52:20 -0600 Subject: [PATCH 072/225] desktop qr save only button --- .../generate_receiving_uri_qr_code_view.dart | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 75d0deba0..981def830 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -547,6 +547,7 @@ class _GenerateUriQrCodeViewState extends State { borderColor: Theme.of(context) .extension()! .background, + width: isDesktop ? 370 : null, child: Column( children: [ Text( @@ -578,26 +579,31 @@ class _GenerateUriQrCodeViewState extends State { height: 12, ), Row( + mainAxisAlignment: isDesktop + ? MainAxisAlignment.center + : MainAxisAlignment.start, children: [ - SecondaryButton( - width: 170, - desktopMed: true, - onPressed: () async { - await _capturePng(false); - }, - label: "Share", - icon: SvgPicture.asset( - Assets.svg.share, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + if (!isDesktop) + SecondaryButton( + width: 170, + desktopMed: true, + onPressed: () async { + await _capturePng(false); + }, + label: "Share", + icon: SvgPicture.asset( + Assets.svg.share, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + if (!isDesktop) + const SizedBox( + width: 16, ), - ), - const SizedBox( - width: 16, - ), PrimaryButton( width: 170, desktopMed: true, From b64246228cadb58d4d9c32faf4b9fd005d9f4112 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 15 Nov 2022 19:58:32 -0600 Subject: [PATCH 073/225] update addressbookcard test --- test/widget_tests/address_book_card_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/widget_tests/address_book_card_test.dart b/test/widget_tests/address_book_card_test.dart index 07b1387df..7c53d8d50 100644 --- a/test/widget_tests/address_book_card_test.dart +++ b/test/widget_tests/address_book_card_test.dart @@ -70,7 +70,7 @@ void main() { await widgetTester.tap(find.byType(RawMaterialButton)); expect(find.byType(ContactPopUp), findsOneWidget); } else if (Util.isDesktop) { - expect(find.byType(RawMaterialButton), findsNothing); + expect(find.byType(RawMaterialButton), findsOneWidget); } }); } From bfc1603197abca97426b06c7599b12409d10f2ed Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 08:11:25 -0700 Subject: [PATCH 074/225] fixed setting container corner rounding and padding --- .../advanced_settings/advanced_settings.dart | 1 + .../home/settings_menu/appearance_settings.dart | 1 + .../backup_and_restore/backup_and_restore_settings.dart | 3 +++ .../currency_settings/currency_settings.dart | 1 + .../language_settings/language_settings.dart | 1 + .../home/settings_menu/nodes_settings.dart | 8 ++++---- .../home/settings_menu/security_settings.dart | 2 +- .../home/settings_menu/syncing_preferences_settings.dart | 1 + 8 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart index 0991a14ca..b4ff3fe6a 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart @@ -32,6 +32,7 @@ class _AdvancedSettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index 27a10ca80..7a9ed557f 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -49,6 +49,7 @@ class _AppearanceOptionSettings right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 663c3f975..7d70d4d0f 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -338,6 +338,7 @@ class _BackupRestoreSettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -501,6 +502,7 @@ class _BackupRestoreSettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -583,6 +585,7 @@ class _BackupRestoreSettings extends ConsumerState { bottom: 40, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index ad7d749ee..4c4225ce4 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -64,6 +64,7 @@ class _CurrencySettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index 96ce5cf61..08aeb9bc3 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -39,6 +39,7 @@ class _LanguageOptionSettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart index 012f7b47a..a7e95d33a 100644 --- a/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/nodes_settings.dart @@ -88,12 +88,12 @@ class _NodesSettings extends ConsumerState { Expanded( child: Padding( padding: const EdgeInsets.only( - right: 32, - bottom: 32, + right: 30, + // bottom: 32, ), child: RoundedWhiteContainer( radiusMultiplier: 2, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -114,7 +114,7 @@ class _NodesSettings extends ConsumerState { style: STextStyles.desktopTextSmall(context), ), const SizedBox( - height: 8, + height: 16, ), Text( "Select a coin to see nodes", diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index de3505ace..9f870440b 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -144,7 +144,7 @@ class _SecuritySettings extends ConsumerState { right: 30, ), child: RoundedWhiteContainer( - // radiusMultiplier: 2, + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 3d8bebb81..408b93e15 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -30,6 +30,7 @@ class _SyncingPreferencesSettings right: 30, ), child: RoundedWhiteContainer( + radiusMultiplier: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ From 0f3a0b18a4a156a7fdb79f162dde21c326fb4036 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 09:53:51 -0700 Subject: [PATCH 075/225] manual backup ok button on dialog works --- .../create_backup_view.dart | 142 +++++++++--------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 821504bf8..55fc08087 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -654,9 +654,10 @@ class _RestoreFromFileViewState extends State { }, ), ); + // make sure the dialog is able to be displayed for at least 1 second - await Future.delayed( - const Duration(seconds: 1)); + final fut = Future.delayed( + const Duration(seconds: 2)); final DateTime now = DateTime.now(); final String fileToSave = @@ -674,81 +675,86 @@ class _RestoreFromFileViewState extends State { jsonEncode(backup), ); + await Future.wait([fut]); + if (mounted) { // pop encryption progress dialog if (!isDesktop) Navigator.of(context).pop(); if (result) { await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( + context: context, + barrierDismissible: false, + builder: (context) { + if (Platform.isAndroid) { + return StackOkDialog( title: "Backup saved to:", message: fileToSave, - ) - : !isDesktop - ? const StackOkDialog( - title: - "Backup creation succeeded") - : DesktopDialog( - maxHeight: double.infinity, - maxWidth: 500, - child: Padding( - padding: - const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Column( - mainAxisSize: - MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - const SizedBox( - height: 26), - Text( - "Stack backup saved to: \n", - style: STextStyles - .desktopH3( - context), - ), - Text( - fileToSave, - style: STextStyles - .desktopTextExtraExtraSmall( - context), - ), - const SizedBox( - height: 40, - ), - Row( - children: [ - // const Spacer(), - Expanded( - child: - PrimaryButton( - label: "Ok", - desktopMed: - true, - onPressed: - () { - // Navigator.of( - // context) - // .pop(); - }, - ), - ), - ], - ) - ], - ), - ), + ); + } else if (isDesktop) { + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 500, + child: Padding( + padding: + const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, ), - ); + child: Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + const SizedBox( + height: 26), + Text( + "Stack backup saved to: \n", + style: STextStyles + .desktopH3(context), + ), + Text( + fileToSave, + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + const SizedBox( + height: 40, + ), + Row( + children: [ + // const Spacer(), + Expanded( + child: + PrimaryButton( + label: "Ok", + desktopMed: true, + onPressed: () { + int count = 0; + Navigator.of( + context) + .popUntil((_) => + count++ >= + 2); + }, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return const StackOkDialog( + title: + "Backup creation succeeded"); + } + }); passwordController.text = ""; passwordRepeatController.text = ""; setState(() {}); From e9a5cc85aec3e07b72a0b1eee81d03c0ea4454f6 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 11:15:41 -0600 Subject: [PATCH 076/225] add delay for ui to update properly --- .../stack_backup_views/create_backup_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 55fc08087..02556beb9 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -655,9 +655,12 @@ class _RestoreFromFileViewState extends State { ), ); + await Future.delayed( + const Duration(seconds: 1)); + // make sure the dialog is able to be displayed for at least 1 second final fut = Future.delayed( - const Duration(seconds: 2)); + const Duration(seconds: 1)); final DateTime now = DateTime.now(); final String fileToSave = From c50b2054fe8025fd804220695e0b6f1d88e62bb7 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 11:23:42 -0600 Subject: [PATCH 077/225] no notifications fix --- .../desktop_notifications_view.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart index 0c51f899d..b9c03ff19 100644 --- a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart +++ b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart @@ -39,13 +39,20 @@ class _DesktopNotificationsViewState ), ), body: notifications.isEmpty - ? RoundedWhiteContainer( - child: Center( - child: Text( - "Notifications will appear here", - style: STextStyles.desktopTextExtraExtraSmall(context), + ? Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: RoundedWhiteContainer( + child: Center( + child: Text( + "Notifications will appear here", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ), ), - ), + ], ) : ListView.builder( primary: false, From 0ce2477cf81fcc1a278a41748b9f2cd5424fb97f Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 12:08:19 -0600 Subject: [PATCH 078/225] desktop addressbook layout --- .../desktop_address_book.dart | 176 ++++++++---------- 1 file changed, 74 insertions(+), 102 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 407ed9897..ec40e5f60 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -13,8 +13,10 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -89,37 +91,31 @@ class _DesktopAddressBook extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; - final size = MediaQuery.of(context).size; - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DesktopAppBar( - isCompactHeight: true, - leading: Row( - children: [ - const SizedBox( - width: 24, - ), - Text( - "Address Book", - style: STextStyles.desktopH3(context), - ) - ], - ), + return DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox( + width: 24, + ), + Text( + "Address Book", + style: STextStyles.desktopH3(context), + ) + ], ), - const SizedBox(height: 53), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: RoundedContainer( - color: Theme.of(context).extension()!.background, - child: Row( - children: [ - SizedBox( - height: 60, - width: size.width - 800, - child: ClipRRect( + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Row( + children: [ + Expanded( + flex: 6, + child: Column( + children: [ + ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), @@ -172,81 +168,57 @@ class _DesktopAddressBook extends ConsumerState { ), ), ), - ), - const SizedBox(width: 20), - TextButton( - style: Theme.of(context) - .extension()! - .getDesktopMenuButtonColorSelected(context), - onPressed: () { - selectCryptocurrency(); - }, - child: SizedBox( - width: 200, - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: SvgPicture.asset(Assets.svg.filter), - ), - Text( - "Filter", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ], - ), + const SizedBox( + height: 24, ), - ), - const SizedBox(width: 20), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - newContact(); - }, - child: SizedBox( - width: 200, - height: 56, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: SvgPicture.asset(Assets.svg.circlePlus), - ), - Text( - "Add new", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .popupBG, - ), - ), - ], - ), - ), - ), - ], + const AddressBookView(), + ], + ), ), - ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: Column( + children: [ + Row( + children: [ + SecondaryButton( + width: 184, + label: "Filter", + desktopMed: true, + icon: SvgPicture.asset( + Assets.svg.filter, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: selectCryptocurrency, + ), + const SizedBox( + width: 20, + ), + PrimaryButton( + width: 184, + label: "Add new", + desktopMed: true, + icon: SvgPicture.asset( + Assets.svg.circlePlus, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + onPressed: newContact, + ), + ], + ), + ], + ), + ), + ], ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 26), - child: SizedBox( - width: 489, - child: AddressBookView(), - ), - ), - ], + ), ); } } From f66b780e53ca89731f7dad19324e28a7f2ea76d4 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 12:09:19 -0600 Subject: [PATCH 079/225] desktop popup edge color fix --- lib/widgets/desktop/desktop_dialog.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/widgets/desktop/desktop_dialog.dart b/lib/widgets/desktop/desktop_dialog.dart index d11124ba6..59c59c575 100644 --- a/lib/widgets/desktop/desktop_dialog.dart +++ b/lib/widgets/desktop/desktop_dialog.dart @@ -25,6 +25,7 @@ class DesktopDialog extends StatelessWidget { maxHeight: maxHeight, ), child: Material( + color: Colors.transparent, borderRadius: BorderRadius.circular( 20, ), From 016b90e904e3e9c6651c42a9c0b50415bdb6839b Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 12:22:09 -0600 Subject: [PATCH 080/225] support view full buttons desktop --- .../global_settings_view/support_view.dart | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 02d281b66..9ff50345c 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -7,7 +7,6 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -138,30 +137,20 @@ class AboutItem extends StatelessWidget { return RoundedWhiteContainer( padding: const EdgeInsets.all(0), - child: ConditionalParent( - condition: !isDesktop, - builder: (child) => RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - launchUrl( - Uri.parse(linkUrl), - mode: LaunchMode.externalApplication, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: child, + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), ), + onPressed: () { + launchUrl( + Uri.parse(linkUrl), + mode: LaunchMode.externalApplication, + ); + }, child: Padding( padding: isDesktop ? const EdgeInsets.symmetric( @@ -212,15 +201,19 @@ class AboutItem extends StatelessWidget { ], ), if (isDesktop) - BlueTextButton( - text: buttonText, - onTap: () { - launchUrl( - Uri.parse(linkUrl), - mode: LaunchMode.externalApplication, - ); - }, - ), + Text( + buttonText, + style: STextStyles.desktopTextExtraExtraSmall(context), + ) + // BlueTextButton( + // text: buttonText, + // onTap: () { + // launchUrl( + // Uri.parse(linkUrl), + // mode: LaunchMode.externalApplication, + // ); + // }, + // ), ], ), ), From 2c88b017f3d399b72e0bb3aec9f4b26e5be200b9 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 12:23:12 -0600 Subject: [PATCH 081/225] updated discord link --- lib/pages/settings_views/global_settings_view/support_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 9ff50345c..ee00bdb00 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -73,7 +73,7 @@ class SupportView extends StatelessWidget { height: 8, ), AboutItem( - linkUrl: "https://discord.gg/RZMG3yUm", + linkUrl: "https://discord.com/invite/mRPZuXx3At", label: "Discord", buttonText: "Stack Wallet", iconAsset: Assets.socials.discord, From be70d75d7551bc7b7cadf3227aeaf741512add9d Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 16 Nov 2022 12:27:57 -0600 Subject: [PATCH 082/225] mouse cursor for desktop favorites card --- .../sub_widgets/favorite_card.dart | 357 +++++++++--------- 1 file changed, 183 insertions(+), 174 deletions(-) diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 8ce8add17..7749f264d 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:tuple/tuple.dart'; class FavoriteCard extends ConsumerStatefulWidget { @@ -54,190 +55,198 @@ class _FavoriteCardState extends ConsumerState { final externalCalls = ref.watch( prefsChangeNotifierProvider.select((value) => value.externalCalls)); - return GestureDetector( - onTap: () { - if (Util.isDesktop) { - Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: walletId, - ); - } else { - Navigator.of(context).pushNamed( - WalletView.routeName, - arguments: Tuple2( - walletId, - managerProvider, - ), - ); - } - }, - child: SizedBox( - width: widget.width, - height: widget.height, - child: CardOverlayStack( - background: Stack( - children: [ - Container( - width: widget.width, - height: widget.height, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .colorForCoin(coin), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (Util.isDesktop) { + Navigator.of(context).pushNamed( + DesktopWalletView.routeName, + arguments: walletId, + ); + } else { + Navigator.of(context).pushNamed( + WalletView.routeName, + arguments: Tuple2( + walletId, + managerProvider, ), - Column( - children: [ - const Spacer(), - SizedBox( - height: widget.width * 0.3, - child: Row( - children: [ - const Spacer( - flex: 9, - ), - SvgPicture.asset( - Assets.svg.ellipse2, - height: widget.width * 0.3, - ), - // ), - const Spacer( - flex: 2, - ), - ], - ), - ), - ], - ), - Row( - children: [ - const Spacer( - flex: 5, - ), - SizedBox( - width: widget.width * 0.45, - child: Column( - children: [ - SvgPicture.asset( - Assets.svg.ellipse1, - width: widget.width * 0.45, - ), - const Spacer(), - ], - ), - ), - const Spacer( - flex: 1, - ), - ], - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + } + }, + child: SizedBox( + width: widget.width, + height: widget.height, + child: CardOverlayStack( + background: Stack( children: [ - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - ref.watch(managerProvider - .select((value) => value.walletName)), - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - overflow: TextOverflow.fade, - ), - ), - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 24, - height: 24, - ), - ], + Container( + width: widget.width, + height: widget.height, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .colorForCoin(coin), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), ), - FutureBuilder( - future: ref.watch( - managerProvider.select((value) => value.totalBalance)), - builder: (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - if (snapshot.data != null) { - _cachedBalance = snapshot.data!; - if (externalCalls) { - _cachedFiatValue = _cachedBalance * - ref - .watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin), - ), - ) - .item1; - } - } - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "${Format.localizedStringAsFixed( - decimalPlaces: 8, - value: _cachedBalance, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${coin.ticker}", - style: STextStyles.titleBold12(context).copyWith( - fontSize: 16, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), + Column( + children: [ + const Spacer(), + SizedBox( + height: widget.width * 0.3, + child: Row( + children: [ + const Spacer( + flex: 9, ), - ), - if (externalCalls) - const SizedBox( - height: 4, + SvgPicture.asset( + Assets.svg.ellipse2, + height: widget.width * 0.3, ), - if (externalCalls) - Text( - "${Format.localizedStringAsFixed( - decimalPlaces: 2, - value: _cachedFiatValue, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( - prefsChangeNotifierProvider - .select((value) => value.currency), - )}", - style: STextStyles.itemSubtitle12(context).copyWith( - fontSize: 10, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), + // ), + const Spacer( + flex: 2, ), - ], - ); - }, + ], + ), + ), + ], + ), + Row( + children: [ + const Spacer( + flex: 5, + ), + SizedBox( + width: widget.width * 0.45, + child: Column( + children: [ + SvgPicture.asset( + Assets.svg.ellipse1, + width: widget.width * 0.45, + ), + const Spacer(), + ], + ), + ), + const Spacer( + flex: 1, + ), + ], ), ], ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + ref.watch(managerProvider + .select((value) => value.walletName)), + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + overflow: TextOverflow.fade, + ), + ), + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + ], + ), + ), + FutureBuilder( + future: ref.watch( + managerProvider.select((value) => value.totalBalance)), + builder: (builderContext, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + if (snapshot.data != null) { + _cachedBalance = snapshot.data!; + if (externalCalls) { + _cachedFiatValue = _cachedBalance * + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ) + .item1; + } + } + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "${Format.localizedStringAsFixed( + decimalPlaces: 8, + value: _cachedBalance, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${coin.ticker}", + style: STextStyles.titleBold12(context).copyWith( + fontSize: 16, + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + ), + ), + if (externalCalls) + const SizedBox( + height: 4, + ), + if (externalCalls) + Text( + "${Format.localizedStringAsFixed( + decimalPlaces: 2, + value: _cachedFiatValue, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + prefsChangeNotifierProvider + .select((value) => value.currency), + )}", + style: + STextStyles.itemSubtitle12(context).copyWith( + fontSize: 10, + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + ), + ], + ); + }, + ), + ], + ), + ), ), ), ), From dfc52e3e7e6a3f3169b3e3c410ccbd0920a9e963 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 12:29:51 -0700 Subject: [PATCH 083/225] unsuccessful login lag fixed --- lib/pages_desktop_specific/desktop_login_view.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index ebd1c334b..f60ce2240 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -55,6 +55,8 @@ class _DesktopLoginViewState extends ConsumerState { ), ); + await Future.delayed(const Duration(seconds: 1)); + await ref .read(storageCryptoHandlerProvider) .initFromExisting(passwordController.text); @@ -79,6 +81,8 @@ class _DesktopLoginViewState extends ConsumerState { // pop loading indicator Navigator.of(context).pop(); + await Future.delayed(const Duration(seconds: 1)); + await showFloatingFlushBar( type: FlushBarType.warning, message: e.toString(), From 5f863bb35a49f661bea9a4b3bfeea19e1989f4a1 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 13:18:32 -0700 Subject: [PATCH 084/225] QR button on desktop Send commented out --- .../wallet_view/sub_widgets/desktop_send.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index cd19c53f8..ec9a1620a 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -40,7 +40,6 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -1247,12 +1246,12 @@ class _DesktopSendState extends ConsumerState { }, child: const AddressBookIcon(), ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key("sendViewScanQrButtonKey"), - onTap: scanQr, - child: const QrCodeIcon(), - ) + // if (sendToController.text.isEmpty) + // TextFieldIconButton( + // key: const Key("sendViewScanQrButtonKey"), + // onTap: scanQr, + // child: const QrCodeIcon(), + // ) ], ), ), From bd0b01efcd71d9cee38cd26eb3f7fb37e2c7fcdf Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 15:51:49 -0700 Subject: [PATCH 085/225] desktop debug shows search bar when scrolling --- .../advanced_settings/debug_info_dialog.dart | 122 ++++++++---------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart index cf687e3e7..3235a5549 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart @@ -108,10 +108,65 @@ class _DebugInfoDialog extends ConsumerState { const DesktopDialogCloseButton(), ], ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: searchDebugController, + focusNode: searchDebugFocusNode, + onChanged: (newString) { + setState(() => _searchTerm = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + searchDebugFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: searchDebugController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + searchDebugController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), Expanded( // flex: 24, child: NestedScrollView( - floatHeaderSlivers: true, + // floatHeaderSlivers: true, headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverOverlapAbsorber( @@ -122,70 +177,7 @@ class _DebugInfoDialog extends ConsumerState { padding: const EdgeInsets.symmetric( vertical: 16, horizontal: 32), child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: - Util.isDesktop ? false : true, - controller: searchDebugController, - focusNode: searchDebugFocusNode, - onChanged: (newString) { - setState(() => _searchTerm = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - searchDebugFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: searchDebugController - .text.isNotEmpty - ? Padding( - padding: - const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - searchDebugController - .text = ""; - _searchTerm = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - ), - const SizedBox( - height: 12, - ), - ], + children: const [], ), ), ), From 2936249bd6190be934a3623457ce2bb679b1cccc Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 16 Nov 2022 16:36:50 -0700 Subject: [PATCH 086/225] textfields clear on send --- .../wallet_view/sub_widgets/desktop_send.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index ec9a1620a..af2c2517a 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -248,6 +248,13 @@ class _DesktopSendState extends ConsumerState { label: "Yes", onPressed: () { Navigator.of(context).pop(true); + + setState(() { + sendToController.text = ""; + cryptoAmountController.text = ""; + baseAmountController.text = ""; + noteController.text = ""; + }); }, ), ), From fc9e4d35dd3d7da66d10a587a052558feee0397d Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 07:02:56 -0600 Subject: [PATCH 087/225] remove loading future --- .../address_book_views/address_book_view.dart | 221 +++++++++--------- 1 file changed, 112 insertions(+), 109 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 50e51110b..fd2a995cc 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -18,7 +18,6 @@ import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -38,9 +37,9 @@ class _AddressBookViewState extends ConsumerState { late TextEditingController _searchController; final _searchFocusNode = FocusNode(); - - List? _cache; - List? _cacheFav; + // + // List? _cache; + // List? _cacheFav; String _searchTerm = ""; @@ -100,8 +99,10 @@ class _AddressBookViewState extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final addressBookEntriesFuture = ref.watch( - addressBookServiceProvider.select((value) => value.addressBookEntries)); + // final addressBookEntriesFuture = ref.watch( + // addressBookServiceProvider.select((value) => value.addressBookEntries)); + final contacts = + ref.watch(addressBookServiceProvider.select((value) => value.contacts)); final isDesktop = Util.isDesktop; return ConditionalParent( @@ -279,57 +280,58 @@ class _AddressBookViewState extends ConsumerState { const SizedBox( height: 12, ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - _cacheFav = snapshot.data!; - } - if (_cacheFav == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cacheFav!.isNotEmpty) { - return RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Column( - children: [ - ..._cacheFav! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select( - (value) => - value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => - e.isFavorite && - ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), + // FutureBuilder( + // future: addressBookEntriesFuture, + // builder: (_, AsyncSnapshot> snapshot) { + // if (snapshot.connectionState == ConnectionState.done && + // snapshot.hasData) { + // _cacheFav = snapshot.data!; + // } + // if (_cacheFav == null) { + // // TODO proper loading animation + // return const LoadingIndicator(); + // } else { + // if (_cacheFav!.isNotEmpty) { + // return + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch(addressBookFilterProvider + .select((value) => value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, ), ), - ); - } - } - }, - ), + ], + ), + ) + // ; + // } else { + // return RoundedWhiteContainer( + // child: Center( + // child: Text( + // "Your favorite contacts will appear here", + // style: STextStyles.itemSubtitle(context), + // ), + // ), + // ); + // } + // } + // }, + // ) + , const SizedBox( height: 16, ), @@ -340,63 +342,64 @@ class _AddressBookViewState extends ConsumerState { const SizedBox( height: 12, ), - FutureBuilder( - future: addressBookEntriesFuture, - builder: (_, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - _cache = snapshot.data!; - } - if (_cache == null) { - // TODO proper loading animation - return const LoadingIndicator(); - } else { - if (_cache!.isNotEmpty) { - return Column( + // FutureBuilder( + // future: addressBookEntriesFuture, + // builder: (_, AsyncSnapshot> snapshot) { + // if (snapshot.connectionState == ConnectionState.done && + // snapshot.hasData) { + // _cache = snapshot.data!; + // } + // if (_cache == null) { + // // TODO proper loading animation + // return const LoadingIndicator(); + // } else { + // if (_cache!.isNotEmpty) { + // return + Column( + children: [ + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( children: [ - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - ..._cache! - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select( - (value) => value.coins - .contains(e.coin)))) - .isNotEmpty) - .where((e) => ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => !element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key( - "desktopContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select((value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => !element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("desktopContactCard_${e.id}_key"), + contactId: e.id, + ), ), - ), - ), ], - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), - ); - } - } - }, - ), + ), + ), + ), + ], + ) + // ; + // } else { + // return RoundedWhiteContainer( + // child: Center( + // child: Text( + // "Your contacts will appear here", + // style: STextStyles.itemSubtitle(context), + // ), + // ), + // ); + // } + // } + // }, + // ) + , ], ), ), From 7e2160d7ccf37fca61f918771ed7337b71fbd153 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 07:03:09 -0600 Subject: [PATCH 088/225] fix duplicate keys error --- .../address_book_views/subviews/contact_details_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/address_book_views/subviews/contact_details_view.dart b/lib/pages/address_book_views/subviews/contact_details_view.dart index c0c10b3b1..a48a535c6 100644 --- a/lib/pages/address_book_views/subviews/contact_details_view.dart +++ b/lib/pages/address_book_views/subviews/contact_details_view.dart @@ -469,7 +469,7 @@ class _ContactDetailsViewState extends ConsumerState { ..._cachedTransactions.map( (e) => TransactionCard( key: Key( - "contactDetailsTransaction_${e.item2.txid}_cardKey"), + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), transaction: e.item2, walletId: e.item1, ), @@ -499,7 +499,7 @@ class _ContactDetailsViewState extends ConsumerState { ..._cachedTransactions.map( (e) => TransactionCard( key: Key( - "contactDetailsTransaction_${e.item2.txid}_cardKey"), + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), transaction: e.item2, walletId: e.item1, ), From e0ef78685ddf9450ac62140544b74c4c8ce8b068 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 07:10:28 -0600 Subject: [PATCH 089/225] empty contacts list fix --- .../address_book_views/address_book_view.dart | 175 +++++++----------- 1 file changed, 69 insertions(+), 106 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index fd2a995cc..147e677e0 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -99,8 +99,6 @@ class _AddressBookViewState extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - // final addressBookEntriesFuture = ref.watch( - // addressBookServiceProvider.select((value) => value.addressBookEntries)); final contacts = ref.watch(addressBookServiceProvider.select((value) => value.contacts)); @@ -280,58 +278,41 @@ class _AddressBookViewState extends ConsumerState { const SizedBox( height: 12, ), - // FutureBuilder( - // future: addressBookEntriesFuture, - // builder: (_, AsyncSnapshot> snapshot) { - // if (snapshot.connectionState == ConnectionState.done && - // snapshot.hasData) { - // _cacheFav = snapshot.data!; - // } - // if (_cacheFav == null) { - // // TODO proper loading animation - // return const LoadingIndicator(); - // } else { - // if (_cacheFav!.isNotEmpty) { - // return - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Column( - children: [ - ...contacts - .where((element) => element.addresses - .where((e) => ref.watch(addressBookFilterProvider - .select((value) => value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => - e.isFavorite && - ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, + if (contacts.isNotEmpty) + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select( + (value) => value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .where((element) => element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, + ), ), - ), - ], + ], + ), + ), + if (contacts.isEmpty) + RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), ), - ) - // ; - // } else { - // return RoundedWhiteContainer( - // child: Center( - // child: Text( - // "Your favorite contacts will appear here", - // style: STextStyles.itemSubtitle(context), - // ), - // ), - // ); - // } - // } - // }, - // ) - , const SizedBox( height: 16, ), @@ -342,64 +323,46 @@ class _AddressBookViewState extends ConsumerState { const SizedBox( height: 12, ), - // FutureBuilder( - // future: addressBookEntriesFuture, - // builder: (_, AsyncSnapshot> snapshot) { - // if (snapshot.connectionState == ConnectionState.done && - // snapshot.hasData) { - // _cache = snapshot.data!; - // } - // if (_cache == null) { - // // TODO proper loading animation - // return const LoadingIndicator(); - // } else { - // if (_cache!.isNotEmpty) { - // return - Column( - children: [ - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - ...contacts - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select((value) => - value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => !element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("desktopContactCard_${e.id}_key"), - contactId: e.id, + if (contacts.isNotEmpty) + Column( + children: [ + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select( + (value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => ref + .read(addressBookServiceProvider) + .matches(_searchTerm, e)) + .map( + (e) => AddressBookCard( + key: Key("desktopContactCard_${e.id}_key"), + contactId: e.id, + ), ), - ), - ], + ], + ), ), ), + ], + ), + if (contacts.isEmpty) + RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), ), - ], - ) - // ; - // } else { - // return RoundedWhiteContainer( - // child: Center( - // child: Text( - // "Your contacts will appear here", - // style: STextStyles.itemSubtitle(context), - // ), - // ), - // ); - // } - // } - // }, - // ) - , + ), ], ), ), From 7cc3c71b0d1949401d79dcbe453b0dd3783a8a27 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 07:22:53 -0600 Subject: [PATCH 090/225] desktop addressbook search --- .../address_book_views/address_book_view.dart | 306 +++++++++--------- .../desktop_address_book.dart | 14 +- 2 files changed, 158 insertions(+), 162 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 147e677e0..35e2601e2 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -23,11 +23,16 @@ import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; class AddressBookView extends ConsumerStatefulWidget { - const AddressBookView({Key? key, this.coin}) : super(key: key); + const AddressBookView({ + Key? key, + this.coin, + this.filterTerm, + }) : super(key: key); static const String routeName = "/addressBook"; final Coin? coin; + final String? filterTerm; @override ConsumerState createState() => _AddressBookViewState(); @@ -37,9 +42,6 @@ class _AddressBookViewState extends ConsumerState { late TextEditingController _searchController; final _searchFocusNode = FocusNode(); - // - // List? _cache; - // List? _cacheFav; String _searchTerm = ""; @@ -198,7 +200,12 @@ class _AddressBookViewState extends ConsumerState { child: IntrinsicHeight( child: Padding( padding: const EdgeInsets.all(4), - child: child, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height - 271, + ), + child: child, + ), ), ), ), @@ -208,163 +215,156 @@ class _AddressBookViewState extends ConsumerState { ), ); }, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height - 271, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: !isDesktop - ? TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (value) { - setState(() { - _searchTerm = value; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: !isDesktop + ? TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, ), - ) - : null, - ), - if (!isDesktop) const SizedBox(height: 16), - Text( - "Favorites", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - if (contacts.isNotEmpty) - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Column( - children: [ - ...contacts - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select( - (value) => value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => - e.isFavorite && - ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .where((element) => element.isFavorite) - .map( - (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, - ), - ), - ], - ), - ), - if (contacts.isEmpty) - RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), - ), - const SizedBox( - height: 16, - ), - Text( - "All contacts", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - if (contacts.isNotEmpty) - Column( - children: [ - RoundedWhiteContainer( - padding: EdgeInsets.all(!isDesktop ? 0 : 15), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - ...contacts - .where((element) => element.addresses - .where((e) => ref.watch( - addressBookFilterProvider.select( - (value) => - value.coins.contains(e.coin)))) - .isNotEmpty) - .where((e) => ref - .read(addressBookServiceProvider) - .matches(_searchTerm, e)) - .map( - (e) => AddressBookCard( - key: Key("desktopContactCard_${e.id}_key"), - contactId: e.id, + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + }, + ), + ], ), ), - ], - ), + ) + : null, ), - ), + ) + : null, + ), + if (!isDesktop) const SizedBox(height: 16), + Text( + "Favorites", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + if (contacts.isNotEmpty) + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Column( + children: [ + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch(addressBookFilterProvider + .select((value) => value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref + .read(addressBookServiceProvider) + .matches(widget.filterTerm ?? _searchTerm, e)) + .where((element) => element.isFavorite) + .map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, + ), + ), ], ), - if (contacts.isEmpty) - RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), + ), + if (contacts.isEmpty) + RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), ), ), - ], - ), + ), + const SizedBox( + height: 16, + ), + Text( + "All contacts", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + if (contacts.isNotEmpty) + Column( + children: [ + RoundedWhiteContainer( + padding: EdgeInsets.all(!isDesktop ? 0 : 15), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + ...contacts + .where((element) => element.addresses + .where((e) => ref.watch( + addressBookFilterProvider.select((value) => + value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => ref + .read(addressBookServiceProvider) + .matches(widget.filterTerm ?? _searchTerm, e)) + .map( + (e) => AddressBookCard( + key: Key("desktopContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ), + ), + ], + ), + if (contacts.isEmpty) + RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ], ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index ec40e5f60..d561de946 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; -import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -34,11 +32,6 @@ class _DesktopAddressBook extends ConsumerState { late final FocusNode _searchFocusNode; - List? _cache; - List? _cacheFav; - - late bool hasContacts = false; - String _searchTerm = ""; Future selectCryptocurrency() async { @@ -90,7 +83,6 @@ class _DesktopAddressBook extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; return DesktopScaffold( appBar: DesktopAppBar( @@ -171,7 +163,11 @@ class _DesktopAddressBook extends ConsumerState { const SizedBox( height: 24, ), - const AddressBookView(), + Expanded( + child: AddressBookView( + filterTerm: _searchTerm, + ), + ), ], ), ), From 49103c86f1b63bd56563a0aa1167bcda5ee69cd8 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 09:00:10 -0600 Subject: [PATCH 091/225] desktop addressbook layout fix --- .../desktop_address_book.dart | 442 +++++++++++++----- 1 file changed, 338 insertions(+), 104 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index d561de946..abb797aac 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -1,23 +1,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import '../../../providers/providers.dart'; +import '../../../providers/ui/address_book_providers/address_book_filter_provider.dart'; + class DesktopAddressBook extends ConsumerStatefulWidget { const DesktopAddressBook({Key? key}) : super(key: key); @@ -69,6 +77,46 @@ class _DesktopAddressBook extends ConsumerState { _searchController = TextEditingController(); _searchFocusNode = FocusNode(); + ref.refresh(addressBookFilterProvider); + + // if (widget.coin == null) { + List coins = Coin.values.where((e) => !(e == Coin.epicCash)).toList(); + coins.remove(Coin.firoTestNet); + + bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins; + + if (showTestNet) { + ref.read(addressBookFilterProvider).addAll(coins, false); + } else { + ref.read(addressBookFilterProvider).addAll( + coins.getRange(0, coins.length - kTestNetCoinCount + 1), false); + } + // } else { + // ref.read(addressBookFilterProvider).add(widget.coin!, false); + // } + + WidgetsBinding.instance.addPostFrameCallback((_) async { + List addresses = []; + final managers = ref.read(walletsChangeNotifierProvider).managers; + for (final manager in managers) { + addresses.add( + ContactAddressEntry( + coin: manager.coin, + address: await manager.currentReceivingAddress, + label: "Current Receiving", + other: manager.walletName, + ), + ); + } + final self = Contact( + name: "My Stack", + addresses: addresses, + isFavorite: true, + id: "default", + ); + await ref.read(addressBookServiceProvider).editContact(self); + }); + super.initState(); } @@ -83,6 +131,32 @@ class _DesktopAddressBook extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + final contacts = + ref.watch(addressBookServiceProvider.select((value) => value.contacts)); + + final allContacts = contacts + .where((element) => element.addresses + .where((e) => ref.watch(addressBookFilterProvider + .select((value) => value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + ref.read(addressBookServiceProvider).matches(_searchTerm, e)); + + final favorites = contacts + .where((element) => element.addresses + .where((e) => ref.watch(addressBookFilterProvider + .select((value) => value.coins.contains(e.coin)))) + .isNotEmpty) + .where((e) => + e.isFavorite && + ref.read(addressBookServiceProvider).matches(_searchTerm, e)) + .where((element) => element.isFavorite); + + print("========================================================="); + print("contacts: ${contacts.length}"); + print("favorites: ${favorites.length}"); + print("allContacts: ${allContacts.length}"); + print("========================================================="); return DesktopScaffold( appBar: DesktopAppBar( @@ -100,121 +174,281 @@ class _DesktopAddressBook extends ConsumerState { ), ), body: Padding( - padding: const EdgeInsets.all(24), - child: Row( + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: DesktopAddressBookScaffold( + controlsLeft: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 20, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + controlsRight: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SecondaryButton( + width: 184, + label: "Filter", + desktopMed: true, + icon: SvgPicture.asset( + Assets.svg.filter, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: selectCryptocurrency, + ), + const SizedBox( + width: 20, + ), + PrimaryButton( + width: 184, + label: "Add new", + desktopMed: true, + icon: SvgPicture.asset( + Assets.svg.circlePlus, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + onPressed: newContact, + ), + ], + ), + filterItems: Container(), + upperLabel: favorites.isEmpty && allContacts.isEmpty + ? null + : Text( + favorites.isEmpty ? "All contacts" : "Favorites", + style: STextStyles.smallMed12(context), + ), + lowerLabel: favorites.isEmpty + ? null + : Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 12, + ), + child: Text( + "All contacts", + style: STextStyles.smallMed12(context), + ), + ), + favorites: favorites.isNotEmpty + ? RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + ...favorites.map( + (e) => AddressBookCard( + key: Key("favContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ) + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + all: allContacts.isNotEmpty + ? Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + ...allContacts.map( + (e) => AddressBookCard( + key: Key("desktopContactCard_${e.id}_key"), + contactId: e.id, + ), + ), + ], + ), + ), + ), + ], + ) + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + details: Container( + color: Colors.purple, + ), + ), + ), + ); + } +} + +class DesktopAddressBookScaffold extends StatelessWidget { + const DesktopAddressBookScaffold({ + Key? key, + required this.controlsLeft, + required this.controlsRight, + required this.filterItems, + required this.upperLabel, + required this.lowerLabel, + required this.favorites, + required this.all, + required this.details, + }) : super(key: key); + + final Widget? controlsLeft; + final Widget? controlsRight; + final Widget? filterItems; + final Widget? upperLabel; + final Widget? lowerLabel; + final Widget? favorites; + final Widget? all; + final Widget? details; + + static const double weirdRowHeight = 30; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ Expanded( flex: 6, - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (value) { - setState(() { - _searchTerm = value; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 20, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 24, - ), - Expanded( - child: AddressBookView( - filterTerm: _searchTerm, - ), - ), - ], - ), + child: controlsLeft ?? Container(), ), const SizedBox( width: 20, ), Expanded( flex: 5, - child: Column( - children: [ - Row( - children: [ - SecondaryButton( - width: 184, - label: "Filter", - desktopMed: true, - icon: SvgPicture.asset( - Assets.svg.filter, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - onPressed: selectCryptocurrency, - ), - const SizedBox( - width: 20, - ), - PrimaryButton( - width: 184, - label: "Add new", - desktopMed: true, - icon: SvgPicture.asset( - Assets.svg.circlePlus, - color: Theme.of(context) - .extension()! - .buttonTextPrimary, - ), - onPressed: newContact, - ), - ], - ), - ], - ), + child: controlsRight ?? Container(), ), ], ), - ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: filterItems ?? Container(), + ), + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + flex: 6, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: weirdRowHeight, + child: upperLabel, + ), + favorites ?? Container(), + lowerLabel ?? Container(), + all ?? Container(), + ], + ), + ), + ), + ); + }, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: Column( + children: [ + const SizedBox( + height: weirdRowHeight, + ), + Expanded( + child: details ?? Container(), + ), + ], + ), + ), + ], + ), + ) + ], ); } } From 8c0a6f5669de3d0bf4aa8b6c5329ff4d09e6bc30 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 09:04:54 -0600 Subject: [PATCH 092/225] address book search fixes --- .../address_book_views/address_book_view.dart | 1 + .../desktop_address_book.dart | 51 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 35e2601e2..cdc9fb5b7 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -261,6 +261,7 @@ class _AddressBookViewState extends ConsumerState { onTap: () async { setState(() { _searchController.text = ""; + _searchTerm = ""; }); }, ), diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index abb797aac..51b075284 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -152,12 +152,6 @@ class _DesktopAddressBook extends ConsumerState { ref.read(addressBookServiceProvider).matches(_searchTerm, e)) .where((element) => element.isFavorite); - print("========================================================="); - print("contacts: ${contacts.length}"); - print("favorites: ${favorites.length}"); - print("allContacts: ${allContacts.length}"); - print("========================================================="); - return DesktopScaffold( appBar: DesktopAppBar( isCompactHeight: true, @@ -222,6 +216,7 @@ class _DesktopAddressBook extends ConsumerState { onTap: () async { setState(() { _searchController.text = ""; + _searchTerm = ""; }); }, ), @@ -284,8 +279,18 @@ class _DesktopAddressBook extends ConsumerState { style: STextStyles.smallMed12(context), ), ), - favorites: favorites.isNotEmpty - ? RoundedWhiteContainer( + favorites: favorites.isEmpty + ? contacts.isNotEmpty + ? null + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your favorite contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ) + : RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: Column( children: [ @@ -297,17 +302,19 @@ class _DesktopAddressBook extends ConsumerState { ), ], ), - ) - : RoundedWhiteContainer( - child: Center( - child: Text( - "Your favorite contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), ), - all: allContacts.isNotEmpty - ? Column( + all: allContacts.isEmpty + ? contacts.isNotEmpty + ? null + : RoundedWhiteContainer( + child: Center( + child: Text( + "Your contacts will appear here", + style: STextStyles.itemSubtitle(context), + ), + ), + ) + : Column( children: [ RoundedWhiteContainer( padding: const EdgeInsets.all(0), @@ -326,14 +333,6 @@ class _DesktopAddressBook extends ConsumerState { ), ), ], - ) - : RoundedWhiteContainer( - child: Center( - child: Text( - "Your contacts will appear here", - style: STextStyles.itemSubtitle(context), - ), - ), ), details: Container( color: Colors.purple, From 72248d6a644807d6f7410c31529ae3a57869066f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 10:12:19 -0600 Subject: [PATCH 093/225] expandable fix --- .../wallet_network_settings_view.dart | 10 +++++----- .../sub_widgets/contact_list_item.dart | 2 +- lib/widgets/expandable.dart | 6 +++--- lib/widgets/node_card.dart | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index accf244eb..3044467aa 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -77,7 +77,7 @@ class _WalletNetworkSettingsViewState late double _percent; late int _blocksRemaining; - bool _advancedIsExpanded = true; + bool _advancedIsExpanded = false; Future _attemptRescan() async { if (!Platform.isLinux) await Wakelock.enable(); @@ -855,8 +855,8 @@ class _WalletNetworkSettingsViewState ), SvgPicture.asset( _advancedIsExpanded - ? Assets.svg.chevronDown - : Assets.svg.chevronUp, + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, width: 12, height: 6, color: Theme.of(context) @@ -877,11 +877,11 @@ class _WalletNetworkSettingsViewState text: "Rescan", onTap: () async { await Navigator.of(context).push( - FadePageRoute( + FadePageRoute( ConfirmFullRescanDialog( onConfirm: _attemptRescan, ), - const RouteSettings(), + const RouteSettings(), ), ); // await showDialog( diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart index e030f9882..7acfaae9e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -58,7 +58,7 @@ class _ContactListItemState extends ConsumerState { ), child: AddressBookCard( contactId: contactId, - indicatorDown: _state == ExpandableState.expanded, + indicatorDown: _state, ), ), body: Column( diff --git a/lib/widgets/expandable.dart b/lib/widgets/expandable.dart index 47726d6d6..737f4ce7d 100644 --- a/lib/widgets/expandable.dart +++ b/lib/widgets/expandable.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; enum ExpandableState { - expanded, collapsed, + expanded, } class ExpandableController { @@ -45,11 +45,11 @@ class _ExpandableState extends State with TickerProviderStateMixin { Future toggle() async { if (animation.isDismissed) { await animationController.forward(); - _toggleState = ExpandableState.collapsed; + _toggleState = ExpandableState.expanded; widget.onExpandChanged?.call(_toggleState); } else if (animation.isCompleted) { await animationController.reverse(); - _toggleState = ExpandableState.expanded; + _toggleState = ExpandableState.collapsed; widget.onExpandChanged?.call(_toggleState); } controller?.state = _toggleState; diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index 1da7e9012..c3fb36c70 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -46,7 +46,7 @@ class NodeCard extends ConsumerStatefulWidget { class _NodeCardState extends ConsumerState { String _status = "Disconnected"; late final String nodeId; - bool _advancedIsExpanded = true; + bool _advancedIsExpanded = false; Future _notifyWalletsOfUpdatedNode(WidgetRef ref) async { final managers = ref @@ -367,8 +367,8 @@ class _NodeCardState extends ConsumerState { if (isDesktop) SvgPicture.asset( _advancedIsExpanded - ? Assets.svg.chevronDown - : Assets.svg.chevronUp, + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, width: 12, height: 6, color: Theme.of(context) From df810c2a1449458e046aa994960b2ccdd5cbeecb Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 10:12:38 -0600 Subject: [PATCH 094/225] "send from" contacts fix --- .../address_book_address_chooser.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart index 9f309a08e..92dd9f6fc 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart @@ -200,12 +200,19 @@ class _AddressBookAddressChooserState extends State { final favorites = pullOutFavorites(contacts); - return ListView.builder( + final totalLength = favorites.length + + contacts.length + + 2; // +2 for "fav" and "all" headers + + return ListView.separated( primary: false, shrinkWrap: true, - itemCount: favorites.length + - contacts.length + - 2, // +2 for "fav" and "all" headers + itemCount: totalLength, + separatorBuilder: (context, index) { + return const SizedBox( + height: 10, + ); + }, itemBuilder: (context, index) { if (index == 0) { return Padding( @@ -220,7 +227,7 @@ class _AddressBookAddressChooserState extends State { STextStyles.desktopTextExtraExtraSmall(context), ), ); - } else if (index <= favorites.length) { + } else if (index < favorites.length + 1) { final id = favorites[index - 1].id; return ContactListItem( key: Key("contactContactListItem_${id}_key"), @@ -241,7 +248,7 @@ class _AddressBookAddressChooserState extends State { ), ); } else { - final id = contacts[index - favorites.length - 1].id; + final id = contacts[index - favorites.length - 2].id; return ContactListItem( key: Key("contactContactListItem_${id}_key"), contactId: id, From b988342bb146d1085f64527b4ce28f7765532c6f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 10:12:59 -0600 Subject: [PATCH 095/225] "send from" contact card fix --- lib/widgets/address_book_card.dart | 185 +++++++++++++++-------------- 1 file changed, 95 insertions(+), 90 deletions(-) diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index c9ac86052..329e35fdf 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -9,6 +9,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class AddressBookCard extends ConsumerStatefulWidget { @@ -19,7 +21,7 @@ class AddressBookCard extends ConsumerStatefulWidget { }) : super(key: key); final String contactId; - final bool? indicatorDown; + final ExpandableState? indicatorDown; @override ConsumerState createState() => _AddressBookCardState(); @@ -58,108 +60,111 @@ class _AddressBookCardState extends ConsumerState { } } - return RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => ContactPopUp( - contactId: contact.id, + return ConditionalParent( + condition: !isDesktop, + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Theme.of(context) + .extension()! + .myStackContactIconBG + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: contact.id == "default" - ? Theme.of(context) - .extension()! - .myStackContactIconBG - : Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(32), - ), - child: contact.id == "default" + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 20, + ), + ) + : contact.emojiChar != null ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 20, - ), + child: Text(contact.emojiChar!), ) - : contact.emojiChar != null - ? Center( - child: Text(contact.emojiChar!), - ) - : Center( - child: SvgPicture.asset( - Assets.svg.user, - width: 18, - ), - ), - ), - const SizedBox( - width: 12, - ), - if (isDesktop) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 12, + ), + if (isDesktop) + Text( + contact.name, + style: STextStyles.itemSubtitle12(context), + ), + if (isDesktop) + const SizedBox( + width: 16, + ), + if (isDesktop) + Text( + coinsString, + style: STextStyles.label(context), + ), + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( contact.name, style: STextStyles.itemSubtitle12(context), ), - if (isDesktop) const SizedBox( - width: 16, + height: 4, ), - if (isDesktop) Text( coinsString, style: STextStyles.label(context), ), - if (!isDesktop) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - contact.name, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 4, - ), - Text( - coinsString, - style: STextStyles.label(context), - ), - ], - ), - if (isDesktop) const Spacer(), - // if (isDesktop) - // SvgPicture.asset( - // widget.indicatorDown == true - // ? Assets.svg.chevronDown - // : Assets.svg.chevronUp, - // width: 10, - // height: 5, - // color: - // Theme.of(context).extension()!.textSubtitle2, - // ), - ], + ], + ), + if (isDesktop) const Spacer(), + if (isDesktop) + SvgPicture.asset( + widget.indicatorDown == ExpandableState.collapsed + ? Assets.svg.chevronDown + : Assets.svg.chevronUp, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textSubtitle2, + ), + ], + ), + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => ContactPopUp( + contactId: contact.id, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: child, ), ), ), From b6e4357c3c63a6567fbdf2809ffb0b9c4b22b4a7 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 10:23:12 -0600 Subject: [PATCH 096/225] wallet overview syncing/loading balance text color fix for darkmode --- .../wallet_view/sub_widgets/desktop_wallet_summary.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 7a9e93467..f4bfed976 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -243,7 +243,7 @@ class _WDesktopWalletSummaryState extends State { fontSize: 24, color: Theme.of(context) .extension()! - .textFavoriteCard, + .textDark, ), ), if (externalCalls) From 81d5f757b3d039528f523df3af8b8d1cca21a81e Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 11:10:26 -0600 Subject: [PATCH 097/225] WIP: desktop contact details --- .../desktop_address_book.dart | 252 ++++++++---------- .../desktop_address_book_scaffold.dart | 111 ++++++++ .../subwidgets/desktop_contact_details.dart | 191 +++++++++++++ lib/widgets/address_book_card.dart | 9 +- 4 files changed, 427 insertions(+), 136 deletions(-) create mode 100644 lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart create mode 100644 lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 51b075284..5e22a6089 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -5,7 +5,11 @@ import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -19,13 +23,11 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import '../../../providers/providers.dart'; -import '../../../providers/ui/address_book_providers/address_book_filter_provider.dart'; - class DesktopAddressBook extends ConsumerStatefulWidget { const DesktopAddressBook({Key? key}) : super(key: key); @@ -42,6 +44,8 @@ class _DesktopAddressBook extends ConsumerState { String _searchTerm = ""; + String? currentContactId; + Future selectCryptocurrency() async { await showDialog( context: context, @@ -139,8 +143,9 @@ class _DesktopAddressBook extends ConsumerState { .where((e) => ref.watch(addressBookFilterProvider .select((value) => value.coins.contains(e.coin)))) .isNotEmpty) - .where((e) => - ref.read(addressBookServiceProvider).matches(_searchTerm, e)); + .where( + (e) => ref.read(addressBookServiceProvider).matches(_searchTerm, e)) + .toList(); final favorites = contacts .where((element) => element.addresses @@ -150,7 +155,8 @@ class _DesktopAddressBook extends ConsumerState { .where((e) => e.isFavorite && ref.read(addressBookServiceProvider).matches(_searchTerm, e)) - .where((element) => element.isFavorite); + .where((element) => element.isFavorite) + .toList(); return DesktopScaffold( appBar: DesktopAppBar( @@ -294,12 +300,56 @@ class _DesktopAddressBook extends ConsumerState { padding: const EdgeInsets.all(0), child: Column( children: [ - ...favorites.map( - (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, + for (int i = 0; i < favorites.length; i++) + Column( + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .accentColorDark + .withOpacity( + currentContactId == favorites[i].id + ? 0.08 + : 0, + ), + child: RawMaterialButton( + onPressed: () { + setState(() { + currentContactId = favorites[i].id; + }); + }, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: AddressBookCard( + key: Key( + "favContactCard_${favorites[i].id}_key"), + contactId: favorites[i].id, + desktopSendFrom: false, + ), + ), + ), + ), + ], ), - ), ], ), ), @@ -318,136 +368,70 @@ class _DesktopAddressBook extends ConsumerState { children: [ RoundedWhiteContainer( padding: const EdgeInsets.all(0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - ...allContacts.map( - (e) => AddressBookCard( - key: Key("desktopContactCard_${e.id}_key"), - contactId: e.id, - ), + child: Column( + children: [ + for (int i = 0; i < allContacts.length; i++) + Column( + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .accentColorDark + .withOpacity( + currentContactId == allContacts[i].id + ? 0.08 + : 0, + ), + child: RawMaterialButton( + onPressed: () { + setState(() { + currentContactId = allContacts[i].id; + }); + }, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: AddressBookCard( + key: Key( + "favContactCard_${allContacts[i].id}_key"), + contactId: allContacts[i].id, + desktopSendFrom: false, + ), + ), + ), + ), + ], ), - ], - ), + ], ), ), ], ), - details: Container( - color: Colors.purple, - ), + details: currentContactId == null + ? Container() + : DesktopContactDetails( + contactId: currentContactId!, + ), ), ), ); } } - -class DesktopAddressBookScaffold extends StatelessWidget { - const DesktopAddressBookScaffold({ - Key? key, - required this.controlsLeft, - required this.controlsRight, - required this.filterItems, - required this.upperLabel, - required this.lowerLabel, - required this.favorites, - required this.all, - required this.details, - }) : super(key: key); - - final Widget? controlsLeft; - final Widget? controlsRight; - final Widget? filterItems; - final Widget? upperLabel; - final Widget? lowerLabel; - final Widget? favorites; - final Widget? all; - final Widget? details; - - static const double weirdRowHeight = 30; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - flex: 6, - child: controlsLeft ?? Container(), - ), - const SizedBox( - width: 20, - ), - Expanded( - flex: 5, - child: controlsRight ?? Container(), - ), - ], - ), - const SizedBox( - height: 20, - ), - Row( - children: [ - Expanded( - child: filterItems ?? Container(), - ), - ], - ), - Expanded( - child: Row( - children: [ - Expanded( - flex: 6, - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: weirdRowHeight, - child: upperLabel, - ), - favorites ?? Container(), - lowerLabel ?? Container(), - all ?? Container(), - ], - ), - ), - ), - ); - }, - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - flex: 5, - child: Column( - children: [ - const SizedBox( - height: weirdRowHeight, - ), - Expanded( - child: details ?? Container(), - ), - ], - ), - ), - ], - ), - ) - ], - ); - } -} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart new file mode 100644 index 000000000..f32ea1f7f --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart @@ -0,0 +1,111 @@ +import 'package:flutter/widgets.dart'; + +class DesktopAddressBookScaffold extends StatelessWidget { + const DesktopAddressBookScaffold({ + Key? key, + required this.controlsLeft, + required this.controlsRight, + required this.filterItems, + required this.upperLabel, + required this.lowerLabel, + required this.favorites, + required this.all, + required this.details, + }) : super(key: key); + + final Widget? controlsLeft; + final Widget? controlsRight; + final Widget? filterItems; + final Widget? upperLabel; + final Widget? lowerLabel; + final Widget? favorites; + final Widget? all; + final Widget? details; + + static const double weirdRowHeight = 30; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + flex: 6, + child: controlsLeft ?? Container(), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: controlsRight ?? Container(), + ), + ], + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: filterItems ?? Container(), + ), + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + flex: 6, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + primary: false, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: weirdRowHeight, + child: upperLabel, + ), + favorites ?? Container(), + lowerLabel ?? Container(), + all ?? Container(), + ], + ), + ), + ), + ); + }, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 5, + child: Column( + children: [ + const SizedBox( + height: weirdRowHeight, + ), + Expanded( + child: details ?? Container(), + ), + ], + ), + ), + ], + ), + ) + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart new file mode 100644 index 000000000..5184b2293 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class DesktopContactDetails extends ConsumerStatefulWidget { + const DesktopContactDetails({ + Key? key, + required this.contactId, + }) : super(key: key); + + final String contactId; + + @override + ConsumerState createState() => + _DesktopContactDetailsState(); +} + +class _DesktopContactDetailsState extends ConsumerState { + @override + Widget build(BuildContext context) { + final contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(widget.contactId))); + + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), + ), + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 20, + ), + ) + : contact.emojiChar != null + ? Center( + child: Text(contact.emojiChar!), + ) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 16, + ), + Text( + contact.name, + style: STextStyles.desktopTextSmall(context), + ), + ], + ), + SecondaryButton( + label: "Options", + onPressed: () {}, + ), + ], + ), + const SizedBox( + height: 24, + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Addresses", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + BlueTextButton( + text: "Add new", + onTap: () {}, + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...contact.addresses + .map((e) => AddressCard(entry: e)), + ], + ) + ], + ), + ), + ), + ); + }, + ), + ), + ], + ); + } +} + +class AddressCard extends StatelessWidget { + const AddressCard({ + Key? key, + required this.entry, + }) : super(key: key); + + final ContactAddressEntry entry; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor( + coin: entry.coin, + ), + height: 32, + width: 32, + ), + const SizedBox( + width: 16, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "${entry.label} ${entry.coin.ticker}", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + entry.address, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + BlueTextButton( + text: "Copy", + onTap: () {}, + ), + const SizedBox( + width: 16, + ), + BlueTextButton( + text: "Edit", + onTap: () {}, + ), + ], + ) + ], + ), + ], + ); + } +} diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index 329e35fdf..b79f89662 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -18,10 +18,12 @@ class AddressBookCard extends ConsumerStatefulWidget { Key? key, required this.contactId, this.indicatorDown, + this.desktopSendFrom = true, }) : super(key: key); final String contactId; final ExpandableState? indicatorDown; + final bool desktopSendFrom; @override ConsumerState createState() => _AddressBookCardState(); @@ -30,10 +32,12 @@ class AddressBookCard extends ConsumerStatefulWidget { class _AddressBookCardState extends ConsumerState { late final String contactId; late final bool isDesktop; + late final bool desktopSendFrom; @override void initState() { contactId = widget.contactId; + desktopSendFrom = widget.desktopSendFrom; isDesktop = Util.isDesktop; super.initState(); } @@ -107,6 +111,7 @@ class _AddressBookCardState extends ConsumerState { const SizedBox( width: 16, ), + if (isDesktop && !desktopSendFrom) const Spacer(), if (isDesktop) Text( coinsString, @@ -129,8 +134,8 @@ class _AddressBookCardState extends ConsumerState { ), ], ), - if (isDesktop) const Spacer(), - if (isDesktop) + if (isDesktop && desktopSendFrom) const Spacer(), + if (isDesktop && desktopSendFrom) SvgPicture.asset( widget.indicatorDown == ExpandableState.collapsed ? Assets.svg.chevronDown From 9063749eadcaf1ab414b37db7591d55049efcfe3 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 17 Nov 2022 10:42:11 -0700 Subject: [PATCH 098/225] anonymize button added to firo wallet --- .../wallet_view/desktop_wallet_view.dart | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 81b531632..769f22157 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -15,6 +16,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_vie import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; @@ -27,8 +29,11 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/hover_text_field.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -165,6 +170,75 @@ class _DesktopWalletViewState extends ConsumerState { } } + Future attemptAnonymize() async { + final managerProvider = + ref.read(walletsChangeNotifierProvider).getManagerProvider(walletId); + + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Anonymizing balance", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + final firoWallet = ref.read(managerProvider).wallet as FiroWallet; + + final publicBalance = await firoWallet.availablePublicBalance(); + if (publicBalance <= Decimal.zero) { + shouldPop = true; + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "No funds available to anonymize!", + context: context, + ), + ); + } + return; + } + + try { + await firoWallet.anonymizeAllPublicFunds(); + shouldPop = true; + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Anonymize transaction submitted", + context: context, + ), + ); + } + } catch (e) { + shouldPop = true; + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Anonymize all failed", + message: "Reason: $e", + ), + ); + } + } + } + @override void initState() { controller = TextEditingController(); @@ -333,6 +407,67 @@ class _DesktopWalletViewState extends ConsumerState { : WalletSyncStatus.synced, ), const Spacer(), + if (coin == Coin.firo) const SizedBox(width: 10), + if (coin == Coin.firo) + SecondaryButton( + width: 180, + desktopMed: true, + label: "Anonymize funds", + onPressed: () async { + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => DesktopDialog( + maxWidth: 500, + maxHeight: 210, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, vertical: 20), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "You're about to anonymize all of your public funds.", + style: + STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 180, + desktopMed: true, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 180, + desktopMed: true, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(); + + unawaited(attemptAnonymize()); + }, + ) + ], + ), + ], + ), + ), + ), + ); + }, + ), + if (coin == Coin.firo) const SizedBox(width: 16), SecondaryButton( width: 180, desktopMed: true, From 9e7c1ccf9dc20e08ac74120e7faf866e1db3f103 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 11:46:44 -0600 Subject: [PATCH 099/225] button size enum --- .../subviews/address_book_filter_view.dart | 4 +- .../generate_receiving_uri_qr_code_view.dart | 5 +- .../send_view/confirm_transaction_view.dart | 4 +- .../building_transaction_dialog.dart | 2 +- .../global_settings_view/currency_view.dart | 4 +- .../add_edit_node_view.dart | 8 +- .../manage_nodes_views/node_details_view.dart | 4 +- .../create_backup_view.dart | 8 +- .../dialogs/cancel_stack_restore_dialog.dart | 4 +- .../edit_auto_backup_view.dart | 4 +- .../restore_from_file_view.dart | 4 +- .../stack_restore_progress_view.dart | 8 +- .../sub_widgets/confirm_full_rescan.dart | 4 +- .../all_transactions_view.dart | 2 +- .../transaction_search_filter_view.dart | 4 +- .../desktop_address_book.dart | 4 +- .../wallet_view/desktop_wallet_view.dart | 2 +- .../sub_widgets/desktop_auth_send.dart | 4 +- .../sub_widgets/desktop_receive.dart | 2 +- .../wallet_view/sub_widgets/desktop_send.dart | 10 +-- .../backup_and_restore_settings.dart | 14 +-- .../create_auto_backup.dart | 7 +- .../enable_backup_dialog.dart | 4 +- .../currency_settings/currency_settings.dart | 2 +- .../language_settings/language_settings.dart | 2 +- .../home/settings_menu/security_settings.dart | 4 +- .../syncing_preferences_settings.dart | 2 +- lib/widgets/desktop/custom_text_button.dart | 10 +++ lib/widgets/desktop/primary_button.dart | 86 +++++++++++++++--- lib/widgets/desktop/secondary_button.dart | 89 ++++++++++++++++--- 30 files changed, 225 insertions(+), 86 deletions(-) diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index df779331e..c129251d5 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -159,7 +159,7 @@ class _AddressBookFilterViewState extends ConsumerState { children: [ SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel", onPressed: () { @@ -169,7 +169,7 @@ class _AddressBookFilterViewState extends ConsumerState { // const SizedBox(width: 16), PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Apply", onPressed: () { diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 981def830..05cedb148 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -530,7 +530,7 @@ class _GenerateUriQrCodeViewState extends State { }); } : onGeneratePressed, - desktopMed: true, + buttonHeight: ButtonHeight.l, ), if (isDesktop && didGenerate) Row( @@ -586,7 +586,6 @@ class _GenerateUriQrCodeViewState extends State { if (!isDesktop) SecondaryButton( width: 170, - desktopMed: true, onPressed: () async { await _capturePng(false); }, @@ -606,7 +605,7 @@ class _GenerateUriQrCodeViewState extends State { ), PrimaryButton( width: 170, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { // TODO: add save functionality instead of share // save works on linux at the moment diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 0f1692c08..8f7afb0bb 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -148,7 +148,7 @@ class _ConfirmTransactionViewState const Spacer(), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Ok", onPressed: Navigator.of(context).pop, ), @@ -780,7 +780,7 @@ class _ConfirmTransactionViewState : const EdgeInsets.all(0), child: PrimaryButton( label: "Send", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { final dynamic unlocked; diff --git a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart index 045218e54..1f6c95df6 100644 --- a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart @@ -77,7 +77,7 @@ class _RestoringDialogState extends State height: 40, ), SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { onCancel.call(); diff --git a/lib/pages/settings_views/global_settings_view/currency_view.dart b/lib/pages/settings_views/global_settings_view/currency_view.dart index 4e8fd5f6e..dccf2d61b 100644 --- a/lib/pages/settings_views/global_settings_view/currency_view.dart +++ b/lib/pages/settings_views/global_settings_view/currency_view.dart @@ -189,7 +189,7 @@ class _CurrencyViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -199,7 +199,7 @@ class _CurrencyViewState extends ConsumerState { Expanded( child: PrimaryButton( label: "Save changes", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () { ref.read(prefsChangeNotifierProvider).currency = current; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 890953caf..606c4481f 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -238,7 +238,7 @@ class _AddEditNodeViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -251,7 +251,7 @@ class _AddEditNodeViewState extends ConsumerState { Expanded( child: PrimaryButton( label: "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -561,7 +561,7 @@ class _AddEditNodeViewState extends ConsumerState { child: SecondaryButton( label: "Test connection", enabled: testConnectionEnabled, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: testConnectionEnabled ? () async { await _testConnection(); @@ -578,7 +578,7 @@ class _AddEditNodeViewState extends ConsumerState { child: PrimaryButton( label: "Save", enabled: saveEnabled, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: saveEnabled ? attemptSave : null, ), ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index a80a64147..3d49ae6f7 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -349,7 +349,7 @@ class _NodeDetailsViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Test connection", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { await _testConnection(ref, context); }, @@ -364,7 +364,7 @@ class _NodeDetailsViewState extends ConsumerState { child: !nodeId.startsWith("default") ? PrimaryButton( label: _desktopReadOnly ? "Edit" : "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { final shouldSave = _desktopReadOnly == false; setState(() { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 02556beb9..0609e4b1b 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -562,7 +562,7 @@ class _RestoreFromFileViewState extends State { Consumer(builder: (context, ref, __) { return PrimaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Create backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -735,7 +735,9 @@ class _RestoreFromFileViewState extends State { child: PrimaryButton( label: "Ok", - desktopMed: true, + buttonHeight: + ButtonHeight + .l, onPressed: () { int count = 0; Navigator.of( @@ -778,7 +780,7 @@ class _RestoreFromFileViewState extends State { ), SecondaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart index a9f4e134d..905cdea72 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart @@ -85,7 +85,7 @@ class CancelStackRestoreDialog extends StatelessWidget { children: [ SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Keep restoring", onPressed: () { @@ -95,7 +95,7 @@ class CancelStackRestoreDialog extends StatelessWidget { const SizedBox(width: 20), PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel anyway", onPressed: () { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 76d280980..310be9f2b 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -754,7 +754,7 @@ class _EditAutoBackupViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -764,7 +764,7 @@ class _EditAutoBackupViewState extends ConsumerState { Expanded( child: PrimaryButton( label: "Save", - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: shouldEnableCreate, onPressed: onSavePressed, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index c5ccfa6b3..9be6af4cb 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -389,7 +389,7 @@ class _RestoreFromFileViewState extends ConsumerState { children: [ PrimaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Restore", enabled: !(passwordController.text.isEmpty || fileLocationController.text.isEmpty), @@ -566,7 +566,7 @@ class _RestoreFromFileViewState extends ConsumerState { ), SecondaryButton( width: 183, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index e34def23d..c7f53378d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -108,7 +108,7 @@ class _StackRestoreProgressViewState // children: [ // SecondaryButton( // width: 248, - // desktopMed: true, + // buttonHeight: ButtonHeight.l, // enabled: true, // label: "Keep restoring", // onPressed: () { @@ -118,7 +118,7 @@ class _StackRestoreProgressViewState // const SizedBox(width: 16), // PrimaryButton( // width: 248, - // desktopMed: true, + // buttonHeight: ButtonHeight.l, // enabled: true, // label: "Cancel anyway", // onPressed: () { @@ -681,7 +681,7 @@ class _StackRestoreProgressViewState _success ? PrimaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Done", onPressed: () async { @@ -690,7 +690,7 @@ class _StackRestoreProgressViewState ) : SecondaryButton( width: 248, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Cancel restore process", onPressed: () async { diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart index 950d8d79e..150af6ac5 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart @@ -58,7 +58,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, label: "Cancel", ), @@ -68,7 +68,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () { Navigator.of(context).pop(); onConfirm.call(); diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index d41877a9a..95dcc8126 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -385,7 +385,7 @@ class _TransactionDetailsViewState extends ConsumerState { ), if (isDesktop) SecondaryButton( - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, width: 200, label: "Filter", icon: SvgPicture.asset( diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index abebb71e4..d135ea276 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -869,7 +869,7 @@ class _TransactionSearchViewState Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, onPressed: () async { if (!isDesktop) { if (FocusScope.of(context).hasFocus) { @@ -919,7 +919,7 @@ class _TransactionSearchViewState ), Expanded( child: PrimaryButton( - desktopMed: isDesktop, + buttonHeight: ButtonHeight.l, onPressed: () async { await _onApplyPressed(); }, diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index 5e22a6089..f028a3424 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -240,7 +240,7 @@ class _DesktopAddressBook extends ConsumerState { SecondaryButton( width: 184, label: "Filter", - desktopMed: true, + buttonHeight: ButtonHeight.l, icon: SvgPicture.asset( Assets.svg.filter, color: Theme.of(context) @@ -255,7 +255,7 @@ class _DesktopAddressBook extends ConsumerState { PrimaryButton( width: 184, label: "Add new", - desktopMed: true, + buttonHeight: ButtonHeight.l, icon: SvgPicture.asset( Assets.svg.circlePlus, color: Theme.of(context) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 769f22157..952194244 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -470,7 +470,7 @@ class _DesktopWalletViewState extends ConsumerState { if (coin == Coin.firo) const SizedBox(width: 16), SecondaryButton( width: 180, - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () { _onExchangePressed(context); }, diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index 566a82b35..9f863c8a4 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -142,7 +142,7 @@ class _DesktopAuthSendState extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -153,7 +153,7 @@ class _DesktopAuthSendState extends ConsumerState { child: PrimaryButton( enabled: _confirmEnabled, label: "Confirm", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: () async { // TODO show spinner while verifying passphrase diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 9a59c3ec1..3de4ed1e3 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -199,7 +199,7 @@ class _DesktopReceiveState extends ConsumerState { ), if (coin != Coin.epicCash) SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: generateNewAddress, label: "Generate new address", ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index af2c2517a..336dd7b4e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -145,7 +145,7 @@ class _DesktopSendState extends ConsumerState { right: 32, ), child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Ok", onPressed: () { Navigator.of(context).pop(); @@ -232,7 +232,7 @@ class _DesktopSendState extends ConsumerState { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { Navigator.of(context).pop(false); @@ -244,7 +244,7 @@ class _DesktopSendState extends ConsumerState { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Yes", onPressed: () { Navigator.of(context).pop(true); @@ -399,7 +399,7 @@ class _DesktopSendState extends ConsumerState { ), child: Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Yes", onPressed: () { Navigator.of( @@ -1385,7 +1385,7 @@ class _DesktopSendState extends ConsumerState { height: 36, ), PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Preview send", enabled: ref.watch(previewTxButtonStateProvider.state).state, onPressed: ref.watch(previewTxButtonStateProvider.state).state diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 7d70d4d0f..37f3f35a6 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -240,7 +240,7 @@ class _BackupRestoreSettings extends ConsumerState { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: Navigator.of(context).pop, ), @@ -248,7 +248,7 @@ class _BackupRestoreSettings extends ConsumerState { const SizedBox(width: 16), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Disable", onPressed: () { ref @@ -422,7 +422,7 @@ class _BackupRestoreSettings extends ConsumerState { padding: const EdgeInsets.all(10), child: !isEnabledAutoBackup ? PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, width: 200, label: "Enable auto backup", onPressed: () { @@ -467,7 +467,7 @@ class _BackupRestoreSettings extends ConsumerState { Row( children: [ PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, width: 190, label: "Disable auto backup", onPressed: () { @@ -476,7 +476,7 @@ class _BackupRestoreSettings extends ConsumerState { ), const SizedBox(width: 16), SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, width: 190, label: "Edit auto backup", onPressed: () { @@ -560,7 +560,7 @@ class _BackupRestoreSettings extends ConsumerState { child: CreateBackupView(), ) : PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, width: 200, label: "Create manual backup", onPressed: () { @@ -642,7 +642,7 @@ class _BackupRestoreSettings extends ConsumerState { child: RestoreFromFileView(), ) : PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, width: 200, label: "Restore backup", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart index 663136dba..df80da732 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart @@ -556,7 +556,7 @@ class _CreateAutoBackup extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - desktopMed: true, + buttonHeight: ButtonHeight.l, onPressed: Navigator.of(context).pop, ), ), @@ -565,7 +565,7 @@ class _CreateAutoBackup extends ConsumerState { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Enable Auto Backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -792,7 +792,8 @@ class _CreateAutoBackup extends ConsumerState { Expanded( child: PrimaryButton( label: "Ok", - desktopMed: true, + buttonHeight: + ButtonHeight.l, onPressed: () { Navigator.of(context) .pop(); diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart index 6496253d5..df9f18b52 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart @@ -59,7 +59,7 @@ class EnableBackupDialog extends StatelessWidget { children: [ Expanded( child: SecondaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { Navigator.of(context).pop(); @@ -71,7 +71,7 @@ class EnableBackupDialog extends StatelessWidget { ), Expanded( child: PrimaryButton( - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { Navigator.of(context).pop(); diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index 4c4225ce4..0740157ad 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -108,7 +108,7 @@ class _CurrencySettings extends ConsumerState { ), child: PrimaryButton( width: 210, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Change currency", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index 08aeb9bc3..db636ba17 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -85,7 +85,7 @@ class _LanguageOptionSettings extends ConsumerState { ), child: PrimaryButton( width: 210, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Change language", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index 9f870440b..f2853e6f5 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -485,7 +485,7 @@ class _SecuritySettings extends ConsumerState { const SizedBox(height: 20), PrimaryButton( width: 160, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: shouldEnableSave, label: "Save changes", onPressed: () async { @@ -503,7 +503,7 @@ class _SecuritySettings extends ConsumerState { ) : PrimaryButton( width: 210, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Set up new password", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 408b93e15..ae11f5582 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -83,7 +83,7 @@ class _SyncingPreferencesSettings ), child: PrimaryButton( width: 210, - desktopMed: true, + buttonHeight: ButtonHeight.l, enabled: true, label: "Change preferences", onPressed: () {}, diff --git a/lib/widgets/desktop/custom_text_button.dart b/lib/widgets/desktop/custom_text_button.dart index b96a697b8..90b75c459 100644 --- a/lib/widgets/desktop/custom_text_button.dart +++ b/lib/widgets/desktop/custom_text_button.dart @@ -1,6 +1,16 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/util.dart'; +enum ButtonHeight { + xxs, + xs, + s, + m, + l, + xl, + xxl, +} + class CustomTextButtonBase extends StatelessWidget { const CustomTextButtonBase({ Key? key, diff --git a/lib/widgets/desktop/primary_button.dart b/lib/widgets/desktop/primary_button.dart index f3c900c34..134ff36c3 100644 --- a/lib/widgets/desktop/primary_button.dart +++ b/lib/widgets/desktop/primary_button.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; +export 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + class PrimaryButton extends StatelessWidget { const PrimaryButton({ Key? key, @@ -13,7 +15,7 @@ class PrimaryButton extends StatelessWidget { this.icon, this.onPressed, this.enabled = true, - this.desktopMed = false, + this.buttonHeight, }) : super(key: key); final double? width; @@ -22,23 +24,44 @@ class PrimaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool enabled; final Widget? icon; - final bool desktopMed; + final ButtonHeight? buttonHeight; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { - if (desktopMed) { - return STextStyles.desktopTextExtraSmall(context).copyWith( - color: enabled - ? Theme.of(context).extension()!.buttonTextPrimary - : Theme.of(context) - .extension()! - .buttonTextPrimaryDisabled, - ); - } else { + if (buttonHeight == null) { return enabled ? STextStyles.desktopButtonEnabled(context) : STextStyles.desktopButtonDisabled(context); } + + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + return STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context).extension()!.buttonTextPrimary + : Theme.of(context) + .extension()! + .buttonTextPrimaryDisabled, + ); + + case ButtonHeight.m: + case ButtonHeight.l: + return STextStyles.desktopTextExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context).extension()!.buttonTextPrimary + : Theme.of(context) + .extension()! + .buttonTextPrimaryDisabled, + ); + + case ButtonHeight.xl: + case ButtonHeight.xxl: + return enabled + ? STextStyles.desktopButtonEnabled(context) + : STextStyles.desktopButtonDisabled(context); + } } else { return STextStyles.button(context).copyWith( color: enabled @@ -50,12 +73,51 @@ class PrimaryButton extends StatelessWidget { } } + double? _getHeight() { + if (buttonHeight == null) { + return height; + } + + if (Util.isDesktop) { + switch (buttonHeight!) { + case ButtonHeight.xxs: + return 28; + case ButtonHeight.xs: + return 32; + case ButtonHeight.s: + return 40; + case ButtonHeight.m: + return 48; + case ButtonHeight.l: + return 56; + case ButtonHeight.xl: + return 70; + case ButtonHeight.xxl: + return 96; + } + } else { + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + case ButtonHeight.m: + return 28; + case ButtonHeight.l: + return 30; + case ButtonHeight.xl: + return 46; + case ButtonHeight.xxl: + return 56; + } + } + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; return CustomTextButtonBase( - height: desktopMed ? 56 : height, + height: _getHeight(), width: width, textButton: TextButton( onPressed: enabled ? onPressed : null, diff --git a/lib/widgets/desktop/secondary_button.dart b/lib/widgets/desktop/secondary_button.dart index 8d5eae0ce..7cf8e9f72 100644 --- a/lib/widgets/desktop/secondary_button.dart +++ b/lib/widgets/desktop/secondary_button.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; +export 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + class SecondaryButton extends StatelessWidget { const SecondaryButton({ Key? key, @@ -13,7 +15,7 @@ class SecondaryButton extends StatelessWidget { this.icon, this.onPressed, this.enabled = true, - this.desktopMed = false, + this.buttonHeight, }) : super(key: key); final double? width; @@ -22,23 +24,47 @@ class SecondaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool enabled; final Widget? icon; - final bool desktopMed; + final ButtonHeight? buttonHeight; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { - if (desktopMed) { - return STextStyles.desktopTextExtraSmall(context).copyWith( - color: enabled - ? Theme.of(context).extension()!.buttonTextSecondary - : Theme.of(context) - .extension()! - .buttonTextSecondaryDisabled, - ); - } else { + if (buttonHeight == null) { return enabled ? STextStyles.desktopButtonSecondaryEnabled(context) : STextStyles.desktopButtonSecondaryDisabled(context); } + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + return STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension()! + .buttonTextSecondary + : Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + ); + + case ButtonHeight.m: + case ButtonHeight.l: + return STextStyles.desktopTextExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension()! + .buttonTextSecondary + : Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + ); + + case ButtonHeight.xl: + case ButtonHeight.xxl: + return enabled + ? STextStyles.desktopButtonSecondaryEnabled(context) + : STextStyles.desktopButtonSecondaryDisabled(context); + } } else { return STextStyles.button(context).copyWith( color: enabled @@ -50,12 +76,51 @@ class SecondaryButton extends StatelessWidget { } } + double? _getHeight() { + if (buttonHeight == null) { + return height; + } + + if (Util.isDesktop) { + switch (buttonHeight!) { + case ButtonHeight.xxs: + return 28; + case ButtonHeight.xs: + return 32; + case ButtonHeight.s: + return 40; + case ButtonHeight.m: + return 48; + case ButtonHeight.l: + return 56; + case ButtonHeight.xl: + return 70; + case ButtonHeight.xxl: + return 96; + } + } else { + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + case ButtonHeight.m: + return 28; + case ButtonHeight.l: + return 30; + case ButtonHeight.xl: + return 46; + case ButtonHeight.xxl: + return 56; + } + } + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; return CustomTextButtonBase( - height: desktopMed ? 56 : height, + height: _getHeight(), width: width, textButton: TextButton( onPressed: enabled ? onPressed : null, From 1d238c29f09207b975968c27707df5097de218ac Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 12:01:52 -0600 Subject: [PATCH 100/225] WIP: centralize button heights --- .../create_backup_view.dart | 4 +- .../restore_from_file_view.dart | 4 +- .../wallet_view/desktop_wallet_view.dart | 6 +- .../advanced_settings/advanced_settings.dart | 111 +++++------------- .../backup_and_restore_settings.dart | 10 +- .../currency_settings/currency_settings.dart | 2 +- .../language_settings/language_settings.dart | 4 +- .../home/settings_menu/security_settings.dart | 2 +- .../syncing_preferences_settings.dart | 2 +- lib/widgets/desktop/primary_button.dart | 4 +- lib/widgets/desktop/secondary_button.dart | 4 +- 11 files changed, 52 insertions(+), 101 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 0609e4b1b..a6241d25a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -562,7 +562,7 @@ class _RestoreFromFileViewState extends State { Consumer(builder: (context, ref, __) { return PrimaryButton( width: 183, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, label: "Create backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -780,7 +780,7 @@ class _RestoreFromFileViewState extends State { ), SecondaryButton( width: 183, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 9be6af4cb..d6571967d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -389,7 +389,7 @@ class _RestoreFromFileViewState extends ConsumerState { children: [ PrimaryButton( width: 183, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, label: "Restore", enabled: !(passwordController.text.isEmpty || fileLocationController.text.isEmpty), @@ -566,7 +566,7 @@ class _RestoreFromFileViewState extends ConsumerState { ), SecondaryButton( width: 183, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, label: "Cancel", onPressed: () {}, ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 952194244..d21a19aee 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -411,7 +411,7 @@ class _DesktopWalletViewState extends ConsumerState { if (coin == Coin.firo) SecondaryButton( width: 180, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Anonymize funds", onPressed: () async { await showDialog( @@ -441,7 +441,7 @@ class _DesktopWalletViewState extends ConsumerState { children: [ SecondaryButton( width: 180, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { Navigator.of(context).pop(); @@ -450,7 +450,7 @@ class _DesktopWalletViewState extends ConsumerState { const SizedBox(width: 20), PrimaryButton( width: 180, - desktopMed: true, + buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { Navigator.of(context).pop(); diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart index b4ff3fe6a..621683e65 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'debug_info_dialog.dart'; @@ -143,7 +144,21 @@ class _AdvancedSettings extends ConsumerState { ), ], ), - const StackPrivacyButton(), + PrimaryButton( + label: "Change", + buttonHeight: ButtonHeight.xs, + width: 86, + onPressed: () async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const StackPrivacyDialog(); + }, + ); + }, + ) ], ), ); @@ -172,7 +187,21 @@ class _AdvancedSettings extends ConsumerState { .textDark), textAlign: TextAlign.left, ), - ShowLogsButton(), + PrimaryButton( + buttonHeight: ButtonHeight.xs, + label: "Show logs", + width: 101, + onPressed: () async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const DebugInfoDialog(); + }, + ); + }, + ), ], ), ), @@ -184,81 +213,3 @@ class _AdvancedSettings extends ConsumerState { ); } } - -class StackPrivacyButton extends ConsumerWidget { - const StackPrivacyButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future changePrivacySettings() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackPrivacyDialog(); - }, - ); - } - - return SizedBox( - width: 84, - height: 37, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - // Navigator.of(context).pushNamed( - // StackPrivacyCalls.routeName, - // arguments: false, - // ); - changePrivacySettings(); - }, - child: Text( - "Change", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith(color: Colors.white), - ), - ), - ); - } -} - -class ShowLogsButton extends ConsumerWidget { - const ShowLogsButton({ - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context, WidgetRef ref) { - Future viewDebugLogs() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const DebugInfoDialog(); - }, - ); - } - - return SizedBox( - width: 101, - height: 37, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - viewDebugLogs(); - }, - child: Text( - "Show logs", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith(color: Colors.white), - ), - ), - ); - } -} diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart index 37f3f35a6..c82b5f923 100644 --- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -422,7 +422,7 @@ class _BackupRestoreSettings extends ConsumerState { padding: const EdgeInsets.all(10), child: !isEnabledAutoBackup ? PrimaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, width: 200, label: "Enable auto backup", onPressed: () { @@ -467,7 +467,7 @@ class _BackupRestoreSettings extends ConsumerState { Row( children: [ PrimaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, width: 190, label: "Disable auto backup", onPressed: () { @@ -476,7 +476,7 @@ class _BackupRestoreSettings extends ConsumerState { ), const SizedBox(width: 16), SecondaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, width: 190, label: "Edit auto backup", onPressed: () { @@ -560,7 +560,7 @@ class _BackupRestoreSettings extends ConsumerState { child: CreateBackupView(), ) : PrimaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, width: 200, label: "Create manual backup", onPressed: () { @@ -642,7 +642,7 @@ class _BackupRestoreSettings extends ConsumerState { child: RestoreFromFileView(), ) : PrimaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, width: 200, label: "Restore backup", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index 0740157ad..d9c20d8fa 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -108,7 +108,7 @@ class _CurrencySettings extends ConsumerState { ), child: PrimaryButton( width: 210, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, enabled: true, label: "Change currency", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index db636ba17..acddcb055 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -80,12 +80,12 @@ class _LanguageOptionSettings extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.all( + padding: const EdgeInsets.all( 10, ), child: PrimaryButton( width: 210, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, enabled: true, label: "Change language", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index f2853e6f5..f6762afa1 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -503,7 +503,7 @@ class _SecuritySettings extends ConsumerState { ) : PrimaryButton( width: 210, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, enabled: true, label: "Set up new password", onPressed: () { diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index ae11f5582..815e506db 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -83,7 +83,7 @@ class _SyncingPreferencesSettings ), child: PrimaryButton( width: 210, - buttonHeight: ButtonHeight.l, + buttonHeight: ButtonHeight.m, enabled: true, label: "Change preferences", onPressed: () {}, diff --git a/lib/widgets/desktop/primary_button.dart b/lib/widgets/desktop/primary_button.dart index 134ff36c3..9441168e7 100644 --- a/lib/widgets/desktop/primary_button.dart +++ b/lib/widgets/desktop/primary_button.dart @@ -81,9 +81,9 @@ class PrimaryButton extends StatelessWidget { if (Util.isDesktop) { switch (buttonHeight!) { case ButtonHeight.xxs: - return 28; - case ButtonHeight.xs: return 32; + case ButtonHeight.xs: + return 37; case ButtonHeight.s: return 40; case ButtonHeight.m: diff --git a/lib/widgets/desktop/secondary_button.dart b/lib/widgets/desktop/secondary_button.dart index 7cf8e9f72..62bd900dd 100644 --- a/lib/widgets/desktop/secondary_button.dart +++ b/lib/widgets/desktop/secondary_button.dart @@ -84,9 +84,9 @@ class SecondaryButton extends StatelessWidget { if (Util.isDesktop) { switch (buttonHeight!) { case ButtonHeight.xxs: - return 28; - case ButtonHeight.xs: return 32; + case ButtonHeight.xs: + return 37; case ButtonHeight.s: return 40; case ButtonHeight.m: From 95a9fade38c065fb0d578b8ea92f4c83ca6b1a1a Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 12:17:55 -0600 Subject: [PATCH 101/225] desktop contact address details --- .../desktop_address_book.dart | 7 +- .../subwidgets/desktop_address_card.dart | 75 ++++++++++++ .../subwidgets/desktop_contact_details.dart | 108 ++++++------------ 3 files changed, 116 insertions(+), 74 deletions(-) create mode 100644 lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index f028a3424..fd25617e7 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -427,8 +427,11 @@ class _DesktopAddressBook extends ConsumerState { ), details: currentContactId == null ? Container() - : DesktopContactDetails( - contactId: currentContactId!, + : RoundedWhiteContainer( + padding: const EdgeInsets.all(24), + child: DesktopContactDetails( + contactId: currentContactId!, + ), ), ), ), diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart new file mode 100644 index 000000000..49b75a4a8 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; + +class DesktopAddressCard extends StatelessWidget { + const DesktopAddressCard({ + Key? key, + required this.entry, + }) : super(key: key); + + final ContactAddressEntry entry; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.iconFor( + coin: entry.coin, + ), + height: 32, + width: 32, + ), + const SizedBox( + width: 16, + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "${entry.label} (${entry.coin.ticker})", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + entry.address, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + BlueTextButton( + text: "Copy", + onTap: () {}, + ), + const SizedBox( + width: 16, + ), + BlueTextButton( + text: "Edit", + onTap: () {}, + ), + ], + ) + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index 5184b2293..bc19fe3bd 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; class DesktopContactDetails extends ConsumerStatefulWidget { const DesktopContactDetails({ @@ -74,6 +74,8 @@ class _DesktopContactDetailsState extends ConsumerState { ), SecondaryButton( label: "Options", + width: 86, + buttonHeight: ButtonHeight.xxs, onPressed: () {}, ), ], @@ -106,12 +108,38 @@ class _DesktopContactDetailsState extends ConsumerState { ), ], ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...contact.addresses - .map((e) => AddressCard(entry: e)), - ], + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < contact.addresses.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(18), + child: DesktopAddressCard( + entry: contact.addresses[i], + ), + ), + ], + ), + ], + ), ) ], ), @@ -125,67 +153,3 @@ class _DesktopContactDetailsState extends ConsumerState { ); } } - -class AddressCard extends StatelessWidget { - const AddressCard({ - Key? key, - required this.entry, - }) : super(key: key); - - final ContactAddressEntry entry; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor( - coin: entry.coin, - ), - height: 32, - width: 32, - ), - const SizedBox( - width: 16, - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText( - "${entry.label} ${entry.coin.ticker}", - style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ), - ), - const SizedBox( - height: 2, - ), - SelectableText( - entry.address, - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - const SizedBox( - height: 8, - ), - Row( - children: [ - BlueTextButton( - text: "Copy", - onTap: () {}, - ), - const SizedBox( - width: 16, - ), - BlueTextButton( - text: "Edit", - onTap: () {}, - ), - ], - ) - ], - ), - ], - ); - } -} From 682966dab83a391ef3224c67833cc0d333a0cba9 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 17 Nov 2022 14:08:06 -0700 Subject: [PATCH 102/225] desktop block explorer dialog --- .../transaction_details_view.dart | 200 ++++++++++++------ .../wallet_view/desktop_wallet_view.dart | 4 +- 2 files changed, 142 insertions(+), 62 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1c2fb8e5d..dc4e41152 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -30,6 +30,8 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/pencil_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -154,60 +156,136 @@ class _TransactionDetailsViewState Future showExplorerWarning(String explorer) async { final bool? shouldContinue = await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => StackDialog( - title: "Attention", - message: - "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", - icon: Row( - children: [ - Consumer(builder: (_, ref, __) { - return Checkbox( - value: ref.watch(prefsChangeNotifierProvider - .select((value) => value.hideBlockExplorerWarning)), - onChanged: (value) { - if (value is bool) { - ref - .read(prefsChangeNotifierProvider) - .hideBlockExplorerWarning = value; - setState(() {}); - } + context: context, + barrierDismissible: false, + builder: (_) { + if (!isDesktop) { + return StackDialog( + title: "Attention", + message: + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + icon: Row( + children: [ + Consumer(builder: (_, ref, __) { + return Checkbox( + value: ref.watch(prefsChangeNotifierProvider + .select((value) => value.hideBlockExplorerWarning)), + onChanged: (value) { + if (value is bool) { + ref + .read(prefsChangeNotifierProvider) + .hideBlockExplorerWarning = value; + setState(() {}); + } + }, + ); + }), + Text( + "Never show again", + style: STextStyles.smallMed14(context), + ) + ], + ), + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(false); }, - ); - }), - Text( - "Never show again", - style: STextStyles.smallMed14(context), - ) - ], - ), - leftButton: TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + rightButton: TextButton( + style: Theme.of(context) .extension()! - .accentColorDark), - ), - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text( - "Continue", - style: STextStyles.button(context), - ), - ), - ), - ); + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + "Continue", + style: STextStyles.button(context), + ), + ), + ); + } else { + return DesktopDialog( + maxWidth: 550, + maxHeight: 300, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Attention", + style: STextStyles.desktopH2(context), + ), + Row( + children: [ + Consumer(builder: (_, ref, __) { + return Checkbox( + value: ref.watch(prefsChangeNotifierProvider + .select((value) => + value.hideBlockExplorerWarning)), + onChanged: (value) { + if (value is bool) { + ref + .read(prefsChangeNotifierProvider) + .hideBlockExplorerWarning = value; + setState(() {}); + } + }, + ); + }), + Text( + "Never show again", + style: STextStyles.smallMed14(context), + ) + ], + ), + ], + ), + const SizedBox(height: 16), + Text( + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ], + ), + ], + ), + ), + ); + } + }); return shouldContinue ?? false; } @@ -995,15 +1073,17 @@ class _TransactionDetailsViewState .externalApplication, ); } catch (_) { - unawaited(showDialog( - context: context, - builder: (_) => StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", + unawaited( + showDialog( + context: context, + builder: (_) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), ), - )); + ); } finally { // Future.delayed( // const Duration(seconds: 1), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index d21a19aee..a7de8fdf4 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -440,7 +440,7 @@ class _DesktopWalletViewState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.center, children: [ SecondaryButton( - width: 180, + width: 200, buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { @@ -449,7 +449,7 @@ class _DesktopWalletViewState extends ConsumerState { ), const SizedBox(width: 20), PrimaryButton( - width: 180, + width: 200, buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { From 11735cdaf7d8e5393687c2ec89893a72908d7d8a Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 12:56:10 -0600 Subject: [PATCH 103/225] desktop emoji select --- .../subviews/add_address_book_entry_view.dart | 132 ++++-------- lib/widgets/emoji_select_sheet.dart | 188 ++++++++++-------- 2 files changed, 151 insertions(+), 169 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 5835c80cd..0007f3d81 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -191,33 +191,33 @@ class _AddAddressBookEntryViewState style: STextStyles.desktopH3(context), textAlign: TextAlign.center, ), - const SizedBox(width: 10), - AppBarIconButton( - key: - const Key("addAddressBookEntryFavoriteButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context) - .extension()! - .background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _isFavorite - ? Theme.of(context) - .extension()! - .favoriteStarActive - : Theme.of(context) - .extension()! - .favoriteStarInactive, - width: 20, - height: 20, - ), - onPressed: () { - setState(() { - _isFavorite = !_isFavorite; - }); - }, - ), + // const SizedBox(width: 10), + // AppBarIconButton( + // key: + // const Key("addAddressBookEntryFavoriteButtonKey"), + // size: 36, + // shadows: const [], + // color: Theme.of(context) + // .extension()! + // .background, + // icon: SvgPicture.asset( + // Assets.svg.star, + // color: _isFavorite + // ? Theme.of(context) + // .extension()! + // .favoriteStarActive + // : Theme.of(context) + // .extension()! + // .favoriteStarInactive, + // width: 20, + // height: 20, + // ), + // onPressed: () { + // setState(() { + // _isFavorite = !_isFavorite; + // }); + // }, + // ), ], ), ), @@ -225,10 +225,11 @@ class _AddAddressBookEntryViewState ], ), Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: child, - )), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: child, + ), + ), ], ); }, @@ -292,66 +293,17 @@ class _AddAddressBookEntryViewState : showDialog( context: context, builder: (context) { - return DesktopDialog( + return const DesktopDialog( maxHeight: 700, - maxWidth: 700, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: - const EdgeInsets - .all(32), - child: Text( - "Select emoji", - style: STextStyles - .desktopH3( - context), - textAlign: - TextAlign - .center, - ), - ), - ], - ), - Expanded( - child: LayoutBuilder( - builder: (context, - constraints) { - return SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: - ConstrainedBox( - constraints: - BoxConstraints( - minHeight: - constraints - .maxHeight, - minWidth: - constraints - .maxWidth, - ), - child: - IntrinsicHeight( - child: Column( - children: const [ - Padding( - padding: - EdgeInsets.symmetric(horizontal: 32), - // child: - // EmojiSelectSheet(), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ], + maxWidth: 600, + child: Padding( + padding: EdgeInsets.only( + left: 32, + right: 20, + top: 32, + bottom: 32, + ), + child: EmojiSelectSheet(), ), ); }).then((value) { diff --git a/lib/widgets/emoji_select_sheet.dart b/lib/widgets/emoji_select_sheet.dart index 85a90fec8..7bf02e967 100644 --- a/lib/widgets/emoji_select_sheet.dart +++ b/lib/widgets/emoji_select_sheet.dart @@ -4,6 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; class EmojiSelectSheet extends ConsumerWidget { const EmojiSelectSheet({ @@ -16,7 +19,9 @@ class EmojiSelectSheet extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final size = MediaQuery.of(context).size; + final isDesktop = Util.isDesktop; + + final size = isDesktop ? const Size(600, 700) : MediaQuery.of(context).size; final double maxHeight = size.height * 0.60; final double availableWidth = size.width - (2 * horizontalPadding); final int emojisPerRow = @@ -24,90 +29,115 @@ class EmojiSelectSheet extends ConsumerWidget { final itemCount = Emoji.all().length; - return Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + child: LimitedBox( + maxHeight: maxHeight, + child: Padding( + padding: EdgeInsets.only( + left: horizontalPadding, + right: horizontalPadding, + top: 10, + bottom: 0, + ), + child: child, + ), ), ), - child: LimitedBox( - maxHeight: maxHeight, - child: Padding( - padding: EdgeInsets.only( - left: horizontalPadding, - right: horizontalPadding, - top: 10, - bottom: 0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + Center( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - width: 60, - height: 4, ), + width: 60, + height: 4, ), - const SizedBox( - height: 36, - ), - Text( - "Select emoji", - style: STextStyles.pageTitleH2(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 16, - ), - Flexible( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: GridView.builder( - itemCount: itemCount, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: emojisPerRow, - ), - itemBuilder: (context, index) { - final emoji = Emoji.all()[index]; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(emoji); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Colors.transparent, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(emoji.char), - ), - ), - ); - }, - ), - ) - ], - ), - ), - const SizedBox( - height: 24, - ), - ], + ), + if (!isDesktop) + const SizedBox( + height: 36, + ), + Text( + "Select emoji", + style: isDesktop + ? STextStyles.desktopH3(context) + : STextStyles.pageTitleH2(context), + textAlign: TextAlign.left, ), - ), + SizedBox( + height: isDesktop ? 28 : 16, + ), + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: GridView.builder( + itemCount: itemCount, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: emojisPerRow, + ), + itemBuilder: (context, index) { + final emoji = Emoji.all()[index]; + return GestureDetector( + onTap: () { + Navigator.of(context).pop(emoji); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Colors.transparent, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + emoji.char, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : null, + ), + ), + ), + ); + }, + ), + ) + ], + ), + ), + SizedBox( + height: isDesktop ? 20 : 24, + ), + if (isDesktop) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SecondaryButton( + label: "Cancel", + width: 248, + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ], + ), + ], ), ); } From 134087bfc4c18c3482e49d6b1e7a16b9f0a78032 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 13:15:57 -0600 Subject: [PATCH 104/225] desktop add contact popup tweaks --- .../subviews/add_address_book_entry_view.dart | 375 ++++++------------ .../new_contact_address_entry_form.dart | 2 +- 2 files changed, 121 insertions(+), 256 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 0007f3d81..bb93c68d8 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -21,6 +21,8 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -191,33 +193,6 @@ class _AddAddressBookEntryViewState style: STextStyles.desktopH3(context), textAlign: TextAlign.center, ), - // const SizedBox(width: 10), - // AppBarIconButton( - // key: - // const Key("addAddressBookEntryFavoriteButtonKey"), - // size: 36, - // shadows: const [], - // color: Theme.of(context) - // .extension()! - // .background, - // icon: SvgPicture.asset( - // Assets.svg.star, - // color: _isFavorite - // ? Theme.of(context) - // .extension()! - // .favoriteStarActive - // : Theme.of(context) - // .extension()! - // .favoriteStarInactive, - // width: 20, - // height: 20, - // ), - // onPressed: () { - // setState(() { - // _isFavorite = !_isFavorite; - // }); - // }, - // ), ], ), ), @@ -226,7 +201,11 @@ class _AddAddressBookEntryViewState ), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.only( + left: 10, + right: 10, + bottom: 32, + ), child: child, ), ), @@ -239,16 +218,17 @@ class _AddAddressBookEntryViewState padding: const EdgeInsets.symmetric(horizontal: 12), child: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only( + padding: EdgeInsets.only( // top: 8, left: 4, right: 4, - bottom: 16, + bottom: isDesktop ? 0 : 16, ), child: ConstrainedBox( constraints: BoxConstraints( // subtract top and bottom padding set in parent - minHeight: constraint.maxHeight - 16, // - 8, + minHeight: + constraint.maxHeight - (isDesktop ? 0 : 16), // - 8, ), child: IntrinsicHeight( child: Column( @@ -259,38 +239,21 @@ class _AddAddressBookEntryViewState mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () { - if (_selectedEmoji != null) { - setState(() { - _selectedEmoji = null; - }); - return; - } + SizedBox( + height: 56, + width: 56, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + if (_selectedEmoji != null) { + setState(() { + _selectedEmoji = null; + }); + return; + } - ///TODO if desktop make dialog - !isDesktop - ? showModalBottomSheet( - backgroundColor: - Colors.transparent, - context: context, - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => - const EmojiSelectSheet(), - ).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }) - : showDialog( + showDialog( context: context, builder: (context) { return const DesktopDialog( @@ -307,77 +270,80 @@ class _AddAddressBookEntryViewState ), ); }).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); - }, - child: SizedBox( - height: 56, - width: 56, - child: Stack( - children: [ - Container( - height: 56, - width: 56, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.user, - height: 30, - width: 30, - ) - : Text( - _selectedEmoji!.char, - style: STextStyles - .pageTitleH1(context), - ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Container( - height: 14, - width: 14, + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + }, + child: Stack( + children: [ + Container( + height: 56, + width: 56, decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(14), - color: Theme.of(context) - .extension()! - .accentColorDark), + borderRadius: + BorderRadius.circular(100), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), child: Center( child: _selectedEmoji == null ? SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension< - StackColors>()! - .textWhite, - width: 12, - height: 12, + Assets.svg.user, + height: 30, + width: 30, ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension< - StackColors>()! - .textWhite, - width: 8, - height: 8, + : Text( + _selectedEmoji!.char, + style: STextStyles + .pageTitleH1( + context), ), ), ), - ) - ], + Align( + alignment: Alignment.bottomRight, + child: Container( + height: 14, + width: 14, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 14), + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorDark), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of( + context) + .extension< + StackColors>()! + .textWhite, + width: 12, + height: 12, + ) + : SvgPicture.asset( + Assets.svg.thickX, + color: Theme.of( + context) + .extension< + StackColors>()! + .textWhite, + width: 8, + height: 8, + ), + ), + ), + ) + ], + ), ), ), ), @@ -453,100 +419,23 @@ class _AddAddressBookEntryViewState return; } - ///TODO if desktop make dialog - !isDesktop - ? showModalBottomSheet( - backgroundColor: - Colors.transparent, - context: context, - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => - const EmojiSelectSheet(), - ).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }) - : showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 700, - maxWidth: 700, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: - const EdgeInsets - .all(32), - child: Text( - "Select emoji", - style: STextStyles - .desktopH3( - context), - textAlign: - TextAlign - .center, - ), - ), - ], - ), - Expanded( - child: LayoutBuilder( - builder: (context, - constraints) { - return SingleChildScrollView( - scrollDirection: - Axis.vertical, - child: - ConstrainedBox( - constraints: - BoxConstraints( - minHeight: - constraints - .maxHeight, - minWidth: - constraints - .maxWidth, - ), - child: - IntrinsicHeight( - child: Column( - children: const [ - Padding( - padding: - EdgeInsets.symmetric(horizontal: 32), - // child: - // EmojiSelectSheet(), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ], - ), - ); - }).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + const EmojiSelectSheet(), + ).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); }, child: SizedBox( height: 48, @@ -734,22 +623,16 @@ class _AddAddressBookEntryViewState Row( children: [ Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop ? ButtonHeight.m : null, onPressed: () async { - if (FocusScope.of(context).hasFocus) { + if (!isDesktop && + FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future.delayed( - const Duration(milliseconds: 75)); + const Duration(milliseconds: 75), + ); } if (mounted) { Navigator.of(context).pop(); @@ -776,16 +659,11 @@ class _AddAddressBookEntryViewState bool shouldEnableSave = validForms && nameExists; - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor( - context), + return PrimaryButton( + label: "Save", + buttonHeight: + isDesktop ? ButtonHeight.m : null, + enabled: shouldEnableSave, onPressed: shouldEnableSave ? () async { if (FocusScope.of(context) @@ -827,19 +705,6 @@ class _AddAddressBookEntryViewState } } : null, - child: Text( - "Save", - style: - STextStyles.button(context).copyWith( - color: shouldEnableSave - ? Theme.of(context) - .extension()! - .buttonTextPrimary - : Theme.of(context) - .extension()! - .buttonTextPrimaryDisabled, - ), - ), ); }, ), diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index b6cf0aad4..f49547858 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -253,7 +253,7 @@ class _NewContactAddressEntryFormState }, child: const ClipboardIcon(), ), - if (ref.watch(addressEntryDataProvider(widget.id) + if (!Util.isDesktop && ref.watch(addressEntryDataProvider(widget.id) .select((value) => value.address)) == null) TextFieldIconButton( From 0503999fa708aa0b7ab38d39c044d87435e045de Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 15:24:28 -0600 Subject: [PATCH 105/225] WIP coin dropdown --- .../new_contact_address_entry_form.dart | 245 ++++++++++++------ 1 file changed, 172 insertions(+), 73 deletions(-) diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index f49547858..25cff073b 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -1,8 +1,10 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/address_book_views/subviews/coin_select_sheet.dart'; +import 'package:stackwallet/providers/providers.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/utilities/address_utils.dart'; @@ -47,6 +49,8 @@ class _NewContactAddressEntryFormState late final FocusNode addressLabelFocusNode; late final FocusNode addressFocusNode; + List coins = []; + @override void initState() { addressLabelController = TextEditingController() @@ -55,6 +59,7 @@ class _NewContactAddressEntryFormState ..text = ref.read(addressEntryDataProvider(widget.id)).address ?? ""; addressLabelFocusNode = FocusNode(); addressFocusNode = FocusNode(); + coins = [...Coin.values]; super.initState(); } @@ -70,86 +75,179 @@ class _NewContactAddressEntryFormState @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; + bool showTestNet = ref.watch( + prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), + ); + if (isDesktop) { + coins = [...Coin.values]; + + coins.remove(Coin.firoTestNet); + if (showTestNet) { + coins = coins.sublist(0, coins.length - kTestNetCoinCount); + } + } + return Column( children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - readOnly: true, - style: STextStyles.field(context), - decoration: InputDecoration( - hintText: "Select cryptocurrency", - hintStyle: STextStyles.fieldLabel(context), - prefixIcon: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: RawMaterialButton( - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + if (isDesktop) + DropdownButtonHideUnderline( + child: DropdownButton2( + hint: Text( + "Select cryptocurrency", + style: STextStyles.fieldLabel(context), + ), + offset: const Offset(0, -10), + isExpanded: true, + dropdownElevation: 0, + value: ref.watch(addressEntryDataProvider(widget.id) + .select((value) => value.coin)), + onChanged: (value) { + if (value is Coin) { + ref.read(addressEntryDataProvider(widget.id)).coin = value; + } + }, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + items: [ + ...coins.map( + (coin) => DropdownMenuItem( + value: coin, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + height: 24, + width: 24, + ), + const SizedBox( + width: 12, + ), + Text( + coin.prettyName, + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), ), ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - builder: (_) => const CoinSelectSheet(), - ).then((value) { - if (value is Coin) { - ref.read(addressEntryDataProvider(widget.id)).coin = - value; - } - }); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ref.watch(addressEntryDataProvider(widget.id) - .select((value) => value.coin)) == - null - ? Text( - "Select cryptocurrency", - style: STextStyles.fieldLabel(context), - ) - : Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor( - coin: ref.watch( - addressEntryDataProvider(widget.id) - .select((value) => value.coin))!), - height: 20, - width: 20, - ), - const SizedBox( - width: 12, - ), - Text( - ref - .watch(addressEntryDataProvider(widget.id) - .select((value) => value.coin))! - .prettyName, - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: Theme.of(context) - .extension()! - .textSubtitle2, + ), + ], + ), + ), + if (!isDesktop) + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + readOnly: true, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Select cryptocurrency", + hintStyle: STextStyles.fieldLabel(context), + prefixIcon: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: RawMaterialButton( + splashColor: + Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ], + ), + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (_) => const CoinSelectSheet(), + ).then((value) { + if (value is Coin) { + ref.read(addressEntryDataProvider(widget.id)).coin = + value; + } + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ref.watch(addressEntryDataProvider(widget.id) + .select((value) => value.coin)) == + null + ? Text( + "Select cryptocurrency", + style: STextStyles.fieldLabel(context), + ) + : Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor( + coin: ref.watch( + addressEntryDataProvider(widget.id) + .select( + (value) => value.coin))!), + height: 20, + width: 20, + ), + const SizedBox( + width: 12, + ), + Text( + ref + .watch( + addressEntryDataProvider(widget.id) + .select((value) => value.coin))! + .prettyName, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + if (!isDesktop) + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ], + ), ), ), ), ), ), - ), const SizedBox( height: 8, ), @@ -253,9 +351,10 @@ class _NewContactAddressEntryFormState }, child: const ClipboardIcon(), ), - if (!Util.isDesktop && ref.watch(addressEntryDataProvider(widget.id) - .select((value) => value.address)) == - null) + if (!Util.isDesktop && + ref.watch(addressEntryDataProvider(widget.id) + .select((value) => value.address)) == + null) TextFieldIconButton( key: const Key("addAddressBookEntryScanQrButtonKey"), onTap: () async { From 8799a9cfa2a993379e34a91cef9427b4fe272740 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 15:34:48 -0600 Subject: [PATCH 106/225] my stack contact tweaks --- .../subviews/add_address_book_entry_view.dart | 2 +- .../subwidgets/desktop_address_card.dart | 18 +++++++++++------- .../subwidgets/desktop_contact_details.dart | 11 +++++++---- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index bb93c68d8..2759e9cb1 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -559,7 +559,7 @@ class _AddAddressBookEntryViewState ), ], ), - if (!isDesktop) const SizedBox(height: 8), + const SizedBox(height: 8), if (forms.length <= 1) const SizedBox( height: 8, diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart index 49b75a4a8..405b9107c 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -11,9 +11,11 @@ class DesktopAddressCard extends StatelessWidget { const DesktopAddressCard({ Key? key, required this.entry, + required this.contactId, }) : super(key: key); final ContactAddressEntry entry; + final String contactId; @override Widget build(BuildContext context) { @@ -57,13 +59,15 @@ class DesktopAddressCard extends StatelessWidget { text: "Copy", onTap: () {}, ), - const SizedBox( - width: 16, - ), - BlueTextButton( - text: "Edit", - onTap: () {}, - ), + if (contactId != "default") + const SizedBox( + width: 16, + ), + if (contactId != "default") + BlueTextButton( + text: "Edit", + onTap: () {}, + ), ], ) ], diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index bc19fe3bd..ada330a14 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -40,16 +40,18 @@ class _DesktopContactDetailsState extends ConsumerState { width: 32, height: 32, decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: contact.id == "default" + ? Colors.transparent + : Theme.of(context) + .extension()! + .textFieldDefaultBG, borderRadius: BorderRadius.circular(32), ), child: contact.id == "default" ? Center( child: SvgPicture.asset( Assets.svg.stackIcon(context), - width: 20, + width: 32, ), ) : contact.emojiChar != null @@ -134,6 +136,7 @@ class _DesktopContactDetailsState extends ConsumerState { padding: const EdgeInsets.all(18), child: DesktopAddressCard( entry: contact.addresses[i], + contactId: contact.id, ), ), ], From 51c98f90e94d9bbdeb8fe75828ec8f3a848ece29 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 16:01:11 -0600 Subject: [PATCH 107/225] contact tx history --- .../desktop_address_book.dart | 7 +- .../desktop_address_book_scaffold.dart | 3 +- .../subwidgets/desktop_address_card.dart | 18 +- .../subwidgets/desktop_contact_details.dart | 334 ++++++++++++------ 4 files changed, 248 insertions(+), 114 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index fd25617e7..f028a3424 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -427,11 +427,8 @@ class _DesktopAddressBook extends ConsumerState { ), details: currentContactId == null ? Container() - : RoundedWhiteContainer( - padding: const EdgeInsets.all(24), - child: DesktopContactDetails( - contactId: currentContactId!, - ), + : DesktopContactDetails( + contactId: currentContactId!, ), ), ), diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart index f32ea1f7f..36e44e6d4 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_book_scaffold.dart @@ -56,6 +56,7 @@ class DesktopAddressBookScaffold extends StatelessWidget { ), Expanded( child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 6, @@ -96,7 +97,7 @@ class DesktopAddressBookScaffold extends StatelessWidget { const SizedBox( height: weirdRowHeight, ), - Expanded( + Flexible( child: details ?? Container(), ), ], diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart index 405b9107c..f00e0c137 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -12,10 +16,12 @@ class DesktopAddressCard extends StatelessWidget { Key? key, required this.entry, required this.contactId, + this.clipboard = const ClipboardWrapper(), }) : super(key: key); final ContactAddressEntry entry; final String contactId; + final ClipboardInterface clipboard; @override Widget build(BuildContext context) { @@ -57,7 +63,17 @@ class DesktopAddressCard extends StatelessWidget { children: [ BlueTextButton( text: "Copy", - onTap: () {}, + onTap: () { + clipboard.setData( + ClipboardData(text: entry.address), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, ), if (contactId != "default") const SizedBox( diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index ada330a14..53597826c 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/transaction_card.dart'; +import 'package:tuple/tuple.dart'; class DesktopContactDetails extends ConsumerStatefulWidget { const DesktopContactDetails({ @@ -24,132 +31,245 @@ class DesktopContactDetails extends ConsumerStatefulWidget { } class _DesktopContactDetailsState extends ConsumerState { + List> _cachedTransactions = []; + + bool _contactHasAddress(String address, Contact contact) { + for (final entry in contact.addresses) { + if (entry.address == address) { + return true; + } + } + return false; + } + + Future>> _filteredTransactionsByContact( + List managers, + ) async { + final contact = + ref.read(addressBookServiceProvider).getContactById(widget.contactId); + + // TODO: optimise + + List> result = []; + for (final manager in managers) { + final transactions = (await manager.transactionData) + .getAllTransactions() + .values + .toList() + .where((e) => _contactHasAddress(e.address, contact)); + + for (final tx in transactions) { + result.add(Tuple2(manager.walletId, tx)); + } + } + // sort by date + result.sort((a, b) => b.item2.timestamp - a.item2.timestamp); + + return result; + } + @override Widget build(BuildContext context) { final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(widget.contactId))); - return Column( + return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: contact.id == "default" - ? Colors.transparent - : Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(32), - ), - child: contact.id == "default" - ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 32, - ), - ) - : contact.emojiChar != null - ? Center( - child: Text(contact.emojiChar!), - ) - : Center( - child: SvgPicture.asset( - Assets.svg.user, - width: 18, - ), - ), - ), - const SizedBox( - width: 16, - ), - Text( - contact.name, - style: STextStyles.desktopTextSmall(context), - ), - ], - ), - SecondaryButton( - label: "Options", - width: 86, - buttonHeight: ButtonHeight.xxs, - onPressed: () {}, - ), - ], - ), - const SizedBox( - height: 24, - ), Expanded( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Addresses", - style: STextStyles.desktopTextExtraExtraSmall( - context), - ), - BlueTextButton( - text: "Add new", - onTap: () {}, - ), - ], + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Colors.transparent + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), + ), + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 32, + ), + ) + : contact.emojiChar != null + ? Center( + child: Text(contact.emojiChar!), + ) + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), ), const SizedBox( - height: 12, + width: 16, ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - borderColor: Theme.of(context) - .extension()! - .background, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 0; i < contact.addresses.length; i++) - Column( + Text( + contact.name, + style: STextStyles.desktopTextSmall(context), + ), + ], + ), + SecondaryButton( + label: "Options", + width: 86, + buttonHeight: ButtonHeight.xxs, + onPressed: () {}, + ), + ], + ), + const SizedBox( + height: 24, + ), + Flexible( + child: ListView( + primary: false, + shrinkWrap: true, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Addresses", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + BlueTextButton( + text: "Add new", + onTap: () {}, + ), + ], + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < contact.addresses.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (i > 0) + Container( + color: Theme.of(context) + .extension()! + .background, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(18), + child: DesktopAddressCard( + entry: contact.addresses[i], + contactId: contact.id, + ), + ), + ], + ), + ], + ), + ), + Text( + "Transaction history", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + FutureBuilder( + future: _filteredTransactionsByContact( + ref.watch(walletsChangeNotifierProvider).managers), + builder: (_, + AsyncSnapshot>> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + _cachedTransactions = snapshot.data!; + + if (_cachedTransactions.isNotEmpty) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (i > 0) - Container( - color: Theme.of(context) - .extension()! - .background, - height: 1, - ), - Padding( - padding: const EdgeInsets.all(18), - child: DesktopAddressCard( - entry: contact.addresses[i], - contactId: contact.id, + ..._cachedTransactions.map( + (e) => TransactionCard( + key: Key( + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), + transaction: e.item2, + walletId: e.item1, ), ), ], ), - ], - ), - ) - ], - ), + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "No transactions found", + style: STextStyles.itemSubtitle(context), + ), + ), + ); + } + } else { + // TODO: proper loading animation + if (_cachedTransactions.isEmpty) { + return const LoadingIndicator(); + } else { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ..._cachedTransactions.map( + (e) => TransactionCard( + key: Key( + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), + transaction: e.item2, + walletId: e.item1, + ), + ), + ], + ), + ); + } + } + }, + ), + ], ), ), - ); - }, + ], + ), ), ), ], From 494849364317cb55e46e8c7e40d786dc37ede959 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 16:18:00 -0600 Subject: [PATCH 108/225] contact tx history --- .../subwidgets/desktop_contact_details.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index 53597826c..fb094d766 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -192,9 +192,16 @@ class _DesktopContactDetailsState extends ConsumerState { ], ), ), - Text( - "Transaction history", - style: STextStyles.desktopTextExtraExtraSmall(context), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 12, + ), + child: Text( + "Transaction history", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), ), FutureBuilder( future: _filteredTransactionsByContact( From 318758f76850a471ce0dbfc0a1ecd6a190fb4e4a Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 17 Nov 2022 16:55:34 -0700 Subject: [PATCH 109/225] copy receive address has pointer finger cursor --- .../sub_widgets/desktop_receive.dart | 142 +++++++++--------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 3de4ed1e3..1dd2e607e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -117,78 +117,82 @@ class _DesktopReceiveState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - GestureDetector( - onTap: () { - clipboard.setData( - ClipboardData(text: receivingAddress), - ); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).extension()!.background, - width: 2, + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + clipboard.setData( + ClipboardData(text: receivingAddress), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).extension()!.background, + width: 2, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: RoundedWhiteContainer( - child: Column( - children: [ - Row( - children: [ - Text( - "Your ${coin.ticker} address", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 15, - height: 15, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ], - ), - const SizedBox( - height: 8, - ), - Row( - children: [ - Expanded( - child: Text( - receivingAddress, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + child: RoundedWhiteContainer( + child: Column( + children: [ + Row( + children: [ + Text( + "Your ${coin.ticker} address", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 15, + height: 15, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ], + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + Expanded( + child: Text( + receivingAddress, + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), ), ), From cd19d776ae04631571a8d64b008d3cf52746d331 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 16:47:50 -0600 Subject: [PATCH 110/225] desktop edit contact address entry --- .../subviews/edit_contact_address_view.dart | 433 +++++++++--------- .../subwidgets/desktop_address_card.dart | 62 ++- 2 files changed, 268 insertions(+), 227 deletions(-) diff --git a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart index 618a41982..f0143d39d 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart @@ -12,7 +12,11 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; class EditContactAddressView extends ConsumerStatefulWidget { const EditContactAddressView({ @@ -44,6 +48,42 @@ class _EditContactAddressViewState late final BarcodeScannerInterface barcodeScanner; late final ClipboardInterface clipboard; + Future save(Contact contact) async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75), + ); + } + List entries = contact.addresses.toList(); + + final entry = entries.firstWhere( + (e) => + e.label == addressEntry.label && + e.address == addressEntry.address && + e.coin == addressEntry.coin, + ); + + final index = entries.indexOf(entry); + entries.remove(entry); + + ContactAddressEntry editedEntry = + ref.read(addressEntryDataProvider(0)).buildAddressEntry(); + + entries.insert(index, editedEntry); + + Contact editedContact = contact.copyWith(addresses: entries); + + if (await ref.read(addressBookServiceProvider).editContact(editedContact)) { + if (mounted) { + Navigator.of(context).pop(); + } + // TODO show success notification + } else { + // TODO show error notification + } + } + @override void initState() { contactId = widget.contactId; @@ -59,236 +99,181 @@ class _EditContactAddressViewState final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + final bool isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit address", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit address", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - Row( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: contact.emojiChar == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, - ) - : Text( - contact.emojiChar!, - style: STextStyles.pageTitleH1(context), - ), - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - contact.name, - style: STextStyles.pageTitleH2(context), - ), - ), - ), - ], - ), - const SizedBox( - height: 16, - ), - NewContactAddressEntryForm( - id: 0, - barcodeScanner: barcodeScanner, - clipboard: clipboard, - ), - const SizedBox( - height: 24, - ), - GestureDetector( - onTap: () async { - // delete address - final _addresses = contact.addresses; - final entry = _addresses.firstWhere( - (e) => - e.label == addressEntry.label && - e.address == addressEntry.address && - e.coin == addressEntry.coin, - ); - - _addresses.remove(entry); - Contact editedContact = - contact.copyWith(addresses: _addresses); - if (await ref - .read(addressBookServiceProvider) - .editContact(editedContact)) { - Navigator.of(context).pop(); - // TODO show success notification - } else { - // TODO show error notification - } - }, - child: Text( - "Delete address", - style: STextStyles.link(context), - ), - ), - const Spacer(), - const SizedBox( - height: 16, - ), - Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Builder( - builder: (context) { - bool shouldEnableSave = - ref.watch(validContactStateProvider([0])); - - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor( - context), - onPressed: shouldEnableSave - ? () async { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75), - ); - } - List entries = - contact.addresses.toList(); - - final entry = entries.firstWhere( - (e) => - e.label == - addressEntry.label && - e.address == - addressEntry.address && - e.coin == addressEntry.coin, - ); - - final index = - entries.indexOf(entry); - entries.remove(entry); - - ContactAddressEntry editedEntry = ref - .read( - addressEntryDataProvider(0)) - .buildAddressEntry(); - - entries.insert(index, editedEntry); - - Contact editedContact = contact - .copyWith(addresses: entries); - - if (await ref - .read( - addressBookServiceProvider) - .editContact(editedContact)) { - if (mounted) { - Navigator.of(context).pop(); - } - // TODO show success notification - } else { - // TODO show error notification - } - } - : null, - child: Text( - "Save", - style: STextStyles.button(context), - ), - ); - }, - ), - ), - ], - ), - ], + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, ), ), ), ), + ); + }, + ), + ), + child: Column( + children: [ + Row( + children: [ + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), + child: Center( + child: contact.emojiChar == null + ? SvgPicture.asset( + Assets.svg.user, + height: 24, + width: 24, + ) + : Text( + contact.emojiChar!, + style: STextStyles.pageTitleH1(context), + ), + ), + ), + const SizedBox( + width: 16, + ), + if (isDesktop) + Text( + contact.name, + style: STextStyles.pageTitleH2(context), + ), + if (!isDesktop) + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + contact.name, + style: STextStyles.pageTitleH2(context), + ), + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + NewContactAddressEntryForm( + id: 0, + barcodeScanner: barcodeScanner, + clipboard: clipboard, + ), + const SizedBox( + height: 24, + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, ), - ); - }, + child: GestureDetector( + onTap: () async { + // delete address + final _addresses = contact.addresses; + final entry = _addresses.firstWhere( + (e) => + e.label == addressEntry.label && + e.address == addressEntry.address && + e.coin == addressEntry.coin, + ); + + _addresses.remove(entry); + Contact editedContact = contact.copyWith(addresses: _addresses); + if (await ref + .read(addressBookServiceProvider) + .editContact(editedContact)) { + Navigator.of(context).pop(); + // TODO show success notification + } else { + // TODO show error notification + } + }, + child: Text( + "Delete address", + style: STextStyles.link(context), + ), + ), + ), + const Spacer(), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + if (!isDesktop && FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + enabled: ref.watch(validContactStateProvider([0])), + onPressed: () => save(contact), + buttonHeight: isDesktop ? ButtonHeight.l : null, + ), + ), + ], + ), + ], ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart index f00e0c137..713c9e965 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -1,15 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; class DesktopAddressCard extends StatelessWidget { const DesktopAddressCard({ @@ -80,9 +85,60 @@ class DesktopAddressCard extends StatelessWidget { width: 16, ), if (contactId != "default") - BlueTextButton( - text: "Edit", - onTap: () {}, + Consumer( + builder: (context, ref, child) { + return BlueTextButton( + text: "Edit", + onTap: () async { + ref.read(addressEntryDataProvider(0)).address = + entry.address; + ref.read(addressEntryDataProvider(0)).addressLabel = + entry.label; + ref.read(addressEntryDataProvider(0)).coin = + entry.coin; + + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: 566, + child: Column( + children: [ + Row( + children: [ + const SizedBox( + width: 8, + ), + const AppBarBackButton( + isCompact: true, + ), + Text( + "Edit address", + style: STextStyles.desktopH3(context), + ), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 20, + left: 32, + right: 32, + bottom: 32, + ), + child: EditContactAddressView( + contactId: contactId, + addressEntry: entry, + ), + ), + ), + ], + ), + ), + ); + }, + ); + }, ), ], ) From df64e48e1ef2985cc58363bf844c288bd71ebd5a Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 17:00:06 -0600 Subject: [PATCH 111/225] desktop add new contact address entry --- .../add_new_contact_address_view.dart | 342 +++++++++--------- .../subwidgets/desktop_address_card.dart | 2 + .../subwidgets/desktop_contact_details.dart | 49 ++- 3 files changed, 213 insertions(+), 180 deletions(-) diff --git a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart index e5dbaa7b9..dc25c3dc1 100644 --- a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart @@ -12,7 +12,11 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; class AddNewContactAddressView extends ConsumerStatefulWidget { const AddNewContactAddressView({ @@ -55,190 +59,170 @@ class _AddNewContactAddressViewState final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Add new address", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Add new address", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - Row( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: contact.emojiChar == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, - ) - : Text( - contact.emojiChar!, - style: STextStyles.pageTitleH1(context), - ), - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - contact.name, - style: STextStyles.pageTitleH2(context), - ), - ), - ), - ], - ), - const SizedBox( - height: 16, - ), - NewContactAddressEntryForm( - id: 0, - barcodeScanner: barcodeScanner, - clipboard: clipboard, - ), - const SizedBox( - height: 16, - ), - const Spacer(), - Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Builder( - builder: (context) { - bool shouldEnableSave = - ref.watch(validContactStateProvider([0])); - - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor( - context), - onPressed: shouldEnableSave - ? () async { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75), - ); - } - List entries = - contact.addresses; - - entries.add(ref - .read( - addressEntryDataProvider(0)) - .buildAddressEntry()); - - Contact editedContact = contact - .copyWith(addresses: entries); - - if (await ref - .read( - addressBookServiceProvider) - .editContact(editedContact)) { - if (mounted) { - Navigator.of(context).pop(); - } - // TODO show success notification - } else { - // TODO show error notification - } - } - : null, - child: Text( - "Save", - style: STextStyles.button(context), - ), - ); - }, - ), - ), - ], - ) - ], + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, ), ), ), ), - ), - ); - }, + ); + }, + ), + ), + child: Column( + children: [ + Row( + children: [ + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), + child: Center( + child: contact.emojiChar == null + ? SvgPicture.asset( + Assets.svg.user, + height: 24, + width: 24, + ) + : Text( + contact.emojiChar!, + style: STextStyles.pageTitleH1(context), + ), + ), + ), + const SizedBox( + width: 16, + ), + if (isDesktop) + Text( + contact.name, + style: STextStyles.pageTitleH2(context), + ), + if (!isDesktop) + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + contact.name, + style: STextStyles.pageTitleH2(context), + ), + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + NewContactAddressEntryForm( + id: 0, + barcodeScanner: barcodeScanner, + clipboard: clipboard, + ), + const SizedBox( + height: 16, + ), + const Spacer(), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + if (!isDesktop && FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + enabled: ref.watch(validContactStateProvider([0])), + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75), + ); + } + List entries = contact.addresses; + + entries.add(ref + .read(addressEntryDataProvider(0)) + .buildAddressEntry()); + + Contact editedContact = + contact.copyWith(addresses: entries); + + if (await ref + .read(addressBookServiceProvider) + .editContact(editedContact)) { + if (mounted) { + Navigator.of(context).pop(); + } + // TODO show success notification + } else { + // TODO show error notification + } + }, + ), + ), + ], + ) + ], ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart index 713c9e965..4d58dc474 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart @@ -90,6 +90,8 @@ class DesktopAddressCard extends StatelessWidget { return BlueTextButton( text: "Edit", onTap: () async { + ref.refresh( + addressEntryDataProviderFamilyRefresher); ref.read(addressEntryDataProvider(0)).address = entry.address; ref.read(addressEntryDataProvider(0)).addressLabel = diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index fb094d766..1c27b6836 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -3,14 +3,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -154,7 +158,50 @@ class _DesktopContactDetailsState extends ConsumerState { ), BlueTextButton( text: "Add new", - onTap: () {}, + onTap: () async { + ref.refresh( + addressEntryDataProviderFamilyRefresher); + + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: 566, + child: Column( + children: [ + Row( + children: [ + const SizedBox( + width: 8, + ), + const AppBarBackButton( + isCompact: true, + ), + Text( + "Add new address", + style: + STextStyles.desktopH3(context), + ), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 20, + left: 32, + right: 32, + bottom: 32, + ), + child: AddNewContactAddressView( + contactId: widget.contactId, + ), + ), + ), + ], + ), + ), + ); + }, ), ], ), From e70f5b0709eea46f42cfd4ed8dd9f7ba2aa24b20 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 18:14:30 -0600 Subject: [PATCH 112/225] WIP desktop contact options context popup menu --- .../subwidgets/desktop_contact_details.dart | 262 +++++++++++++++++- 1 file changed, 260 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index 1c27b6836..2cd20a839 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -131,9 +131,19 @@ class _DesktopContactDetailsState extends ConsumerState { ), SecondaryButton( label: "Options", - width: 86, + width: 96, buttonHeight: ButtonHeight.xxs, - onPressed: () {}, + onPressed: () async { + await showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return DesktopContactOptionsMenuPopup( + contactId: contact.id, + ); + }, + ); + }, ), ], ), @@ -330,3 +340,251 @@ class _DesktopContactDetailsState extends ConsumerState { ); } } + +class DesktopContactOptionsMenuPopup extends ConsumerStatefulWidget { + const DesktopContactOptionsMenuPopup({Key? key, required this.contactId}) + : super(key: key); + + final String contactId; + + @override + ConsumerState createState() => + _DesktopContactOptionsMenuPopupState(); +} + +class _DesktopContactOptionsMenuPopupState + extends ConsumerState { + bool hoveredOnStar = false; + bool hoveredOnPencil = false; + bool hoveredOnTrash = false; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 210, + left: MediaQuery.of(context).size.width - 280, + child: Container( + width: 270, + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + 20, + ), + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnStar = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnStar = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + final contact = + ref.read(addressBookServiceProvider).getContactById( + widget.contactId, + ); + ref.read(addressBookServiceProvider).editContact( + contact.copyWith( + isFavorite: !contact.isFavorite, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.star, + width: 24, + height: 22, + color: hoveredOnStar + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + ref.watch(addressBookServiceProvider.select( + (value) => value + .getContactById(widget.contactId) + .isFavorite)) + ? "Remove from favorites" + : "Add to favorites", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + const SizedBox( + height: 2, + ), + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnPencil = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnPencil = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + print("should go to edit"); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 24, + height: 22, + color: hoveredOnPencil + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Edit contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + const SizedBox( + height: 2, + ), + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnTrash = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnTrash = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + print("should delete contact"); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.trash, + width: 24, + height: 22, + color: hoveredOnTrash + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Delete contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} From 38251dc5edb0d5e2f6a771e53deb98aa0e96a68e Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 18:21:04 -0600 Subject: [PATCH 113/225] textStyles prep for ocean theme --- lib/utilities/text_styles.dart | 54 ++++++++++++++++++++++++++++ lib/utilities/theme/color_theme.dart | 1 + 2 files changed, 55 insertions(+) diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index 63aa19afb..db4764459 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -15,6 +15,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -32,6 +33,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 18, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -49,6 +51,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -66,6 +69,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -83,6 +87,7 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -100,6 +105,7 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -117,6 +123,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -134,6 +141,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -151,6 +159,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -168,6 +177,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -185,6 +195,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -202,6 +213,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -219,6 +231,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -237,6 +250,7 @@ class STextStyles { fontSize: 14, height: 14 / 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textFieldActiveSearchIconRight, @@ -255,6 +269,7 @@ class STextStyles { fontWeight: FontWeight.w700, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -272,6 +287,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemLabel, @@ -289,6 +305,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -306,6 +323,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -324,6 +342,7 @@ class STextStyles { fontSize: 14, height: 1.5, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -343,6 +362,7 @@ class STextStyles { fontSize: 14, height: 1.5, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -361,6 +381,7 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -378,6 +399,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorRed, @@ -395,6 +417,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemIcons, @@ -412,6 +435,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorBlue, @@ -429,6 +453,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -446,6 +471,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -463,6 +489,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -480,6 +507,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 10, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textError, @@ -497,6 +525,7 @@ class STextStyles { fontWeight: FontWeight.w500, fontSize: 10, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -517,6 +546,7 @@ class STextStyles { fontSize: 40, height: 40 / 40, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -536,6 +566,7 @@ class STextStyles { fontSize: 32, height: 32 / 32, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -555,6 +586,7 @@ class STextStyles { fontSize: 24, height: 24 / 24, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -574,6 +606,7 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -593,6 +626,7 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -612,6 +646,7 @@ class STextStyles { fontSize: 20, height: 28 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -631,6 +666,7 @@ class STextStyles { fontSize: 24, height: 33 / 24, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -650,6 +686,7 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -669,6 +706,7 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -688,6 +726,7 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -707,6 +746,7 @@ class STextStyles { fontSize: 20, height: 26 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondaryDisabled, @@ -726,6 +766,7 @@ class STextStyles { fontSize: 18, height: 27 / 18, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -745,6 +786,7 @@ class STextStyles { fontSize: 16, height: 24 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -764,6 +806,7 @@ class STextStyles { fontSize: 14, height: 21 / 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -783,6 +826,7 @@ class STextStyles { fontSize: 14, height: 21 / 14, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -802,6 +846,7 @@ class STextStyles { fontSize: 16, height: 24 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -821,6 +866,7 @@ class STextStyles { fontSize: 20, height: 30 / 20, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -840,6 +886,7 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.8), @@ -859,6 +906,7 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -878,6 +926,7 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.5), @@ -897,6 +946,7 @@ class STextStyles { fontSize: 16, height: 20.8 / 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -915,6 +965,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 8, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).textDark, @@ -932,6 +983,7 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 26, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).numberTextDefault, @@ -950,6 +1002,7 @@ class STextStyles { fontWeight: FontWeight.w400, fontSize: 12, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, @@ -969,6 +1022,7 @@ class STextStyles { fontWeight: FontWeight.w600, fontSize: 16, ); + case ThemeType.oceanBreeze: case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart index 49fd41a6e..852e2f586 100644 --- a/lib/utilities/theme/color_theme.dart +++ b/lib/utilities/theme/color_theme.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; enum ThemeType { light, dark, + oceanBreeze, } abstract class StackColorTheme { From 390c3f186ff8266479a38ce90684840b0f554042 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 18:49:56 -0600 Subject: [PATCH 114/225] temporarily disable in wallet exchange button --- .../wallet_view/desktop_wallet_view.dart | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index a7de8fdf4..d08864eee 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -467,36 +467,36 @@ class _DesktopWalletViewState extends ConsumerState { ); }, ), - if (coin == Coin.firo) const SizedBox(width: 16), - SecondaryButton( - width: 180, - buttonHeight: ButtonHeight.l, - onPressed: () { - _onExchangePressed(context); - }, - label: "Exchange", - icon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .buttonBackPrimary - .withOpacity(0.2), - ), - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 14, - height: 14, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), - ), - ), + // if (coin == Coin.firo) const SizedBox(width: 16), + // SecondaryButton( + // width: 180, + // buttonHeight: ButtonHeight.l, + // onPressed: () { + // _onExchangePressed(context); + // }, + // label: "Exchange", + // icon: Container( + // width: 24, + // height: 24, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(24), + // color: Theme.of(context) + // .extension()! + // .buttonBackPrimary + // .withOpacity(0.2), + // ), + // child: Center( + // child: SvgPicture.asset( + // Assets.svg.arrowRotate2, + // width: 14, + // height: 14, + // color: Theme.of(context) + // .extension()! + // .buttonTextSecondary, + // ), + // ), + // ), + // ), ], ), ), From 4eed147f10f978125f7ebaf1b57b7410732fe287 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 17 Nov 2022 19:23:15 -0600 Subject: [PATCH 115/225] update main to check for ocean breeze theme --- lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index b1f917f58..42b4ee718 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -301,6 +301,9 @@ class _MaterialAppWithThemeState extends ConsumerState case "dark": themeType = ThemeType.dark; break; + case "oceanBreeze": + themeType = ThemeType.oceanBreeze; + break; case "light": default: themeType = ThemeType.light; From 2137cffd8459b750c9bd7b056eb46c93e0f60418 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 17 Nov 2022 20:04:02 -0700 Subject: [PATCH 116/225] ocean breeze theme colors added --- .../appearance_settings_view.dart | 5 +- lib/utilities/text_styles.dart | 296 +++++++++++++++++ lib/utilities/theme/ocean_breeze_colors.dart | 306 ++++++++++++++++++ 3 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 lib/utilities/theme/ocean_breeze_colors.dart diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart index b0cf35a84..3a1b842f6 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart @@ -141,7 +141,10 @@ class AppearanceSettingsView extends ConsumerWidget { key: "colorScheme", value: (newValue ? ThemeType.dark - : ThemeType.light) + : (newValue + ? ThemeType.light + : ThemeType + .oceanBreeze)) .name, ); ref diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index db4764459..c9dd15e1d 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -16,6 +16,11 @@ class STextStyles { fontSize: 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -34,6 +39,11 @@ class STextStyles { fontSize: 18, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 18, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -52,6 +62,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -70,6 +85,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -88,6 +108,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -106,6 +131,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -124,6 +154,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -142,6 +177,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -160,6 +200,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimary, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -178,6 +223,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -196,6 +246,11 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -214,6 +269,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark3, @@ -232,6 +292,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -251,6 +316,12 @@ class STextStyles { height: 14 / 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textFieldActiveSearchIconRight, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 14 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textFieldActiveSearchIconRight, @@ -270,6 +341,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w700, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -288,6 +364,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).infoItemLabel, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemLabel, @@ -306,6 +387,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -324,6 +410,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -343,6 +434,12 @@ class STextStyles { height: 1.5, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle2, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -363,6 +460,12 @@ class STextStyles { height: 1.5, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 1.5, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -382,6 +485,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -400,6 +508,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).accentColorRed, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorRed, @@ -418,6 +531,11 @@ class STextStyles { fontSize: 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).infoItemIcons, + fontWeight: FontWeight.w500, + fontSize: 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).infoItemIcons, @@ -436,6 +554,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).accentColorBlue, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).accentColorBlue, @@ -454,6 +577,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -472,6 +600,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -490,6 +623,11 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -508,6 +646,11 @@ class STextStyles { fontSize: 10, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textError, + fontWeight: FontWeight.w500, + fontSize: 10, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textError, @@ -526,6 +669,11 @@ class STextStyles { fontSize: 10, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 10, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -547,6 +695,12 @@ class STextStyles { height: 40 / 40, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 40, + height: 40 / 40, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -567,6 +721,12 @@ class STextStyles { height: 32 / 32, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 32, + height: 32 / 32, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -587,6 +747,12 @@ class STextStyles { height: 24 / 24, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 24, + height: 24 / 24, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -607,6 +773,12 @@ class STextStyles { height: 30 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -627,6 +799,12 @@ class STextStyles { height: 30 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -647,6 +825,12 @@ class STextStyles { height: 28 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 20, + height: 28 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -667,6 +851,12 @@ class STextStyles { height: 33 / 24, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w400, + fontSize: 24, + height: 33 / 24, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -687,6 +877,12 @@ class STextStyles { height: 26 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimary, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, @@ -707,6 +903,12 @@ class STextStyles { height: 26 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -727,6 +929,12 @@ class STextStyles { height: 26 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondary, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -747,6 +955,12 @@ class STextStyles { height: 26 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 26 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondaryDisabled, @@ -767,6 +981,12 @@ class STextStyles { height: 27 / 18, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 18, + height: 27 / 18, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -787,6 +1007,12 @@ class STextStyles { height: 24 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimaryDisabled, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 24 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, @@ -807,6 +1033,12 @@ class STextStyles { height: 21 / 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 14, + height: 21 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle1, @@ -827,6 +1059,12 @@ class STextStyles { height: 21 / 14, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 14, + height: 21 / 14, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -847,6 +1085,12 @@ class STextStyles { height: 24 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).buttonTextSecondary, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 24 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, @@ -867,6 +1111,12 @@ class STextStyles { height: 30 / 20, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textSubtitle2, + fontWeight: FontWeight.w500, + fontSize: 20, + height: 30 / 20, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textSubtitle2, @@ -887,6 +1137,12 @@ class STextStyles { height: 20.8 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark.withOpacity(0.8), + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.8), @@ -907,6 +1163,12 @@ class STextStyles { height: 20.8 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -927,6 +1189,12 @@ class STextStyles { height: 20.8 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark.withOpacity(0.5), + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.5), @@ -947,6 +1215,12 @@ class STextStyles { height: 20.8 / 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 16, + height: 20.8 / 16, + ); case ThemeType.dark: return GoogleFonts.inter( color: _theme(context).textDark, @@ -966,6 +1240,11 @@ class STextStyles { fontSize: 8, ); case ThemeType.oceanBreeze: + return GoogleFonts.roboto( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 8, + ); case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).textDark, @@ -984,6 +1263,11 @@ class STextStyles { fontSize: 26, ); case ThemeType.oceanBreeze: + return GoogleFonts.roboto( + color: _theme(context).numberTextDefault, + fontWeight: FontWeight.w400, + fontSize: 26, + ); case ThemeType.dark: return GoogleFonts.roboto( color: _theme(context).numberTextDefault, @@ -1003,6 +1287,12 @@ class STextStyles { fontSize: 12, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + letterSpacing: 0.5, + color: _theme(context).accentColorDark, + fontWeight: FontWeight.w400, + fontSize: 12, + ); case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, @@ -1023,6 +1313,12 @@ class STextStyles { fontSize: 16, ); case ThemeType.oceanBreeze: + return GoogleFonts.inter( + letterSpacing: 0.5, + color: _theme(context).accentColorDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); case ThemeType.dark: return GoogleFonts.inter( letterSpacing: 0.5, diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart new file mode 100644 index 000000000..ff2f4e85e --- /dev/null +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -0,0 +1,306 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/theme/color_theme.dart'; + +class OceanBreezeColors extends StackColorTheme { + @override + ThemeType get themeType => ThemeType.oceanBreeze; + + @override + Color get background => const Color(0xFFF3F7FA); + @override + Color get overlay => const Color(0xFF111215); + + @override + Color get accentColorBlue => const Color(0xFF077CBE); + @override + Color get accentColorGreen => const Color(0xFF00A591); + @override + Color get accentColorYellow => const Color(0xFFF4C517); + @override + Color get accentColorRed => const Color(0xFFD1382D); + @override + Color get accentColorOrange => const Color(0xFFFF985F); + @override + Color get accentColorDark => const Color(0xFF232323); + + @override + Color get shadow => const Color(0xFF388192); + + @override + Color get textDark => const Color(0xFF232323); + @override + Color get textDark2 => const Color(0xFF333333); + @override + Color get textDark3 => const Color(0xFF696B6C); + @override + Color get textSubtitle1 => const Color(0xFF7E8284); + @override + Color get textSubtitle2 => const Color(0xFF919393); + @override + Color get textSubtitle3 => const Color(0xFFB0B2B2); + @override + Color get textSubtitle4 => const Color(0xFFD1D3D3); + @override + Color get textSubtitle5 => const Color(0xFFDEDFE1); + @override + Color get textSubtitle6 => const Color(0xFFF1F1F1); + @override + Color get textWhite => const Color(0xFFFFFFFF); + @override + Color get textFavoriteCard => const Color(0xFF232323); + @override + Color get textError => const Color(0xFF8D0006); + + // button background + @override + Color get buttonBackPrimary => const Color(0xFF227386); + @override + Color get buttonBackSecondary => const Color(0xFFC2DAE2); + @override + Color get buttonBackPrimaryDisabled => const Color(0xFFBDD5DB); + @override + Color get buttonBackSecondaryDisabled => const Color(0xFFBDBDBD); + @override + Color get buttonBackBorder => const Color(0xFF227386); + @override + Color get buttonBackBorderDisabled => const Color(0xFFBDD5DB); + + @override + Color get numberBackDefault => const Color(0xFFFFFFFF); + @override + Color get numpadBackDefault => const Color(0xFF227386); + @override + Color get bottomNavBack => const Color(0xFFFFFFFF); + + // button text/element + @override + Color get buttonTextPrimary => const Color(0xFFFFFFFF); + @override + Color get buttonTextSecondary => const Color(0xFF232323); + @override + Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF); + @override + Color get buttonTextSecondaryDisabled => const Color(0xFFBDD5DB); + @override + Color get buttonTextBorder => const Color(0xFF227386); + @override + Color get buttonTextDisabled => const Color(0xFFFFFFFF); + @override + Color get buttonTextBorderless => const Color(0xFF056EC6); + @override + Color get buttonTextBorderlessDisabled => const Color(0xFFB6B6B6); + @override + Color get numberTextDefault => const Color(0xFF232323); + @override + Color get numpadTextDefault => const Color(0xFFFFFFFF); + @override + Color get bottomNavText => const Color(0xFF232323); + + // switch + @override + Color get switchBGOn => const Color(0xFF056EC6); + @override + Color get switchBGOff => const Color(0xFFCCDBF9); + @override + Color get switchBGDisabled => const Color(0xFFC5C6C9); + @override + Color get switchCircleOn => const Color(0xFFDAE2FF); + @override + Color get switchCircleOff => const Color(0xFFFBFCFF); + @override + Color get switchCircleDisabled => const Color(0xFFFBFCFF); + + // step indicator background + @override + Color get stepIndicatorBGCheck => const Color(0xFFCDD9FF); + @override + Color get stepIndicatorBGNumber => const Color(0xFFCDD9FF); + @override + Color get stepIndicatorBGInactive => const Color(0xFFA6C7D1); + @override + Color get stepIndicatorBGLines => const Color(0xFF90B8DC); + @override + Color get stepIndicatorBGLinesInactive => const Color(0xFFBCD4EA); + @override + Color get stepIndicatorIconText => const Color(0xFF005BAF); + @override + Color get stepIndicatorIconNumber => const Color(0xFF005BAF); + @override + Color get stepIndicatorIconInactive => const Color(0xFFD4DFFF); + + // checkbox + @override + Color get checkboxBGChecked => const Color(0xFF056EC6); + @override + Color get checkboxBorderEmpty => const Color(0xFF8C8F90); + @override + Color get checkboxBGDisabled => const Color(0xFFB0C9ED); + @override + Color get checkboxIconChecked => const Color(0xFFFFFFFF); + @override + Color get checkboxIconDisabled => const Color(0xFFFFFFFF); + @override + Color get checkboxTextLabel => const Color(0xFF232323); + + // snack bar + @override + Color get snackBarBackSuccess => const Color(0xFFADD6D2); + @override + Color get snackBarBackError => const Color(0xFFF6C7C3); + @override + Color get snackBarBackInfo => const Color(0xFFCCD7FF); + @override + Color get snackBarTextSuccess => const Color(0xFF075547); + @override + Color get snackBarTextError => const Color(0xFF8D0006); + @override + Color get snackBarTextInfo => const Color(0xFF002569); + + // icons + @override + Color get bottomNavIconBack => const Color(0xFFA7C7CF); + @override + Color get bottomNavIconIcon => const Color(0xFF227386); + + @override + Color get topNavIconPrimary => const Color(0xFF227386); + @override + Color get topNavIconGreen => const Color(0xFF00A591); + @override + Color get topNavIconYellow => const Color(0xFFFDD33A); + @override + Color get topNavIconRed => const Color(0xFFEA4649); + + @override + Color get settingsIconBack => const Color(0xFFE0E3E3); + @override + Color get settingsIconIcon => const Color(0xFF232323); + @override + Color get settingsIconBack2 => const Color(0xFF80D2C8); + @override + Color get settingsIconElement => const Color(0xFF00A591); + + // text field + @override + Color get textFieldActiveBG => const Color(0xFFD3E3E7); + @override + Color get textFieldDefaultBG => const Color(0xFFD8E7EB); + @override + Color get textFieldErrorBG => const Color(0xFFF6C7C3); + @override + Color get textFieldSuccessBG => const Color(0xFFADD6D2); + + @override + Color get textFieldActiveSearchIconLeft => const Color(0xFF86898C); + @override + Color get textFieldDefaultSearchIconLeft => const Color(0xFF86898C); + @override + Color get textFieldErrorSearchIconLeft => const Color(0xFF8D0006); + @override + Color get textFieldSuccessSearchIconLeft => const Color(0xFF006C4D); + + @override + Color get textFieldActiveText => const Color(0xFF232323); + @override + Color get textFieldDefaultText => const Color(0xFF86898C); + @override + Color get textFieldErrorText => const Color(0xFF000000); + @override + Color get textFieldSuccessText => const Color(0xFF000000); + + @override + Color get textFieldActiveLabel => const Color(0xFF86898C); + @override + Color get textFieldErrorLabel => const Color(0xFF8D0006); + @override + Color get textFieldSuccessLabel => const Color(0xFF077C6E); + + @override + Color get textFieldActiveSearchIconRight => const Color(0xFF388192); + @override + Color get textFieldDefaultSearchIconRight => const Color(0xFF388192); + @override + Color get textFieldErrorSearchIconRight => const Color(0xFF8D0006); + @override + Color get textFieldSuccessSearchIconRight => const Color(0xFF077C6E); + + // settings item level2 + @override + Color get settingsItem2ActiveBG => const Color(0xFFFFFFFF); + @override + Color get settingsItem2ActiveText => const Color(0xFF232323); + @override + Color get settingsItem2ActiveSub => const Color(0xFF8C8F90); + + // radio buttons + @override + Color get radioButtonIconBorder => const Color(0xFF056EC6); + @override + Color get radioButtonIconBorderDisabled => const Color(0xFF8C8D97); + @override + Color get radioButtonBorderEnabled => const Color(0xFF056EC6); + @override + Color get radioButtonBorderDisabled => const Color(0xFF8C8D97); + @override + Color get radioButtonIconCircle => const Color(0xFF056EC6); + @override + Color get radioButtonIconEnabled => const Color(0xFF056EC6); + @override + Color get radioButtonTextEnabled => const Color(0xFF42444B); + @override + Color get radioButtonTextDisabled => const Color(0xFF42444B); + @override + Color get radioButtonLabelEnabled => const Color(0xFF8C8F90); + @override + Color get radioButtonLabelDisabled => const Color(0xFF8C8F90); + + // info text + @override + Color get infoItemBG => const Color(0xFFFFFFFF); + @override + Color get infoItemLabel => const Color(0xFF838788); + @override + Color get infoItemText => const Color(0xFF232323); + @override + Color get infoItemIcons => const Color(0xFF056EC6); + + // popup + @override + Color get popupBG => const Color(0xFFFFFFFF); + + // currency list + @override + Color get currencyListItemBG => const Color(0xFFF0F5F7); + + // bottom nav + @override + Color get stackWalletBG => const Color(0xFFFFFFFF); + @override + Color get stackWalletMid => const Color(0xFFFFFFFF); + @override + Color get stackWalletBottom => const Color(0xFF232323); + @override + Color get bottomNavShadow => const Color(0xFF388192); + + @override + Color get favoriteStarActive => const Color(0xFFF4C517); + @override + Color get favoriteStarInactive => const Color(0xFFB0B2B2); + + @override + Color get splash => const Color(0xFF8E9192); + @override + Color get highlight => const Color(0xFFA9ACAC); + @override + Color get warningForeground => const Color(0xFF232323); + @override + Color get warningBackground => const Color(0xFFF6C7C3); + @override + Color get loadingOverlayTextColor => const Color(0xFFF7F7F7); + @override + Color get myStackContactIconBG => const Color(0xFFD8E7EB); + @override + Color get textConfirmTotalAmount => const Color(0xFF232323); + @override + Color get textSelectedWordTableItem => const Color(0xFF232323); +} From 02dadd432ca70d30197ca510474b8ee4bb29ba1b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 17 Nov 2022 20:05:29 -0700 Subject: [PATCH 117/225] ocean breeze added --- lib/main.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 42b4ee718..2d1bab160 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -57,6 +57,7 @@ import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; +import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:window_size/window_size.dart'; @@ -317,8 +318,11 @@ class _MaterialAppWithThemeState extends ConsumerState WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - themeType == ThemeType.dark ? DarkColors() : LightColors()); + StackColors.fromStackColorTheme(themeType == ThemeType.dark + ? DarkColors() + : (themeType == ThemeType.light + ? LightColors() + : OceanBreezeColors())); if (Platform.isAndroid) { // fetch open file if it exists From 17e4976a899b3a0f5ceecd20edf10d1495a0568c Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 09:07:11 -0600 Subject: [PATCH 118/225] include flushbartype in flushbar import --- lib/notifications/show_flush_bar.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/notifications/show_flush_bar.dart b/lib/notifications/show_flush_bar.dart index 5320c8a9d..47cea682a 100644 --- a/lib/notifications/show_flush_bar.dart +++ b/lib/notifications/show_flush_bar.dart @@ -6,6 +6,8 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +export 'package:stackwallet/utilities/enums/flush_bar_type.dart'; + Future showFloatingFlushBar({ required FlushBarType type, required String message, From aa966a106dd3c603da01bf3d24b3f6358441399c Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 09:07:31 -0600 Subject: [PATCH 119/225] add delete contact functionality for desktop --- .../subwidgets/desktop_contact_details.dart | 382 ++++++++++++------ lib/widgets/address_book_card.dart | 13 +- 2 files changed, 260 insertions(+), 135 deletions(-) diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index 2cd20a839..96eed4835 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; @@ -15,6 +16,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -74,8 +77,16 @@ class _DesktopContactDetailsState extends ConsumerState { @override Widget build(BuildContext context) { - final contact = ref.watch(addressBookServiceProvider - .select((value) => value.getContactById(widget.contactId))); + // provider hack to prevent trying to update widget with deleted contact + Contact? _contact; + try { + _contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(widget.contactId))); + } catch (_) { + return Container(); + } + + final contact = _contact!; return Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -129,22 +140,23 @@ class _DesktopContactDetailsState extends ConsumerState { ), ], ), - SecondaryButton( - label: "Options", - width: 96, - buttonHeight: ButtonHeight.xxs, - onPressed: () async { - await showDialog( - context: context, - barrierColor: Colors.transparent, - builder: (context) { - return DesktopContactOptionsMenuPopup( - contactId: contact.id, - ); - }, - ); - }, - ), + if (widget.contactId != "default") + SecondaryButton( + label: "Options", + width: 96, + buttonHeight: ButtonHeight.xxs, + onPressed: () async { + await showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return DesktopContactOptionsMenuPopup( + contactId: contact.id, + ); + }, + ); + }, + ), ], ), const SizedBox( @@ -453,132 +465,238 @@ class _DesktopContactOptionsMenuPopupState ), ), ), - const SizedBox( - height: 2, - ), - MouseRegion( - onEnter: (_) { - setState(() { - hoveredOnPencil = true; - }); - }, - onExit: (_) { - setState(() { - hoveredOnPencil = false; - }); - }, - child: RawMaterialButton( - hoverColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, - ), - ), - onPressed: () { - print("should go to edit"); + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnPencil = true; + }); }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 16, + onExit: (_) { + setState(() { + hoveredOnPencil = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 24, - height: 22, - color: hoveredOnPencil - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, - ), - const SizedBox( - width: 12, - ), - Text( - "Edit contact", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + onPressed: () { + print("should go to edit"); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 24, + height: 22, + color: hoveredOnPencil + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, ), - ) - ], + const SizedBox( + width: 12, + ), + Text( + "Edit contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), ), ), ), - ), - const SizedBox( - height: 2, - ), - MouseRegion( - onEnter: (_) { - setState(() { - hoveredOnTrash = true; - }); - }, - onExit: (_) { - setState(() { - hoveredOnTrash = false; - }); - }, - child: RawMaterialButton( - hoverColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, - ), - ), - onPressed: () { - print("should delete contact"); + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnTrash = true; + }); }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 16, + onExit: (_) { + setState(() { + hoveredOnTrash = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.trash, - width: 24, - height: 22, - color: hoveredOnTrash - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, - ), - const SizedBox( - width: 12, - ), - Text( - "Delete contact", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + onPressed: () { + final contact = ref + .read(addressBookServiceProvider) + .getContactById(widget.contactId); + + // pop context menu + Navigator.of(context).pop(); + + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Delete ${contact.name}?", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const Spacer( + flex: 1, + ), + Text( + "Contact will be deleted permanently!", + style: STextStyles.desktopTextSmall( + context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: + Navigator.of(context).pop, + buttonHeight: ButtonHeight.l, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Consumer( + builder: (context, ref, __) => + PrimaryButton( + label: "Delete", + buttonHeight: + ButtonHeight.l, + onPressed: () { + ref + .read( + addressBookServiceProvider) + .removeContact( + contact.id); + Navigator.of(context) + .pop(); + showFloatingFlushBar( + type: FlushBarType + .success, + message: + "${contact.name} deleted", + context: context, + ); + }, + ), + ), + ), + ], + ) + ], + ), + ), + ), + ], ), - ) - ], + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.trash, + width: 24, + height: 22, + color: hoveredOnTrash + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Delete contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), ), ), ), - ), ], ), ), diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index b79f89662..dfa655f86 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -44,10 +45,16 @@ class _AddressBookCardState extends ConsumerState { @override Widget build(BuildContext context) { - // final isTiny = SizingUtilities.isTinyWidth(context); + // provider hack to prevent trying to update widget with deleted contact + Contact? _contact; + try { + _contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(contactId))); + } catch (_) { + return Container(); + } - final contact = ref.watch(addressBookServiceProvider - .select((value) => value.getContactById(contactId))); + final contact = _contact!; final List coins = []; for (var element in contact.addresses) { From 07f229f2a0089a8a566ea846fa1e1d00b0fcc181 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 09:19:48 -0600 Subject: [PATCH 120/225] refactor popup --- .../subwidgets/desktop_contact_details.dart | 358 +---------------- .../desktop_contact_options_menu_popup.dart | 366 ++++++++++++++++++ 2 files changed, 367 insertions(+), 357 deletions(-) create mode 100644 lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart index 96eed4835..62cd993af 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_details.dart @@ -3,9 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_address_card.dart'; +import 'package:stackwallet/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; @@ -16,8 +16,6 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; -import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -352,357 +350,3 @@ class _DesktopContactDetailsState extends ConsumerState { ); } } - -class DesktopContactOptionsMenuPopup extends ConsumerStatefulWidget { - const DesktopContactOptionsMenuPopup({Key? key, required this.contactId}) - : super(key: key); - - final String contactId; - - @override - ConsumerState createState() => - _DesktopContactOptionsMenuPopupState(); -} - -class _DesktopContactOptionsMenuPopupState - extends ConsumerState { - bool hoveredOnStar = false; - bool hoveredOnPencil = false; - bool hoveredOnTrash = false; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Positioned( - top: 210, - left: MediaQuery.of(context).size.width - 280, - child: Container( - width: 270, - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.circular( - 20, - ), - boxShadow: [ - Theme.of(context).extension()!.standardBoxShadow, - ], - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - MouseRegion( - onEnter: (_) { - setState(() { - hoveredOnStar = true; - }); - }, - onExit: (_) { - setState(() { - hoveredOnStar = false; - }); - }, - child: RawMaterialButton( - hoverColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, - ), - ), - onPressed: () { - final contact = - ref.read(addressBookServiceProvider).getContactById( - widget.contactId, - ); - ref.read(addressBookServiceProvider).editContact( - contact.copyWith( - isFavorite: !contact.isFavorite, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 16, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.star, - width: 24, - height: 22, - color: hoveredOnStar - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, - ), - const SizedBox( - width: 12, - ), - Text( - ref.watch(addressBookServiceProvider.select( - (value) => value - .getContactById(widget.contactId) - .isFavorite)) - ? "Remove from favorites" - : "Add to favorites", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ) - ], - ), - ), - ), - ), - if (widget.contactId != "default") - const SizedBox( - height: 2, - ), - if (widget.contactId != "default") - MouseRegion( - onEnter: (_) { - setState(() { - hoveredOnPencil = true; - }); - }, - onExit: (_) { - setState(() { - hoveredOnPencil = false; - }); - }, - child: RawMaterialButton( - hoverColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, - ), - ), - onPressed: () { - print("should go to edit"); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 16, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 24, - height: 22, - color: hoveredOnPencil - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, - ), - const SizedBox( - width: 12, - ), - Text( - "Edit contact", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ) - ], - ), - ), - ), - ), - if (widget.contactId != "default") - const SizedBox( - height: 2, - ), - if (widget.contactId != "default") - MouseRegion( - onEnter: (_) { - setState(() { - hoveredOnTrash = true; - }); - }, - onExit: (_) { - setState(() { - hoveredOnTrash = false; - }); - }, - child: RawMaterialButton( - hoverColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 1000, - ), - ), - onPressed: () { - final contact = ref - .read(addressBookServiceProvider) - .getContactById(widget.contactId); - - // pop context menu - Navigator.of(context).pop(); - - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => DesktopDialog( - maxWidth: 500, - maxHeight: 300, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Delete ${contact.name}?", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Spacer( - flex: 1, - ), - Text( - "Contact will be deleted permanently!", - style: STextStyles.desktopTextSmall( - context), - ), - const Spacer( - flex: 2, - ), - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - onPressed: - Navigator.of(context).pop, - buttonHeight: ButtonHeight.l, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Consumer( - builder: (context, ref, __) => - PrimaryButton( - label: "Delete", - buttonHeight: - ButtonHeight.l, - onPressed: () { - ref - .read( - addressBookServiceProvider) - .removeContact( - contact.id); - Navigator.of(context) - .pop(); - showFloatingFlushBar( - type: FlushBarType - .success, - message: - "${contact.name} deleted", - context: context, - ); - }, - ), - ), - ), - ], - ) - ], - ), - ), - ), - ], - ), - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 16, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.trash, - width: 24, - height: 22, - color: hoveredOnTrash - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, - ), - const SizedBox( - width: 12, - ), - Text( - "Delete contact", - style: STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ) - ], - ), - ), - ), - ), - ], - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart new file mode 100644 index 000000000..88bd6abf0 --- /dev/null +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart @@ -0,0 +1,366 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class DesktopContactOptionsMenuPopup extends ConsumerStatefulWidget { + const DesktopContactOptionsMenuPopup({Key? key, required this.contactId}) + : super(key: key); + + final String contactId; + + @override + ConsumerState createState() => + _DesktopContactOptionsMenuPopupState(); +} + +class _DesktopContactOptionsMenuPopupState + extends ConsumerState { + bool hoveredOnStar = false; + bool hoveredOnPencil = false; + bool hoveredOnTrash = false; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 210, + left: MediaQuery.of(context).size.width - 280, + child: Container( + width: 270, + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + 20, + ), + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnStar = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnStar = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + final contact = + ref.read(addressBookServiceProvider).getContactById( + widget.contactId, + ); + ref.read(addressBookServiceProvider).editContact( + contact.copyWith( + isFavorite: !contact.isFavorite, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.star, + width: 24, + height: 22, + color: hoveredOnStar + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + ref.watch(addressBookServiceProvider.select( + (value) => value + .getContactById(widget.contactId) + .isFavorite)) + ? "Remove from favorites" + : "Add to favorites", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnPencil = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnPencil = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + print("should go to edit"); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 24, + height: 22, + color: hoveredOnPencil + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Edit contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + if (widget.contactId != "default") + const SizedBox( + height: 2, + ), + if (widget.contactId != "default") + MouseRegion( + onEnter: (_) { + setState(() { + hoveredOnTrash = true; + }); + }, + onExit: (_) { + setState(() { + hoveredOnTrash = false; + }); + }, + child: RawMaterialButton( + hoverColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + onPressed: () { + final contact = ref + .read(addressBookServiceProvider) + .getContactById(widget.contactId); + + // pop context menu + Navigator.of(context).pop(); + + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Delete ${contact.name}?", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const Spacer( + flex: 1, + ), + Text( + "Contact will be deleted permanently!", + style: STextStyles.desktopTextSmall( + context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: + Navigator.of(context).pop, + buttonHeight: ButtonHeight.l, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Consumer( + builder: (context, ref, __) => + PrimaryButton( + label: "Delete", + buttonHeight: + ButtonHeight.l, + onPressed: () { + ref + .read( + addressBookServiceProvider) + .removeContact( + contact.id); + Navigator.of(context) + .pop(); + showFloatingFlushBar( + type: FlushBarType + .success, + message: + "${contact.name} deleted", + context: context, + ); + }, + ), + ), + ), + ], + ) + ], + ), + ), + ), + ], + ), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 16, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.trash, + width: 24, + height: 22, + color: hoveredOnTrash + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + const SizedBox( + width: 12, + ), + Text( + "Delete contact", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} From d4b7ec0f174101e406796f4a2bc6162a0c9f0f58 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 09:54:26 -0600 Subject: [PATCH 121/225] desktop edit contact --- .../edit_contact_name_emoji_view.dart | 576 ++++++++++-------- .../desktop_contact_options_menu_popup.dart | 252 ++++---- 2 files changed, 462 insertions(+), 366 deletions(-) diff --git a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart index fff01eee3..a9b264b3c 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,14 +9,17 @@ import 'package:stackwallet/utilities/assets.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class EditContactNameEmojiView extends ConsumerStatefulWidget { const EditContactNameEmojiView({ Key? key, @@ -69,268 +74,323 @@ class _EditContactNameEmojiViewState final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Edit contact", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - GestureDetector( - onTap: () { - if (_selectedEmoji != null) { - setState(() { - _selectedEmoji = null; - }); - return; - } - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => const EmojiSelectSheet(), - ).then((value) { - if (value is Emoji) { - setState(() { - _selectedEmoji = value; - }); - } - }); - }, - child: SizedBox( - height: 48, - width: 48, - child: Stack( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - ), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, - ) - : Text( - _selectedEmoji!.char, - style: STextStyles.pageTitleH1( - context), - ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Container( - height: 14, - width: 14, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - color: Theme.of(context) - .extension()! - .accentColorDark), - child: Center( - child: _selectedEmoji == null - ? SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension()! - .textWhite, - width: 12, - height: 12, - ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension()! - .textWhite, - width: 8, - height: 8, - ), - ), - ), - ) - ], - ), - ), - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: nameController, - focusNode: nameFocusNode, - style: STextStyles.field(context), - onChanged: (_) => setState(() {}), - decoration: standardInputDecoration( - "Enter contact name", - nameFocusNode, - context, - ).copyWith( - suffixIcon: nameController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - nameController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const Spacer(), - const SizedBox( - height: 16, - ), - Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Builder( - builder: (context) { - bool shouldEnableSave = - nameController.text.isNotEmpty; + final isDesktop = Util.isDesktop; + final double emojiSize = isDesktop ? 56 : 48; - return TextButton( - style: shouldEnableSave - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor( - context), - onPressed: shouldEnableSave - ? () async { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75), - ); - } - final editedContact = - contact.copyWith( - shouldCopyEmojiWithNull: true, - name: nameController.text, - emojiChar: _selectedEmoji == null - ? null - : _selectedEmoji!.char, - ); - ref - .read( - addressBookServiceProvider) - .editContact( - editedContact, - ); - if (mounted) { - Navigator.of(context).pop(); - } - } - : null, - child: Text( - "Save", - style: STextStyles.button(context), - ), - ); - }, - ), - ), - ], - ) - ], + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit contact", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, ), ), ), ), + ); + }, + ), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + if (_selectedEmoji != null) { + setState(() { + _selectedEmoji = null; + }); + return; + } + if (isDesktop) { + showDialog( + barrierColor: Colors.transparent, + context: context, + builder: (context) { + return const DesktopDialog( + maxHeight: 700, + maxWidth: 600, + child: Padding( + padding: EdgeInsets.only( + left: 32, + right: 20, + top: 32, + bottom: 32, + ), + child: EmojiSelectSheet(), + ), + ); + }).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + } else { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => const EmojiSelectSheet(), + ).then((value) { + if (value is Emoji) { + setState(() { + _selectedEmoji = value; + }); + } + }); + } + }, + child: SizedBox( + height: emojiSize, + width: emojiSize, + child: Stack( + children: [ + Container( + height: emojiSize, + width: emojiSize, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(emojiSize / 2), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + ), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.user, + height: emojiSize / 2, + width: emojiSize / 2, + ) + : Text( + _selectedEmoji!.char, + style: isDesktop + ? STextStyles.desktopH3(context) + : STextStyles.pageTitleH1(context), + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Container( + height: 14, + width: 14, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + color: Theme.of(context) + .extension()! + .accentColorDark), + child: Center( + child: _selectedEmoji == null + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .textWhite, + width: 12, + height: 12, + ) + : SvgPicture.asset( + Assets.svg.thickX, + color: Theme.of(context) + .extension()! + .textWhite, + width: 8, + height: 8, + ), + ), + ), + ) + ], + ), + ), + ), + if (isDesktop) + const SizedBox( + width: 8, + ), + if (isDesktop) + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: nameController, + focusNode: nameFocusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Enter contact name", + nameFocusNode, + context, + ).copyWith( + suffixIcon: nameController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + nameController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + ], + ), + if (!isDesktop) + const SizedBox( + height: 8, ), - ); - }, + if (!isDesktop) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: nameController, + focusNode: nameFocusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Enter contact name", + nameFocusNode, + context, + ).copyWith( + suffixIcon: nameController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + nameController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const Spacer(), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + if (!isDesktop && FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + enabled: nameController.text.isNotEmpty, + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + if (!isDesktop && FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75), + ); + } + final editedContact = contact.copyWith( + shouldCopyEmojiWithNull: true, + name: nameController.text, + emojiChar: + _selectedEmoji == null ? null : _selectedEmoji!.char, + ); + unawaited( + ref.read(addressBookServiceProvider).editContact( + editedContact, + ), + ); + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + ], + ) + ], ), ); } diff --git a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart index 88bd6abf0..690d1be98 100644 --- a/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart +++ b/lib/pages_desktop_specific/home/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -28,6 +29,147 @@ class _DesktopContactOptionsMenuPopupState bool hoveredOnPencil = false; bool hoveredOnTrash = false; + void editContact() { + // pop context menu + Navigator.of(context).pop(); + + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 580, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Edit contact", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + right: 32, + bottom: 32, + ), + child: EditContactNameEmojiView( + contactId: widget.contactId, + ), + ), + ), + ], + ), + ), + ); + } + + void attemptDeleteContact() { + final contact = + ref.read(addressBookServiceProvider).getContactById(widget.contactId); + + // pop context menu + Navigator.of(context).pop(); + + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => DesktopDialog( + maxWidth: 500, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Delete ${contact.name}?", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Spacer( + flex: 1, + ), + Text( + "Contact will be deleted permanently!", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + buttonHeight: ButtonHeight.l, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Consumer( + builder: (context, ref, __) => PrimaryButton( + label: "Delete", + buttonHeight: ButtonHeight.l, + onPressed: () { + ref + .read(addressBookServiceProvider) + .removeContact(contact.id); + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.success, + message: "${contact.name} deleted", + context: context, + ); + }, + ), + ), + ), + ], + ) + ], + ), + ), + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { return Stack( @@ -148,9 +290,7 @@ class _DesktopContactOptionsMenuPopupState 1000, ), ), - onPressed: () { - print("should go to edit"); - }, + onPressed: editContact, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 25, @@ -213,111 +353,7 @@ class _DesktopContactOptionsMenuPopupState 1000, ), ), - onPressed: () { - final contact = ref - .read(addressBookServiceProvider) - .getContactById(widget.contactId); - - // pop context menu - Navigator.of(context).pop(); - - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => DesktopDialog( - maxWidth: 500, - maxHeight: 300, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Delete ${contact.name}?", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Spacer( - flex: 1, - ), - Text( - "Contact will be deleted permanently!", - style: STextStyles.desktopTextSmall( - context), - ), - const Spacer( - flex: 2, - ), - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - onPressed: - Navigator.of(context).pop, - buttonHeight: ButtonHeight.l, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Consumer( - builder: (context, ref, __) => - PrimaryButton( - label: "Delete", - buttonHeight: - ButtonHeight.l, - onPressed: () { - ref - .read( - addressBookServiceProvider) - .removeContact( - contact.id); - Navigator.of(context) - .pop(); - showFloatingFlushBar( - type: FlushBarType - .success, - message: - "${contact.name} deleted", - context: context, - ); - }, - ), - ), - ), - ], - ) - ], - ), - ), - ), - ], - ), - ), - ); - }, + onPressed: attemptDeleteContact, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 25, From 4d17c1db5f732acb00b1726a530125318889f706 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 10:22:34 -0600 Subject: [PATCH 122/225] addressbook filter coins list fix --- lib/pages/address_book_views/address_book_view.dart | 8 ++++---- .../subviews/address_book_filter_view.dart | 2 +- .../home/address_book_view/desktop_address_book.dart | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index cdc9fb5b7..c87906870 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -51,8 +51,7 @@ class _AddressBookViewState extends ConsumerState { ref.refresh(addressBookFilterProvider); if (widget.coin == null) { - List coins = - Coin.values.where((e) => !(e == Coin.epicCash)).toList(); + List coins = Coin.values.toList(); coins.remove(Coin.firoTestNet); bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins; @@ -60,8 +59,9 @@ class _AddressBookViewState extends ConsumerState { if (showTestNet) { ref.read(addressBookFilterProvider).addAll(coins, false); } else { - ref.read(addressBookFilterProvider).addAll( - coins.getRange(0, coins.length - kTestNetCoinCount + 1), false); + ref + .read(addressBookFilterProvider) + .addAll(coins.getRange(0, coins.length - kTestNetCoinCount), false); } } else { ref.read(addressBookFilterProvider).add(widget.coin!, false); diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index c129251d5..55c3d47ac 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -38,7 +38,7 @@ class _AddressBookFilterViewState extends ConsumerState { } else { _coins = coins .toList(growable: false) - .getRange(0, coins.length - kTestNetCoinCount + 1) + .getRange(0, coins.length - kTestNetCoinCount) .toList(growable: false); } super.initState(); diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart index f028a3424..e5432081c 100644 --- a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart @@ -84,7 +84,7 @@ class _DesktopAddressBook extends ConsumerState { ref.refresh(addressBookFilterProvider); // if (widget.coin == null) { - List coins = Coin.values.where((e) => !(e == Coin.epicCash)).toList(); + List coins = Coin.values.toList(); coins.remove(Coin.firoTestNet); bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins; @@ -92,8 +92,9 @@ class _DesktopAddressBook extends ConsumerState { if (showTestNet) { ref.read(addressBookFilterProvider).addAll(coins, false); } else { - ref.read(addressBookFilterProvider).addAll( - coins.getRange(0, coins.length - kTestNetCoinCount + 1), false); + ref + .read(addressBookFilterProvider) + .addAll(coins.getRange(0, coins.length - kTestNetCoinCount), false); } // } else { // ref.read(addressBookFilterProvider).add(widget.coin!, false); From 8207474d0973387b9fa7d32d4eb510132b34cc72 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 09:30:21 -0700 Subject: [PATCH 123/225] ocean breeze selector + functionality added --- assets/svg/ocean-breeze-theme.svg | 28 +++++ lib/main.dart | 2 +- .../settings_menu/appearance_settings.dart | 105 +++++++++++++++++- lib/utilities/assets.dart | 1 + pubspec.yaml | 1 + 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 assets/svg/ocean-breeze-theme.svg diff --git a/assets/svg/ocean-breeze-theme.svg b/assets/svg/ocean-breeze-theme.svg new file mode 100644 index 000000000..0deb96ec8 --- /dev/null +++ b/assets/svg/ocean-breeze-theme.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index 2d1bab160..66b3bb974 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,7 +77,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1200, 1100)); + setWindowMinSize(const Size(1220, 1100)); setWindowMaxSize(Size.infinite); } diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart index 7a9ed557f..bfd5f3b61 100644 --- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; +import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -291,7 +292,109 @@ class _ThemeToggle extends ConsumerState { ), ), const SizedBox( - width: 20, + width: 10, + ), + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.oceanBreeze.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + OceanBreezeColors(), + ); + + setState(() { + _selectedTheme = "oceanBreeze"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: _selectedTheme == "oceanBreeze" + ? Theme.of(context) + .extension()! + .infoItemIcons + : Theme.of(context).extension()!.popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: SvgPicture.asset( + Assets.svg.themeOcean, + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "oceanBreeze", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "oceanBreeze") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.oceanBreeze.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + OceanBreezeColors(), + ); + + setState(() { + _selectedTheme = "oceanBreeze"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Ocean Breeze", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + width: 10, ), MaterialButton( splashColor: Colors.transparent, diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 38d720969..d7d2ebc6f 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -59,6 +59,7 @@ class _SVG { String txExchangeFailed(BuildContext context) => "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; + String get themeOcean => "assets/svg/ocean-breeze-theme.svg"; String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; String get framedGear => "assets/svg/framed-gear.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index bba5f6ed2..94670af1a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -316,6 +316,7 @@ flutter: - assets/svg/arrow-down.svg - assets/svg/plus-circle.svg - assets/svg/configuration.svg + - assets/svg/ocean-breeze-theme.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Litecoin.svg From 9508afbd5b84823a264e3c091cab9768292e9e01 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 12:26:27 -0600 Subject: [PATCH 124/225] add ocean breeze specific assets --- assets/svg/{dark => }/dark-theme.svg | 0 assets/svg/{light => }/light-mode.svg | 0 assets/svg/oceanBreeze/bell-new.svg | 5 ++ assets/svg/oceanBreeze/buy-coins-icon.svg | 18 +++++ assets/svg/oceanBreeze/exchange-2.svg | 4 + assets/svg/oceanBreeze/stack-icon1.svg | 5 ++ .../oceanBreeze/tx-exchange-icon-failed.svg | 7 ++ .../oceanBreeze/tx-exchange-icon-pending.svg | 6 ++ assets/svg/oceanBreeze/tx-exchange-icon.svg | 4 + .../oceanBreeze/tx-icon-receive-failed.svg | 7 ++ .../oceanBreeze/tx-icon-receive-pending.svg | 5 ++ assets/svg/oceanBreeze/tx-icon-receive.svg | 4 + .../svg/oceanBreeze/tx-icon-send-failed.svg | 7 ++ .../svg/oceanBreeze/tx-icon-send-pending.svg | 6 ++ assets/svg/oceanBreeze/tx-icon-send.svg | 4 + lib/utilities/assets.dart | 5 +- pubspec.yaml | 77 ++++++++++++------- 17 files changed, 135 insertions(+), 29 deletions(-) rename assets/svg/{dark => }/dark-theme.svg (100%) rename assets/svg/{light => }/light-mode.svg (100%) create mode 100644 assets/svg/oceanBreeze/bell-new.svg create mode 100644 assets/svg/oceanBreeze/buy-coins-icon.svg create mode 100644 assets/svg/oceanBreeze/exchange-2.svg create mode 100644 assets/svg/oceanBreeze/stack-icon1.svg create mode 100644 assets/svg/oceanBreeze/tx-exchange-icon-failed.svg create mode 100644 assets/svg/oceanBreeze/tx-exchange-icon-pending.svg create mode 100644 assets/svg/oceanBreeze/tx-exchange-icon.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-receive-failed.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-receive-pending.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-receive.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-send-failed.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-send-pending.svg create mode 100644 assets/svg/oceanBreeze/tx-icon-send.svg diff --git a/assets/svg/dark/dark-theme.svg b/assets/svg/dark-theme.svg similarity index 100% rename from assets/svg/dark/dark-theme.svg rename to assets/svg/dark-theme.svg diff --git a/assets/svg/light/light-mode.svg b/assets/svg/light-mode.svg similarity index 100% rename from assets/svg/light/light-mode.svg rename to assets/svg/light-mode.svg diff --git a/assets/svg/oceanBreeze/bell-new.svg b/assets/svg/oceanBreeze/bell-new.svg new file mode 100644 index 000000000..8cef32715 --- /dev/null +++ b/assets/svg/oceanBreeze/bell-new.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/oceanBreeze/buy-coins-icon.svg b/assets/svg/oceanBreeze/buy-coins-icon.svg new file mode 100644 index 000000000..d9613bccb --- /dev/null +++ b/assets/svg/oceanBreeze/buy-coins-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/oceanBreeze/exchange-2.svg b/assets/svg/oceanBreeze/exchange-2.svg new file mode 100644 index 000000000..7baeaf87f --- /dev/null +++ b/assets/svg/oceanBreeze/exchange-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/oceanBreeze/stack-icon1.svg b/assets/svg/oceanBreeze/stack-icon1.svg new file mode 100644 index 000000000..f316012d7 --- /dev/null +++ b/assets/svg/oceanBreeze/stack-icon1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg b/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg new file mode 100644 index 000000000..a54836bba --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg b/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg new file mode 100644 index 000000000..5f9aa4256 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/oceanBreeze/tx-exchange-icon.svg b/assets/svg/oceanBreeze/tx-exchange-icon.svg new file mode 100644 index 000000000..fcd3ef9dc --- /dev/null +++ b/assets/svg/oceanBreeze/tx-exchange-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-receive-failed.svg b/assets/svg/oceanBreeze/tx-icon-receive-failed.svg new file mode 100644 index 000000000..189bd15c9 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive-failed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-receive-pending.svg b/assets/svg/oceanBreeze/tx-icon-receive-pending.svg new file mode 100644 index 000000000..64ea8da3d --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive-pending.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-receive.svg b/assets/svg/oceanBreeze/tx-icon-receive.svg new file mode 100644 index 000000000..1076d8d57 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-send-failed.svg b/assets/svg/oceanBreeze/tx-icon-send-failed.svg new file mode 100644 index 000000000..9751b61e8 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send-failed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-send-pending.svg b/assets/svg/oceanBreeze/tx-icon-send-pending.svg new file mode 100644 index 000000000..e4ec777e3 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send-pending.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/oceanBreeze/tx-icon-send.svg b/assets/svg/oceanBreeze/tx-icon-send.svg new file mode 100644 index 000000000..ee32aa6b4 --- /dev/null +++ b/assets/svg/oceanBreeze/tx-icon-send.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index d7d2ebc6f..c423ec491 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -60,12 +60,13 @@ class _SVG { "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; String get themeOcean => "assets/svg/ocean-breeze-theme.svg"; + String get themeLight => "assets/svg/light-mode.svg"; + String get themeDark => "assets/svg/dark-theme.svg"; + String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; - String get themeLight => "assets/svg/light/light-mode.svg"; - String get themeDark => "assets/svg/dark/dark-theme.svg"; String get circleNode => "assets/svg/node-circle.svg"; String get circleSun => "assets/svg/sun-circle.svg"; String get circleArrowRotate => "assets/svg/rotate-circle.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 94670af1a..6a721c5c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -205,9 +205,6 @@ flutter: - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg - - assets/svg/light/bell-new.svg - - assets/svg/dark/bell-new.svg - - assets/svg/stack-icon1.svg - assets/svg/arrow-left-fa.svg - assets/svg/copy-fa.svg - assets/svg/star.svg @@ -220,10 +217,7 @@ flutter: - assets/svg/bars.svg - assets/svg/filter.svg - assets/svg/pending.svg - - assets/svg/dark/exchange-2.svg - - assets/svg/light/exchange-2.svg - assets/svg/signal-stream.svg - - assets/svg/buy-coins-icon.svg - assets/svg/Ellipse-43.svg - assets/svg/Ellipse-42.svg - assets/svg/arrow-rotate.svg @@ -265,25 +259,7 @@ flutter: - assets/svg/ellipsis-vertical1.svg - assets/svg/dice-alt.svg - assets/svg/circle-arrow-up-right2.svg - - assets/svg/dark/tx-exchange-icon.svg - - assets/svg/light/tx-exchange-icon.svg - - assets/svg/dark/tx-exchange-icon-pending.svg - - assets/svg/light/tx-exchange-icon-pending.svg - - assets/svg/dark/tx-exchange-icon-failed.svg - - assets/svg/light/tx-exchange-icon-failed.svg - assets/svg/loader.svg - - assets/svg/dark/tx-icon-send.svg - - assets/svg/light/tx-icon-send.svg - - assets/svg/dark/tx-icon-send-pending.svg - - assets/svg/light/tx-icon-send-pending.svg - - assets/svg/dark/tx-icon-send-failed.svg - - assets/svg/light/tx-icon-send-failed.svg - - assets/svg/dark/tx-icon-receive.svg - - assets/svg/light/tx-icon-receive.svg - - assets/svg/dark/tx-icon-receive-pending.svg - - assets/svg/light/tx-icon-receive-pending.svg - - assets/svg/dark/tx-icon-receive-failed.svg - - assets/svg/light/tx-icon-receive-failed.svg - assets/svg/add-backup.svg - assets/svg/auto-backup.svg - assets/svg/restore-backup.svg @@ -305,8 +281,6 @@ flutter: - assets/svg/rotate-circle.svg - assets/svg/sun-circle.svg - assets/svg/node-circle.svg - - assets/svg/dark/dark-theme.svg - - assets/svg/light/light-mode.svg - assets/svg/address-book-desktop.svg - assets/svg/about-desktop.svg - assets/svg/exchange-desktop.svg @@ -316,7 +290,6 @@ flutter: - assets/svg/arrow-down.svg - assets/svg/plus-circle.svg - assets/svg/configuration.svg - - assets/svg/ocean-breeze-theme.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Litecoin.svg @@ -348,6 +321,56 @@ flutter: - assets/svg/exchange_icons/change_now_logo_1.svg - assets/svg/exchange_icons/simpleswap-icon.svg + # theme selectors + - assets/svg/dark-theme.svg + - assets/svg/light-mode.svg + - assets/svg/ocean-breeze-theme.svg + + # light theme specific + - assets/svg/light/tx-exchange-icon.svg + - assets/svg/light/tx-exchange-icon-pending.svg + - assets/svg/light/tx-exchange-icon-failed.svg + - assets/svg/light/tx-icon-send.svg + - assets/svg/light/tx-icon-send-pending.svg + - assets/svg/light/tx-icon-send-failed.svg + - assets/svg/light/tx-icon-receive.svg + - assets/svg/light/tx-icon-receive-pending.svg + - assets/svg/light/tx-icon-receive-failed.svg + - assets/svg/light/exchange-2.svg + - assets/svg/light/bell-new.svg + - assets/svg/light/stack-icon1.svg + - assets/svg/light/buy-coins-icon.svg + + # dark theme specific + - assets/svg/dark/tx-exchange-icon.svg + - assets/svg/dark/tx-exchange-icon-pending.svg + - assets/svg/dark/tx-exchange-icon-failed.svg + - assets/svg/dark/tx-icon-send.svg + - assets/svg/dark/tx-icon-send-pending.svg + - assets/svg/dark/tx-icon-send-failed.svg + - assets/svg/dark/tx-icon-receive.svg + - assets/svg/dark/tx-icon-receive-pending.svg + - assets/svg/dark/tx-icon-receive-failed.svg + - assets/svg/dark/exchange-2.svg + - assets/svg/dark/bell-new.svg + - assets/svg/dark/stack-icon1.svg + - assets/svg/dark/buy-coins-icon.svg + + # light theme specific + - assets/svg/oceanBreeze/tx-exchange-icon.svg + - assets/svg/oceanBreeze/tx-exchange-icon-pending.svg + - assets/svg/oceanBreeze/tx-exchange-icon-failed.svg + - assets/svg/oceanBreeze/tx-icon-send.svg + - assets/svg/oceanBreeze/tx-icon-send-pending.svg + - assets/svg/oceanBreeze/tx-icon-send-failed.svg + - assets/svg/oceanBreeze/tx-icon-receive.svg + - assets/svg/oceanBreeze/tx-icon-receive-pending.svg + - assets/svg/oceanBreeze/tx-icon-receive-failed.svg + - assets/svg/oceanBreeze/exchange-2.svg + - assets/svg/oceanBreeze/bell-new.svg + - assets/svg/oceanBreeze/stack-icon1.svg + - assets/svg/oceanBreeze/buy-coins-icon.svg + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see From 792b91b7c4d27c7212b251150ad4fafef39b96a8 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 11:26:27 -0700 Subject: [PATCH 125/225] syncing pref options show on button press + shows card w current syncing prefs --- .../syncing_preferences_settings.dart | 116 ++++++++++++++---- 1 file changed, 91 insertions(+), 25 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart index 815e506db..720d77b8b 100644 --- a/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/syncing_preferences_settings.dart @@ -3,9 +3,13 @@ import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class SyncingPreferencesSettings extends ConsumerStatefulWidget { @@ -20,6 +24,19 @@ class SyncingPreferencesSettings extends ConsumerStatefulWidget { class _SyncingPreferencesSettings extends ConsumerState { + String _currentTypeDescription(SyncingType type) { + switch (type) { + case SyncingType.currentWalletOnly: + return "Sync only currently open wallet"; + case SyncingType.selectedWalletsAtStartup: + return "Sync only selected wallets at startup"; + case SyncingType.allWalletsOnStartup: + return "Sync all wallets at startup"; + } + } + + late bool changePrefs = false; + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); @@ -34,13 +51,40 @@ class _SyncingPreferencesSettings child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - Assets.svg.circleArrowRotate, - width: 48, - height: 48, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleArrowRotate, + width: 48, + height: 48, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: RoundedContainer( + color: Theme.of(context) + .extension()! + .buttonBackSecondaryDisabled, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _currentTypeDescription(ref.watch( + prefsChangeNotifierProvider + .select((value) => value.syncType))), + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark2), + textAlign: TextAlign.left, + ), + ), + ), + ), + ], ), Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -67,28 +111,50 @@ class _SyncingPreferencesSettings ), ], ), - - ///TODO: ONLY SHOW SYNC OPTIONS ON BUTTON PRESS - Column( - children: const [ - SyncingOptionsView(), - ], - ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.all( - 10, - ), - child: PrimaryButton( - width: 210, - buttonHeight: ButtonHeight.m, - enabled: true, - label: "Change preferences", - onPressed: () {}, - ), - ), + padding: const EdgeInsets.all( + 10, + ), + child: changePrefs + ? SizedBox( + width: 512, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SyncingOptionsView(), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.m, + enabled: true, + label: "Save", + onPressed: () { + setState(() { + changePrefs = false; + }); + }, + ), + ], + ), + ) + : Column( + children: [ + const SizedBox(height: 10), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.m, + enabled: true, + label: "Change preferences", + onPressed: () { + setState(() { + changePrefs = true; + }); + }, + ), + ], + )), ], ), ], From 7ef31cbf87a80598478c2ef68a4ff98cbe3c130a Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 12:34:25 -0600 Subject: [PATCH 126/225] add back exchange menu option and adjust icon color --- .../home/desktop_menu.dart | 76 ++++++++++--------- lib/utilities/theme/ocean_breeze_colors.dart | 2 +- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index bdaa1d6ce..60a424a06 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -104,10 +104,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "My Stack", @@ -120,29 +120,33 @@ class _DesktopMenuState extends ConsumerState { const SizedBox( height: 2, ), - // DesktopMenuItem( - // icon: SvgPicture.asset( - // Assets.svg.exchangeDesktop, - // width: 20, - // height: 20, - // color: DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state - // ? Theme.of(context) - // .extension()! - // .textDark - // : Theme.of(context) - // .extension()! - // .textDark - // .withOpacity(0.8), - // ), - // label: "Exchange", - // value: DesktopMenuItemId.exchange, - // group: ref.watch(currentDesktopMenuItemProvider.state).state, - // onChanged: updateSelectedMenuItem, - // iconOnly: _width == minimizedWidth, - // ), - // const SizedBox( - // height: 2, - // ), + DesktopMenuItem( + icon: SvgPicture.asset( + Assets.svg.exchangeDesktop, + width: 20, + height: 20, + color: DesktopMenuItemId.exchange == + ref + .watch(currentDesktopMenuItemProvider.state) + .state + ? Theme.of(context) + .extension()! + .accentColorDark + : Theme.of(context) + .extension()! + .accentColorDark + .withOpacity(0.8), + ), + label: "Exchange", + value: DesktopMenuItemId.exchange, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, + onChanged: updateSelectedMenuItem, + iconOnly: _width == minimizedWidth, + ), + const SizedBox( + height: 2, + ), DesktopMenuItem( icon: SvgPicture.asset( Assets.svg.bell, @@ -154,10 +158,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Notifications", @@ -181,10 +185,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Address Book", @@ -208,10 +212,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Settings", @@ -235,10 +239,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Support", @@ -262,10 +266,10 @@ class _DesktopMenuState extends ConsumerState { .state ? Theme.of(context) .extension()! - .textDark + .accentColorDark : Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "About", @@ -283,7 +287,7 @@ class _DesktopMenuState extends ConsumerState { height: 20, color: Theme.of(context) .extension()! - .textDark + .accentColorDark .withOpacity(0.8), ), label: "Exit", diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart index ff2f4e85e..1eb06e068 100644 --- a/lib/utilities/theme/ocean_breeze_colors.dart +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -21,7 +21,7 @@ class OceanBreezeColors extends StackColorTheme { @override Color get accentColorOrange => const Color(0xFFFF985F); @override - Color get accentColorDark => const Color(0xFF232323); + Color get accentColorDark => const Color(0xFF227386); @override Color get shadow => const Color(0xFF388192); From ea143d9ffa901d30cf9f2631b0362fab80cfee71 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 12:45:42 -0600 Subject: [PATCH 127/225] basic desktop exchange layout --- .../desktop_exchange_view.dart | 89 +++++++++++++++ .../subwidgets/desktop_trade_history.dart | 103 ++++++++++++++++++ .../home/desktop_home_view.dart | 10 +- lib/route_generator.dart | 7 ++ 4 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart new file mode 100644 index 000000000..0f44eb59b --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart'; +import 'package:stackwallet/utilities/text_styles.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 DesktopExchangeView extends StatefulWidget { + const DesktopExchangeView({Key? key}) : super(key: key); + + static const String routeName = "/desktopExchange"; + + @override + State createState() => _DesktopExchangeViewState(); +} + +class _DesktopExchangeViewState extends State { + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Padding( + padding: const EdgeInsets.only( + left: 24, + ), + child: Text( + "Exchange", + style: STextStyles.desktopH3(context), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 16, + ), + const RoundedWhiteContainer( + padding: EdgeInsets.all(24), + child: ExchangeForm(), + ), + ], + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 16, + ), + const RoundedWhiteContainer( + padding: EdgeInsets.all(0), + child: DesktopTradeHistory(), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart new file mode 100644 index 000000000..40eeb8c1b --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -0,0 +1,103 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; +import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/trade_card.dart'; +import 'package:tuple/tuple.dart'; + +class DesktopTradeHistory extends ConsumerStatefulWidget { + const DesktopTradeHistory({Key? key}) : super(key: key); + + @override + ConsumerState createState() => + _DesktopTradeHistoryState(); +} + +class _DesktopTradeHistoryState extends ConsumerState { + @override + Widget build(BuildContext context) { + final trades = + ref.watch(tradesServiceProvider.select((value) => value.trades)); + + final tradeCount = trades.length; + final hasHistory = tradeCount > 0; + + if (hasHistory) { + return ListView.separated( + itemBuilder: (context, index) { + return TradeCard( + key: Key("tradeCard_${trades[index].uuid}"), + trade: trades[index], + onTap: () async { + final String tradeId = trades[index].tradeId; + + final lookup = ref.read(tradeSentFromStackLookupProvider).all; + + debugPrint("ALL: $lookup"); + + final String? txid = ref + .read(tradeSentFromStackLookupProvider) + .getTxidForTradeId(tradeId); + final List? walletIds = ref + .read(tradeSentFromStackLookupProvider) + .getWalletIdsForTradeId(tradeId); + + if (txid != null && walletIds != null && walletIds.isNotEmpty) { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletIds.first); + + debugPrint("name: ${manager.walletName}"); + + // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData + final txData = await manager.transactionData; + + final tx = txData.getAllTransactions()[txid]; + + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4( + tradeId, tx, walletIds.first, manager.walletName), + ), + ); + } + } else { + unawaited( + Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4(tradeId, null, walletIds?.first, null), + ), + ); + } + }, + ); + }, + separatorBuilder: (context, index) { + return Container( + height: 1, + color: Theme.of(context).extension()!.background, + ); + }, + itemCount: tradeCount, + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "Trades will appear here", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ); + } + } +} diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index b1c35f00b..9791cd867 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; @@ -29,10 +30,11 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, ), - // Container( - // // todo: exchange - // color: Colors.green, - // ), + DesktopMenuItemId.exchange: const Navigator( + key: Key("desktopExchangeHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopExchangeView.routeName, + ), DesktopMenuItemId.notifications: const Navigator( key: Key("desktopNotificationsHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, diff --git a/lib/route_generator.dart b/lib/route_generator.dart index d7865d013..8ccc923bc 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -85,6 +85,7 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; @@ -1019,6 +1020,12 @@ class RouteGenerator { builder: (_) => const DesktopNotificationsView(), settings: RouteSettings(name: settings.name)); + case DesktopExchangeView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopExchangeView(), + settings: RouteSettings(name: settings.name)); + case DesktopSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, From 83e2554b545e62a6cf4af323093a150dfe054812 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 12:42:12 -0700 Subject: [PATCH 128/225] mobile theme radio buttons --- .../appearance_settings_view.dart | 364 +++++++++++++++--- 1 file changed, 309 insertions(+), 55 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart index 3a1b842f6..d1e893802 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; +import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -18,6 +19,17 @@ class AppearanceSettingsView extends ConsumerWidget { static const String routeName = "/appearanceSettings"; + String chooseThemeType(ThemeType type) { + switch (type) { + case ThemeType.light: + return "Light theme"; + case ThemeType.oceanBreeze: + return "Ocean theme"; + case ThemeType.dark: + return "Dark theme"; + } + } + @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( @@ -100,68 +112,39 @@ class AppearanceSettingsView extends ConsumerWidget { height: 10, ), RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: null, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Enable dark mode", + "Choose Theme", style: STextStyles.titleBold12(context), textAlign: TextAlign.left, ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: (DB.instance.get( - boxName: DB.boxNameTheme, - key: "colorScheme") - as String?) == - "dark", - onValueChanged: (newValue) { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: (newValue - ? ThemeType.dark - : (newValue - ? ThemeType.light - : ThemeType - .oceanBreeze)) - .name, - ); - ref - .read(colorThemeProvider.state) - .state = - StackColors.fromStackColorTheme( - newValue - ? DarkColors() - : LightColors()); - }, - ), - ) + const Padding( + padding: EdgeInsets.all(10), + child: ThemeOptionsView(), + ), ], ), - ), - ); - }, + ], + ), + ), ), ), ], @@ -175,3 +158,274 @@ class AppearanceSettingsView extends ConsumerWidget { ); } } + +class ThemeOptionsView extends ConsumerStatefulWidget { + const ThemeOptionsView({ + Key? key, + }) : super(key: key); + + @override + ConsumerState createState() => _ThemeOptionsView(); +} + +class _ThemeOptionsView extends ConsumerState { + late String _selectedTheme; + + @override + void initState() { + _selectedTheme = + DB.instance.get(boxName: DB.boxNameTheme, key: "colorScheme") + as String? ?? + "light"; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.light.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + LightColors(), + ); + + setState(() { + _selectedTheme = "light"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox( + width: 10, + height: 10, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "light", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "light") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.light.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + LightColors(), + ); + + setState(() { + _selectedTheme = "light"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Light", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + height: 10, + ), + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.oceanBreeze.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + OceanBreezeColors(), + ); + + setState(() { + _selectedTheme = "oceanBreeze"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox( + width: 10, + height: 10, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "oceanBreeze", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "oceanBreeze") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.oceanBreeze.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + OceanBreezeColors(), + ); + + setState(() { + _selectedTheme = "oceanBreeze"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Ocean Breeze", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + height: 10, + ), + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.dark.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + DarkColors(), + ); + + setState(() { + _selectedTheme = "dark"; + }); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox( + width: 10, + height: 10, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: "dark", + groupValue: _selectedTheme, + onChanged: (newValue) { + if (newValue is String && newValue == "dark") { + DB.instance.put( + boxName: DB.boxNameTheme, + key: "colorScheme", + value: ThemeType.dark.name, + ); + ref.read(colorThemeProvider.state).state = + StackColors.fromStackColorTheme( + DarkColors(), + ); + + setState(() { + _selectedTheme = "dark"; + }); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + "Dark", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ); + } +} From 9956a497df081a54a67a90ae68f93ee5d6e32cc3 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 13:26:17 -0700 Subject: [PATCH 129/225] ocean breeze shadow color fix --- lib/utilities/theme/ocean_breeze_colors.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart index 1eb06e068..665eaa0c3 100644 --- a/lib/utilities/theme/ocean_breeze_colors.dart +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -24,7 +24,7 @@ class OceanBreezeColors extends StackColorTheme { Color get accentColorDark => const Color(0xFF227386); @override - Color get shadow => const Color(0xFF388192); + Color get shadow => const Color(0x0F2D3132); @override Color get textDark => const Color(0xFF232323); From e665926b1bc229e6abee16bdda38debe242191e4 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 14:59:53 -0600 Subject: [PATCH 130/225] firo anonymize navigation fix --- .../wallet_view/desktop_wallet_view.dart | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index d08864eee..85dde4aba 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -193,6 +193,7 @@ class _DesktopWalletViewState extends ConsumerState { if (publicBalance <= Decimal.zero) { shouldPop = true; if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context).popUntil( ModalRoute.withName(DesktopWalletView.routeName), ); @@ -211,6 +212,7 @@ class _DesktopWalletViewState extends ConsumerState { await firoWallet.anonymizeAllPublicFunds(); shouldPop = true; if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context).popUntil( ModalRoute.withName(DesktopWalletView.routeName), ); @@ -225,14 +227,53 @@ class _DesktopWalletViewState extends ConsumerState { } catch (e) { shouldPop = true; if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context).popUntil( ModalRoute.withName(DesktopWalletView.routeName), ); await showDialog( context: context, - builder: (_) => StackOkDialog( - title: "Anonymize all failed", - message: "Reason: $e", + builder: (_) => DesktopDialog( + maxWidth: 400, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Anonymize all failed", + style: STextStyles.desktopH3(context), + ), + const Spacer( + flex: 1, + ), + Text( + "Reason: $e", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: true).pop, + ), + ), + ], + ) + ], + ), + ), ), ); } From 9ba83f36eb480ff0dab6af8ca4a2ebed5d37642d Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 16:05:15 -0600 Subject: [PATCH 131/225] desktop exchange rate toggle style --- assets/svg/lock-open.svg | 3 + .../sub_widgets/rate_type_toggle.dart | 115 +++++++++++++----- pubspec.yaml | 1 + 3 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 assets/svg/lock-open.svg diff --git a/assets/svg/lock-open.svg b/assets/svg/lock-open.svg new file mode 100644 index 000000000..f2b00f341 --- /dev/null +++ b/assets/svg/lock-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index 31460c75f..9697710e8 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -7,8 +7,8 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; class RateTypeToggle extends ConsumerWidget { const RateTypeToggle({ @@ -21,12 +21,17 @@ class RateTypeToggle extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + final estimated = ref.watch(prefsChangeNotifierProvider .select((value) => value.exchangeRateType)) == ExchangeRateType.estimated; - return RoundedWhiteContainer( + return RoundedContainer( padding: const EdgeInsets.all(0), + color: isDesktop + ? Theme.of(context).extension()!.buttonBackSecondary + : Theme.of(context).extension()!.popupBG, child: Row( children: [ Expanded( @@ -39,6 +44,9 @@ class RateTypeToggle extends ConsumerWidget { } }, child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(17) + : const EdgeInsets.all(0), color: estimated ? Theme.of(context) .extension()! @@ -48,29 +56,50 @@ class RateTypeToggle extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( - Assets.svg.lock, + Assets.svg.lockOpen, width: 12, height: 14, - color: estimated - ? Theme.of(context).extension()!.textDark - : Theme.of(context) - .extension()! - .textSubtitle1, + color: isDesktop + ? estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, ), const SizedBox( width: 5, ), Text( "Estimate rate", - style: STextStyles.smallMed12(context).copyWith( - color: estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary, + ) + : STextStyles.smallMed12(context).copyWith( + color: estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), ), ], ), @@ -87,6 +116,9 @@ class RateTypeToggle extends ConsumerWidget { } }, child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(17) + : const EdgeInsets.all(0), color: !estimated ? Theme.of(context) .extension()! @@ -99,26 +131,47 @@ class RateTypeToggle extends ConsumerWidget { Assets.svg.lock, width: 12, height: 14, - color: !estimated - ? Theme.of(context).extension()!.textDark - : Theme.of(context) - .extension()! - .textSubtitle1, + color: isDesktop + ? !estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : !estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, ), const SizedBox( width: 5, ), Text( "Fixed rate", - style: STextStyles.smallMed12(context).copyWith( - color: !estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: !estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary, + ) + : STextStyles.smallMed12(context).copyWith( + color: !estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 6a721c5c1..e8f417586 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -228,6 +228,7 @@ flutter: - assets/svg/chevron-down.svg - assets/svg/chevron-up.svg - assets/svg/lock-keyhole.svg + - assets/svg/lock-open.svg - assets/svg/rotate-exclamation.svg - assets/svg/folder-down.svg - assets/svg/network-wired.svg From 16113fd1d52319b855cb6e38044da84f810feb7a Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 16:05:46 -0600 Subject: [PATCH 132/225] desktop exchange provider options dropdown style --- .../exchange_provider_options.dart | 706 ++++++++++-------- 1 file changed, 379 insertions(+), 327 deletions(-) diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 2113e199c..4dd768403 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -15,7 +15,9 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class ExchangeProviderOptions extends ConsumerWidget { @@ -38,353 +40,403 @@ class ExchangeProviderOptions extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final isDesktop = Util.isDesktop; return RoundedWhiteContainer( + padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), + borderColor: isDesktop + ? Theme.of(context).extension()!.background + : null, child: Column( children: [ - GestureDetector( - onTap: () { - if (ref.read(currentExchangeNameStateProvider.state).state != - ChangeNowExchange.exchangeName) { - ref.read(currentExchangeNameStateProvider.state).state = - ChangeNowExchange.exchangeName; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName( - ref.read(currentExchangeNameStateProvider.state).state); - } - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ChangeNowExchange.exchangeName, - groupValue: ref - .watch(currentExchangeNameStateProvider.state) - .state, - onChanged: (value) { - if (value is String) { - ref - .read(currentExchangeNameStateProvider.state) - .state = value; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (ref.read(currentExchangeNameStateProvider.state).state != + ChangeNowExchange.exchangeName) { + ref.read(currentExchangeNameStateProvider.state).state = + ChangeNowExchange.exchangeName; + ref.read(exchangeFormStateProvider).exchange = + Exchange.fromName(ref + .read(currentExchangeNameStateProvider.state) + .state); + } + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: ChangeNowExchange.exchangeName, + groupValue: ref + .watch(currentExchangeNameStateProvider.state) + .state, + onChanged: (value) { + if (value is String) { + ref .read(currentExchangeNameStateProvider.state) - .state); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - SvgPicture.asset( - Assets.exchange.changeNow, - width: 24, - height: 24, - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - ChangeNowExchange.exchangeName, - style: STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), + .state = value; + ref.read(exchangeFormStateProvider).exchange = + Exchange.fromName(ref + .read(currentExchangeNameStateProvider + .state) + .state); + } + }, ), - if (from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero) - FutureBuilder( - future: ChangeNowExchange().getEstimate( - from!, - to!, - reversed ? toAmount! : fromAmount!, - fixedRate, - reversed, + ), + const SizedBox( + width: 14, + ), + SvgPicture.asset( + Assets.exchange.changeNow, + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ChangeNowExchange.exchangeName, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Decimal rate; - if (estimate.reversed) { - rate = - (toAmount! / estimate.estimatedAmount) + if (from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero) + FutureBuilder( + future: ChangeNowExchange().getEstimate( + from!, + to!, + reversed ? toAmount! : fromAmount!, + fixedRate, + reversed, + ), + builder: (context, + AsyncSnapshot> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimate = snapshot.data?.value; + if (estimate != null) { + Decimal rate; + if (estimate.reversed) { + rate = (toAmount! / + estimate.estimatedAmount) .toDecimal( scaleOnInfinitePrecision: 12); + } else { + rate = (estimate.estimatedAmount / + fromAmount!) + .toDecimal( + scaleOnInfinitePrecision: 12); + } + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + decimalPlaces: to!.toUpperCase() == + Coin.monero.ticker + .toUpperCase() + ? Constants.decimalPlacesMonero + : Constants.decimalPlaces, + )} ${to!.toUpperCase()}", + style: + STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", + level: LogLevel.Warning, + ); + return Text( + "Failed to fetch rate", + style: + STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } } else { - rate = - (estimate.estimatedAmount / fromAmount!) - .toDecimal( - scaleOnInfinitePrecision: 12); - } - return Text( - "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - value: rate, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + return AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, ), - decimalPlaces: to!.toUpperCase() == - Coin.monero.ticker.toUpperCase() - ? Constants.decimalPlacesMonero - : Constants.decimalPlaces, - )} ${to!.toUpperCase()}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - if (!(from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), + ); + } + }, + ), + if (!(from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero)) + Text( + "n/a", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], ), - ], + ), ), ), ), - const SizedBox( - height: 16, - ), - GestureDetector( - onTap: () { - if (ref.read(currentExchangeNameStateProvider.state).state != - SimpleSwapExchange.exchangeName) { - ref.read(currentExchangeNameStateProvider.state).state = - SimpleSwapExchange.exchangeName; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName( - ref.read(currentExchangeNameStateProvider.state).state); - } - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: SimpleSwapExchange.exchangeName, - groupValue: ref - .watch(currentExchangeNameStateProvider.state) - .state, - onChanged: (value) { - if (value is String) { - ref - .read(currentExchangeNameStateProvider.state) - .state = value; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref + if (isDesktop) + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (ref.read(currentExchangeNameStateProvider.state).state != + SimpleSwapExchange.exchangeName) { + ref.read(currentExchangeNameStateProvider.state).state = + SimpleSwapExchange.exchangeName; + ref.read(exchangeFormStateProvider).exchange = + Exchange.fromName(ref + .read(currentExchangeNameStateProvider.state) + .state); + } + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: SimpleSwapExchange.exchangeName, + groupValue: ref + .watch(currentExchangeNameStateProvider.state) + .state, + onChanged: (value) { + if (value is String) { + ref .read(currentExchangeNameStateProvider.state) - .state); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - SvgPicture.asset( - Assets.exchange.simpleSwap, - width: 24, - height: 24, - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - SimpleSwapExchange.exchangeName, - style: STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), + .state = value; + ref.read(exchangeFormStateProvider).exchange = + Exchange.fromName(ref + .read(currentExchangeNameStateProvider + .state) + .state); + } + }, ), - if (from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero) - FutureBuilder( - future: SimpleSwapExchange().getEstimate( - from!, - to!, - // reversed ? toAmount! : fromAmount!, - fromAmount!, - fixedRate, - // reversed, - false, + ), + const SizedBox( + width: 14, + ), + SvgPicture.asset( + Assets.exchange.simpleSwap, + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + SimpleSwapExchange.exchangeName, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Decimal rate = (estimate.estimatedAmount / - fromAmount!) - .toDecimal(scaleOnInfinitePrecision: 12); + if (from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero) + FutureBuilder( + future: SimpleSwapExchange().getEstimate( + from!, + to!, + // reversed ? toAmount! : fromAmount!, + fromAmount!, + fixedRate, + // reversed, + false, + ), + builder: (context, + AsyncSnapshot> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimate = snapshot.data?.value; + if (estimate != null) { + Decimal rate = (estimate.estimatedAmount / + fromAmount!) + .toDecimal( + scaleOnInfinitePrecision: 12); - return Text( - "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - value: rate, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + decimalPlaces: to!.toUpperCase() == + Coin.monero.ticker + .toUpperCase() + ? Constants.decimalPlacesMonero + : Constants.decimalPlaces, + )} ${to!.toUpperCase()}", + style: + STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}", + level: LogLevel.Warning, + ); + return Text( + "Failed to fetch rate", + style: + STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, ), - decimalPlaces: to!.toUpperCase() == - Coin.monero.ticker.toUpperCase() - ? Constants.decimalPlacesMonero - : Constants.decimalPlaces, - )} ${to!.toUpperCase()}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - // if (!(from != null && - // to != null && - // (reversed - // ? toAmount != null && toAmount! > Decimal.zero - // : fromAmount != null && - // fromAmount! > Decimal.zero))) - if (!(from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), + ); + } + }, + ), + // if (!(from != null && + // to != null && + // (reversed + // ? toAmount != null && toAmount! > Decimal.zero + // : fromAmount != null && + // fromAmount! > Decimal.zero))) + if (!(from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero)) + Text( + "n/a", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], ), - ], + ), ), ), ), From 96453e90541ce78a9c7a2f6e57f81554fc1839c0 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 16:06:03 -0600 Subject: [PATCH 133/225] missing asset declaration --- lib/utilities/assets.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index c423ec491..6fbe61005 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -107,6 +107,7 @@ class _SVG { String get swap => "assets/svg/swap.svg"; String get downloadFolder => "assets/svg/folder-down.svg"; String get lock => "assets/svg/lock-keyhole.svg"; + String get lockOpen => "assets/svg/lock-open.svg"; String get network => "assets/svg/network-wired.svg"; String get networkWired => "assets/svg/network-wired-2.svg"; String get addressBook => "assets/svg/address-book.svg"; From 3ae38c582bdcd70c9ab68560ef832a6a7aad3fdf Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 16:06:29 -0600 Subject: [PATCH 134/225] desktop exchange form layout --- lib/pages/exchange_view/exchange_form.dart | 194 ++++++++++++++------- 1 file changed, 134 insertions(+), 60 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 7b04f90b3..5ece5aba8 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -27,9 +27,12 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:tuple/tuple.dart'; @@ -54,6 +57,7 @@ class _ExchangeFormState extends ConsumerState { late final TextEditingController _sendController; late final TextEditingController _receiveController; + final isDesktop = Util.isDesktop; final FocusNode _sendFocusNode = FocusNode(); final FocusNode _receiveFocusNode = FocusNode(); @@ -960,8 +964,8 @@ class _ExchangeFormState extends ConsumerState { color: Theme.of(context).extension()!.textDark3, ), ), - const SizedBox( - height: 4, + SizedBox( + height: isDesktop ? 10 : 4, ), TextFormField( style: STextStyles.smallMed14(context).copyWith( @@ -970,6 +974,8 @@ class _ExchangeFormState extends ConsumerState { focusNode: _sendFocusNode, controller: _sendController, textAlign: TextAlign.right, + enableSuggestions: false, + autocorrect: false, onTap: () { if (_sendController.text == "-") { _sendController.text = ""; @@ -1100,68 +1106,122 @@ class _ExchangeFormState extends ConsumerState { ), ), ), - const SizedBox( - height: 4, + + SizedBox( + height: isDesktop ? 10 : 4, ), - Stack( + if (ref + .watch( + exchangeFormStateProvider.select((value) => value.warning)) + .isNotEmpty && + !ref.watch( + exchangeFormStateProvider.select((value) => value.reversed))) + Text( + ref.watch( + exchangeFormStateProvider.select((value) => value.warning)), + style: STextStyles.errorSmall(context), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Positioned.fill( - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - "You will receive", - style: STextStyles.itemSubtitle(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ), + Text( + "You will receive", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: RoundedContainer( + padding: const EdgeInsets.all(6), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + radiusMultiplier: 0.75, + child: child, ), ), - ), - Center( - child: Column( - children: [ - const SizedBox( - height: 6, + child: GestureDetector( + onTap: () async { + await _swap(); + }, + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg.swap, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, ), - GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - const SizedBox( - height: 6, - ), - ], - ), - ), - Positioned.fill( - child: Align( - alignment: ref.watch(exchangeFormStateProvider - .select((value) => value.reversed)) - ? Alignment.bottomRight - : Alignment.topRight, - child: Text( - ref.watch(exchangeFormStateProvider - .select((value) => value.warning)), - style: STextStyles.errorSmall(context), ), ), ), ], ), - const SizedBox( - height: 4, + // Stack( + // children: [ + // Positioned.fill( + // child: Align( + // alignment: Alignment.bottomLeft, + // child: Text( + // "You will receive", + // style: STextStyles.itemSubtitle(context).copyWith( + // color: + // Theme.of(context).extension()!.textDark3, + // ), + // ), + // ), + // ), + // Center( + // child: Column( + // children: [ + // const SizedBox( + // height: 6, + // ), + // GestureDetector( + // onTap: () async { + // await _swap(); + // }, + // child: Padding( + // padding: const EdgeInsets.all(4), + // child: SvgPicture.asset( + // Assets.svg.swap, + // width: 20, + // height: 20, + // color: Theme.of(context) + // .extension()! + // .accentColorDark, + // ), + // ), + // ), + // const SizedBox( + // height: 6, + // ), + // ], + // ), + // ), + // Positioned.fill( + // child: Align( + // alignment: ref.watch(exchangeFormStateProvider + // .select((value) => value.reversed)) + // ? Alignment.bottomRight + // : Alignment.topRight, + // child: Text( + // ref.watch(exchangeFormStateProvider + // .select((value) => value.warning)), + // style: STextStyles.errorSmall(context), + // ), + // ), + // ), + // ], + // ), + SizedBox( + height: isDesktop ? 10 : 4, ), TextFormField( style: STextStyles.smallMed14(context).copyWith( @@ -1169,6 +1229,8 @@ class _ExchangeFormState extends ConsumerState { ), focusNode: _receiveFocusNode, controller: _receiveController, + enableSuggestions: false, + autocorrect: false, readOnly: ref.watch(prefsChangeNotifierProvider .select((value) => value.exchangeRateType)) == ExchangeRateType.estimated || @@ -1304,16 +1366,27 @@ class _ExchangeFormState extends ConsumerState { ), ), ), - const SizedBox( - height: 12, + if (ref + .watch( + exchangeFormStateProvider.select((value) => value.warning)) + .isNotEmpty && + ref.watch( + exchangeFormStateProvider.select((value) => value.reversed))) + Text( + ref.watch( + exchangeFormStateProvider.select((value) => value.warning)), + style: STextStyles.errorSmall(context), + ), + SizedBox( + height: isDesktop ? 20 : 12, ), RateTypeToggle( onChanged: onRateTypeChanged, ), if (ref.read(exchangeFormStateProvider).fromAmount != null && ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) - const SizedBox( - height: 8, + SizedBox( + height: isDesktop ? 20 : 12, ), if (ref.read(exchangeFormStateProvider).fromAmount != null && ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) @@ -1328,10 +1401,11 @@ class _ExchangeFormState extends ConsumerState { reversed: ref.watch( exchangeFormStateProvider.select((value) => value.reversed)), ), - const SizedBox( - height: 12, + SizedBox( + height: isDesktop ? 20 : 12, ), PrimaryButton( + buttonHeight: isDesktop ? ButtonHeight.l : null, enabled: ref.watch( exchangeFormStateProvider.select((value) => value.canExchange)), onPressed: ref.watch(exchangeFormStateProvider From 51cfc3f4dfce57fd22832173bdd0f2a3daddbdf0 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 18 Nov 2022 16:14:27 -0600 Subject: [PATCH 135/225] light colors accent blue fix? --- lib/utilities/theme/light_colors.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/theme/light_colors.dart b/lib/utilities/theme/light_colors.dart index ea3a7cb92..896ae4e5e 100644 --- a/lib/utilities/theme/light_colors.dart +++ b/lib/utilities/theme/light_colors.dart @@ -11,7 +11,7 @@ class LightColors extends StackColorTheme { Color get overlay => const Color(0xFF111215); @override - Color get accentColorBlue => const Color(0xFF4C86E9); + Color get accentColorBlue => const Color(0xFF0052DF); @override Color get accentColorGreen => const Color(0xFF4CC0A0); @override From 80802727381722405be4bee6a06941b076d9510d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 16:19:38 -0700 Subject: [PATCH 136/225] WIP: delete wallet --- .../wallet_view/desktop_wallet_view.dart | 5 + .../sub_widgets/delete_wallet_button.dart | 238 ++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 85dde4aba..739dbe1a1 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/my_wallet.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/network_info_button.dart'; @@ -412,6 +413,10 @@ class _DesktopWalletViewState extends ConsumerState { WalletKeysButton( walletId: walletId, ), + const SizedBox( + width: 2, + ), + DeleteWalletButton(), const SizedBox( width: 12, ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart new file mode 100644 index 000000000..f45367d0b --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/assets.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/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; + +class DeleteWalletButton extends ConsumerStatefulWidget { + const DeleteWalletButton({ + Key? key, + }) : super(key: key); + + @override + ConsumerState createState() => _DeleteWalletButton(); +} + +class _DeleteWalletButton extends ConsumerState { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + bool _continueEnabled = false; + + Future attentionDelete() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + ), + ], + ), + ], + ), + ), + ); + } + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: () { + showDialog( + barrierDismissible: true, + context: context, + builder: (context) => DesktopDialog( + maxHeight: 475, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + const SizedBox(height: 16), + Text( + "Delete wallet", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "Enter your password", + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + const SizedBox(height: 24), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopDeleteWalletPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "desktopDeleteWalletShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _continueEnabled = + passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + enabled: _continueEnabled, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(); + + attentionDelete(); + }, + ), + ], + ) + ], + ), + ), + ], + ), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 19, + horizontal: 32, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.ellipsis, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ], + ), + ), + ); + } +} From 92da601fb80350706b412bc72bc2f952120446a0 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 18 Nov 2022 17:43:35 -0700 Subject: [PATCH 137/225] WIP: delete wallete Attention dialog --- .../sub_widgets/delete_wallet_button.dart | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart index f45367d0b..96930c044 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; class DeleteWalletButton extends ConsumerStatefulWidget { @@ -34,7 +35,7 @@ class _DeleteWalletButton extends ConsumerState { barrierDismissible: true, builder: (context) => DesktopDialog( maxWidth: 580, - maxHeight: 400, + maxHeight: 530, child: Column( children: [ Row( @@ -43,17 +44,62 @@ class _DeleteWalletButton extends ConsumerState { DesktopDialogCloseButton(), ], ), - Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 32, vertical: 26), - child: Text( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + Text( "Attention!", style: STextStyles.desktopH2(context), ), - ), - ], + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackError, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " + "the only way you can have access to your funds is by using your backup key." + "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." + "\n\nPLEASE SAVE YOUR BACKUP KEY.", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "View Backup Key", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ) + ], + ), ), ], ), @@ -84,7 +130,7 @@ class _DeleteWalletButton extends ConsumerState { borderRadius: BorderRadius.circular(1000), ), onPressed: () { - showDialog( + showDialog( barrierDismissible: true, context: context, builder: (context) => DesktopDialog( From 5f1a485ed5b4a7f2db44bee6a06172774fafe5a6 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Sat, 19 Nov 2022 11:00:15 -0700 Subject: [PATCH 138/225] WIP: delete wallete stateful widget + attention warning dialog --- .../wallet_view/desktop_wallet_view.dart | 4 +- .../sub_widgets/delete_wallet_button.dart | 253 ++------------- .../desktop_delete_wallet_dialog.dart | 299 ++++++++++++++++++ lib/route_generator.dart | 23 ++ 4 files changed, 350 insertions(+), 229 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 739dbe1a1..5996597b5 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -416,7 +416,9 @@ class _DesktopWalletViewState extends ConsumerState { const SizedBox( width: 2, ), - DeleteWalletButton(), + DeleteWalletButton( + walletId: walletId, + ), const SizedBox( width: 12, ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart index 96930c044..54f991c37 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -1,128 +1,37 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.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/desktop/desktop_dialog.dart'; -import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -import 'package:stackwallet/widgets/rounded_container.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; + +import 'desktop_delete_wallet_dialog.dart'; class DeleteWalletButton extends ConsumerStatefulWidget { const DeleteWalletButton({ Key? key, + required this.walletId, }) : super(key: key); + final String walletId; + @override ConsumerState createState() => _DeleteWalletButton(); } class _DeleteWalletButton extends ConsumerState { - late final TextEditingController passwordController; - late final FocusNode passwordFocusNode; - - bool hidePassword = true; - bool _continueEnabled = false; - - Future attentionDelete() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) => DesktopDialog( - maxWidth: 580, - maxHeight: 530, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: const [ - DesktopDialogCloseButton(), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), - child: Column( - children: [ - Text( - "Attention!", - style: STextStyles.desktopH2(context), - ), - const SizedBox( - height: 16, - ), - RoundedContainer( - color: Theme.of(context) - .extension()! - .snackBarBackError, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Text( - "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " - "the only way you can have access to your funds is by using your backup key." - "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." - "\n\nPLEASE SAVE YOUR BACKUP KEY.", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ), - ), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SecondaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - const SizedBox(width: 16), - PrimaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "View Backup Key", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ) - ], - ), - ), - ], - ), - ), - ); - } + late final String walletId; @override void initState() { - passwordController = TextEditingController(); - passwordFocusNode = FocusNode(); + walletId = widget.walletId; + final managerProvider = + ref.read(walletsChangeNotifierProvider).getManagerProvider(walletId); super.initState(); } - @override - void dispose() { - passwordController.dispose(); - passwordFocusNode.dispose(); - - super.dispose(); - } - @override Widget build(BuildContext context) { return RawMaterialButton( @@ -130,134 +39,22 @@ class _DeleteWalletButton extends ConsumerState { borderRadius: BorderRadius.circular(1000), ), onPressed: () { - showDialog( - barrierDismissible: true, + showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: 475, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: const [ - DesktopDialogCloseButton(), - ], - ), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 32, vertical: 26), - child: Column( - children: [ - const SizedBox(height: 16), - Text( - "Delete wallet", - style: STextStyles.desktopH2(context), - ), - const SizedBox(height: 16), - Text( - "Enter your password", - style: STextStyles.desktopTextMedium(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - const SizedBox(height: 24), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("desktopDeleteWalletPasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - labelStyle: STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: SizedBox( - height: 70, - child: Row( - children: [ - const SizedBox( - width: 24, - ), - GestureDetector( - key: const Key( - "desktopDeleteWalletShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 24, - ), - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - ), - onChanged: (newValue) { - setState(() { - _continueEnabled = - passwordController.text.isNotEmpty; - }); - }, - ), - ), - const SizedBox(height: 50), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SecondaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - const SizedBox(width: 16), - PrimaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - enabled: _continueEnabled, - label: "Continue", - onPressed: () { - Navigator.of(context).pop(); - - attentionDelete(); - }, - ), - ], - ) - ], + barrierDismissible: false, + builder: (context) => Navigator( + initialRoute: DesktopDeleteWalletDialog.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: DesktopDeleteWalletDialog.routeName, + arguments: walletId, ), - ), - ], - ), + ) + ]; + }, ), ); }, diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart new file mode 100644 index 000000000..e2ab4fa86 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -0,0 +1,299 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/utilities/assets.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/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; + +import '../../../../../providers/desktop/storage_crypto_handler_provider.dart'; +import '../../../../../providers/global/wallets_provider.dart'; + +class DesktopDeleteWalletDialog extends ConsumerStatefulWidget { + const DesktopDeleteWalletDialog({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/desktopDeleteWalletDialog"; + + @override + ConsumerState createState() => + _DesktopDeleteWalletDialog(); +} + +class _DesktopDeleteWalletDialog + extends ConsumerState { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + bool _continueEnabled = false; + + Future attentionDelete() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => DesktopDialog( + maxWidth: 610, + maxHeight: 530, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + }, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackError, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " + "the only way you can have access to your funds is by using your backup key." + "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." + "\n\nPLEASE SAVE YOUR BACKUP KEY.", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "View Backup Key", + onPressed: () {}, + ), + ], + ) + ], + ), + ), + ], + ), + ), + ); + } + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + const SizedBox(height: 16), + Text( + "Delete wallet", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "Enter your password", + style: STextStyles.desktopTextMedium(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + const SizedBox(height: 24), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopDeleteWalletPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + labelStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "desktopDeleteWalletShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _continueEnabled = passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + enabled: _continueEnabled, + label: "Continue", + onPressed: _continueEnabled + ? () async { + final verified = await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + + if (verified) { + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + Navigator.of(context).pop(); + + attentionDelete(); + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase!", + context: context, + ), + ); + } + } + : null, + ), + ], + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 8ccc923bc..77b5e7d11 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -92,6 +92,7 @@ import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; @@ -1170,6 +1171,28 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopDeleteWalletDialog.routeName: + if (args is String) { + return FadePageRoute( + DesktopDeleteWalletDialog( + walletId: args, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case QRCodeDesktopPopupContent.routeName: if (args is String) { return FadePageRoute( From a8faa7b8e7b851c0e919b0678f9d066fc237120a Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 09:20:43 -0600 Subject: [PATCH 139/225] exchange form desktop routing and dialogs --- lib/pages/exchange_view/exchange_form.dart | 223 +++++++++++++----- .../desktop/simple_desktop_dialog.dart | 65 +++++ 2 files changed, 229 insertions(+), 59 deletions(-) create mode 100644 lib/widgets/desktop/simple_desktop_dialog.dart diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 5ece5aba8..a6d736228 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -30,7 +30,11 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -139,14 +143,27 @@ class _ExchangeFormState extends ConsumerState { .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", + unawaited( + showDialog( + context: context, + builder: (_) { + if (isDesktop) { + return const SimpleDesktopDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } else { + return const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } + }, ), - )); + ); + return; } }, @@ -229,14 +246,26 @@ class _ExchangeFormState extends ConsumerState { .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", + unawaited( + showDialog( + context: context, + builder: (_) { + if (isDesktop) { + return const SimpleDesktopDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } else { + return const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ); + } + }, ), - )); + ); return; } }, @@ -324,7 +353,7 @@ class _ExchangeFormState extends ConsumerState { await ref.read(exchangeFormStateProvider).swap(); } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } _swapLock = false; } @@ -567,14 +596,14 @@ class _ExchangeFormState extends ConsumerState { ? "-" : ref.read(exchangeFormStateProvider).toAmountString; if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; } } } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } if (!(fromTicker == "-" || toTicker == "-")) { unawaited( @@ -620,7 +649,7 @@ class _ExchangeFormState extends ConsumerState { true, ); if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; case SimpleSwapExchange.exchangeName: @@ -657,7 +686,7 @@ class _ExchangeFormState extends ConsumerState { ? "-" : ref.read(exchangeFormStateProvider).toAmountString; if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } return; } @@ -669,7 +698,7 @@ class _ExchangeFormState extends ConsumerState { } } if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); } unawaited( showFloatingFlushBar( @@ -722,15 +751,27 @@ class _ExchangeFormState extends ConsumerState { } if (!isAvailable) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) { + if (isDesktop) { + return SimpleDesktopDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ); + } else { + return StackDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ); + } + }, ), - )); + ); return; } rate = @@ -744,37 +785,101 @@ class _ExchangeFormState extends ConsumerState { shouldCancel = await showDialog( context: context, barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to update trade estimate", - message: - "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Attempt", - style: STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), + builder: (_) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Failed to update trade estimate", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const Spacer(), + Text( + estimate.warningMessage!, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer(), + Text( + "Do you want to attempt trade anyways?", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(true), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Attempt", + buttonHeight: ButtonHeight.l, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(false), + ), + ), + ], + ) + ], + ), + ); + } else { + return StackDialog( + title: "Failed to update trade estimate", + message: + "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Attempt", + style: STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, + ), + ); + } + }, ); } diff --git a/lib/widgets/desktop/simple_desktop_dialog.dart b/lib/widgets/desktop/simple_desktop_dialog.dart new file mode 100644 index 000000000..cd066c221 --- /dev/null +++ b/lib/widgets/desktop/simple_desktop_dialog.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; + +class SimpleDesktopDialog extends StatelessWidget { + const SimpleDesktopDialog({ + Key? key, + required this.title, + required this.message, + }) : super(key: key); + + final String title; + final String message; + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const Spacer(), + Text( + message, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ), + ], + ) + ], + ), + ); + } +} From b2ff99be19400374c71b9e2fbe6be4eb08bc8147 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 09:20:58 -0600 Subject: [PATCH 140/225] login loading indicator size --- lib/pages_desktop_specific/desktop_login_view.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index f60ce2240..f865fad47 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -49,8 +49,15 @@ class _DesktopLoginViewState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => const LoadingIndicator( - width: 200, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], ), ), ); From cc4dc9e3c71d67745fd0294e38dd74ddd709f413 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 09:24:32 -0600 Subject: [PATCH 141/225] exchange rate type toggle mouse regions --- .../sub_widgets/rate_type_toggle.dart | 287 ++++++++++-------- 1 file changed, 153 insertions(+), 134 deletions(-) diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index 9697710e8..31ee01ce2 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; class RateTypeToggle extends ConsumerWidget { @@ -35,145 +36,163 @@ class RateTypeToggle extends ConsumerWidget { child: Row( children: [ Expanded( - child: GestureDetector( - onTap: () { - if (!estimated) { - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.estimated; - onChanged?.call(ExchangeRateType.estimated); - } - }, - child: RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(17) - : const EdgeInsets.all(0), - color: estimated - ? Theme.of(context) - .extension()! - .textFieldDefaultBG - : Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.lockOpen, - width: 12, - height: 14, - color: isDesktop - ? estimated - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), - Text( - "Estimate rate", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: estimated - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary, - ) - : STextStyles.smallMed12(context).copyWith( - color: estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], + child: ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: estimated + ? SystemMouseCursors.basic + : SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (!estimated) { + ref.read(prefsChangeNotifierProvider).exchangeRateType = + ExchangeRateType.estimated; + onChanged?.call(ExchangeRateType.estimated); + } + }, + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(17) + : const EdgeInsets.all(0), + color: estimated + ? Theme.of(context) + .extension()! + .textFieldDefaultBG + : Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.lockOpen, + width: 12, + height: 14, + color: isDesktop + ? estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + const SizedBox( + width: 5, + ), + Text( + "Estimate rate", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary, + ) + : STextStyles.smallMed12(context).copyWith( + color: estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), ), ), ), ), Expanded( - child: GestureDetector( - onTap: () { - if (estimated) { - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.fixed; - onChanged?.call(ExchangeRateType.fixed); - } - }, - child: RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(17) - : const EdgeInsets.all(0), - color: !estimated - ? Theme.of(context) - .extension()! - .textFieldDefaultBG - : Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.lock, - width: 12, - height: 14, - color: isDesktop - ? !estimated - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : !estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), - Text( - "Fixed rate", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: !estimated - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary, - ) - : STextStyles.smallMed12(context).copyWith( - color: !estimated - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], + child: ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: !estimated + ? SystemMouseCursors.basic + : SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (estimated) { + ref.read(prefsChangeNotifierProvider).exchangeRateType = + ExchangeRateType.fixed; + onChanged?.call(ExchangeRateType.fixed); + } + }, + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(17) + : const EdgeInsets.all(0), + color: !estimated + ? Theme.of(context) + .extension()! + .textFieldDefaultBG + : Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.lock, + width: 12, + height: 14, + color: isDesktop + ? !estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : !estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + const SizedBox( + width: 5, + ), + Text( + "Fixed rate", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: !estimated + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary, + ) + : STextStyles.smallMed12(context).copyWith( + color: !estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), ), ), ), From 601001f96df83e182959782e1de05e90e782ce9a Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 10:01:09 -0600 Subject: [PATCH 142/225] WIP: desktop exchange steps flow ui --- lib/pages/exchange_view/exchange_form.dart | 54 ++++++-- .../exchange_steps/step_scaffold.dart | 55 ++++++++ .../desktop_exchange_steps_indicator.dart | 121 ++++++++++++++++++ 3 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index a6d736228..2d89c7660 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view. import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; @@ -908,20 +909,49 @@ class _ExchangeFormState extends ConsumerState { if (walletInitiated) { ref.read(exchangeSendFromWalletIdStateProvider.state).state = Tuple2(walletId!, coin!); - unawaited( - Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - ), - ); + if (isDesktop) { + await showDialog( + context: context, + builder: (context) { + return const DesktopDialog( + maxWidth: 700, + child: StepScaffold( + step: 1, + ), + ); + }, + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + Step2View.routeName, + arguments: model, + ), + ); + } } else { ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited( - Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - ), - ); + + if (isDesktop) { + await showDialog( + context: context, + builder: (context) { + return const DesktopDialog( + maxWidth: 700, + child: StepScaffold( + step: 0, + ), + ); + }, + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + ), + ); + } } } } diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart new file mode 100644 index 000000000..09aea9dbf --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; + +class StepScaffold extends StatefulWidget { + const StepScaffold({Key? key, required this.step}) : super(key: key); + + final int step; + + @override + State createState() => _StepScaffoldState(); +} + +class _StepScaffoldState extends State { + int currentStep = 0; + + @override + void initState() { + currentStep = widget.step; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + const AppBarBackButton( + isCompact: true, + ), + Text( + "Exchange XXX to XXX", + style: STextStyles.desktopH3(context), + ), + ], + ), + const SizedBox( + height: 32, + ), + DesktopExchangeStepsIndicator( + currentStep: currentStep, + ), + const SizedBox( + height: 32, + ), + Container( + height: 200, + color: Colors.red, + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart new file mode 100644 index 000000000..44831bb4b --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class DesktopExchangeStepsIndicator extends StatelessWidget { + const DesktopExchangeStepsIndicator({Key? key, required this.currentStep}) + : super(key: key); + + final int currentStep; + + Color getColor(BuildContext context, int step) { + if (currentStep > step) { + return Theme.of(context) + .extension()! + .accentColorBlue + .withOpacity(0.5); + } else if (currentStep < step) { + return Theme.of(context).extension()!.textSubtitle3; + } else { + return Theme.of(context).extension()!.accentColorBlue; + } + } + + static const double verticalSpacing = 4; + static const double horizontalSpacing = 16; + static const double barHeight = 6; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Column( + children: [ + Text( + "Confirm amount", + style: STextStyles.desktopTextSmall(context).copyWith( + color: getColor(context, 0), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 0), + height: barHeight, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Enter details", + style: STextStyles.desktopTextSmall(context).copyWith( + color: getColor(context, 1), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 1), + height: barHeight, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Confirm details", + style: STextStyles.desktopTextSmall(context).copyWith( + color: getColor(context, 2), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 2), + height: barHeight, + ), + ], + ), + ), + const SizedBox( + width: horizontalSpacing, + ), + Expanded( + child: Column( + children: [ + Text( + "Complete exchange", + style: STextStyles.desktopTextSmall(context).copyWith( + color: getColor(context, 3), + ), + ), + const SizedBox( + height: verticalSpacing, + ), + RoundedContainer( + color: getColor(context, 3), + height: barHeight, + ), + ], + ), + ), + ], + ); + } +} From 90dc9e3116747ca80f0aa541bfce550bbee1113c Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 11:32:18 -0600 Subject: [PATCH 143/225] mobile button height fix --- .../manage_nodes_views/node_details_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 3d49ae6f7..71d764135 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -349,7 +349,7 @@ class _NodeDetailsViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Test connection", - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { await _testConnection(ref, context); }, From d4d85259e1c89d6f418c044a48e432704c83501b Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 12:52:32 -0600 Subject: [PATCH 144/225] logging fix --- lib/services/coins/wownero/wownero_wallet.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index e39d13005..3d5ae3ad6 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -863,7 +863,8 @@ class WowneroWallet extends CoinServiceAPI { await DB.instance.get(boxName: walletId, key: indexKey) as int; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain(0, curIndex); - Logging.instance.log("xmr address in init existing: $newReceivingAddress", + Logging.instance.log( + "wownero address in init existing: $newReceivingAddress", level: LogLevel.Info); _currentReceivingAddress = Future(() => newReceivingAddress); } From 719c7abd49906cc621612aabd19e4e1fe1776f43 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 13:44:36 -0600 Subject: [PATCH 145/225] clean up logs --- lib/services/coins/monero/monero_wallet.dart | 4 ++-- lib/services/coins/wownero/wownero_wallet.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index c35323d53..f94f0cd2a 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -185,8 +185,8 @@ class MoneroWallet extends CoinServiceAPI { try { if (walletBase!.syncStatus! is SyncedSyncStatus && walletBase!.syncStatus!.progress() == 1.0) { - Logging.instance - .log("currentSyncingHeight lol", level: LogLevel.Warning); + // Logging.instance + // .log("currentSyncingHeight lol", level: LogLevel.Warning); return getSyncingHeight(); } } catch (e, s) {} diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 3d5ae3ad6..e6a531b78 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -153,7 +153,7 @@ class WowneroWallet extends CoinServiceAPI { try { _height = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int blocksRemaining = -1; @@ -162,7 +162,7 @@ class WowneroWallet extends CoinServiceAPI { blocksRemaining = (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } int currentHeight = _height + blocksRemaining; if (_height == -1 || blocksRemaining == -1) { @@ -186,8 +186,8 @@ class WowneroWallet extends CoinServiceAPI { try { if (walletBase!.syncStatus! is SyncedSyncStatus && walletBase!.syncStatus!.progress() == 1.0) { - Logging.instance - .log("currentSyncingHeight lol", level: LogLevel.Warning); + // Logging.instance + // .log("currentSyncingHeight lol", level: LogLevel.Warning); return getSyncingHeight(); } } catch (e, s) {} @@ -195,7 +195,7 @@ class WowneroWallet extends CoinServiceAPI { try { syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } final cachedHeight = DB.instance.get(boxName: walletId, key: "storedSyncingHeight") @@ -418,7 +418,7 @@ class WowneroWallet extends CoinServiceAPI { try { progress = (walletBase!.syncStatus!).progress(); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + // Logging.instance.log("$e $s", level: LogLevel.Warning); } await _fetchTransactionData(); From b333253287c44beb6bc86428cdfe3f55eb938df9 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 15:12:08 -0600 Subject: [PATCH 146/225] reduce minimum window height --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 66b3bb974..6879b69c5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,7 +77,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1220, 1100)); + setWindowMinSize(const Size(1220, 1000)); setWindowMaxSize(Size.infinite); } From e2a172f7477550da845224003325da7bfbce35c5 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 19 Nov 2022 18:04:53 -0600 Subject: [PATCH 147/225] firo private/public balance desktop toggle --- assets/images/glasses-hidden.png | Bin 0 -> 1060 bytes assets/images/glasses.png | Bin 0 -> 1955 bytes .../desktop_balance_toggle_button.dart | 60 +++ .../sub_widgets/desktop_wallet_summary.dart | 375 +++++++----------- lib/utilities/assets.dart | 3 + pubspec.yaml | 2 + 6 files changed, 215 insertions(+), 225 deletions(-) create mode 100644 assets/images/glasses-hidden.png create mode 100644 assets/images/glasses.png create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart diff --git a/assets/images/glasses-hidden.png b/assets/images/glasses-hidden.png new file mode 100644 index 0000000000000000000000000000000000000000..9176cc69b914fb6b435fe881fd4987835a0fd5dc GIT binary patch literal 1060 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xg+b9?AEiM4&lZ%393nj6eNt<+`IRx3Jj z`A3<)2MX`bC>`Fx>nzLUYIgVBvFV#G?BCJk{ew5%pnLy@3IC%*dU&6A?PceGFtNtA zV|S5E^34|>eM>`g*PMxR*SY@DJne&>-9-6m@!y2z-3+yVc{a&UcAA4wHcRbO?MB!t0g?Ro$<;*$q(~6EN{17>x+%?6ks| zmG?{dHfipg9#vU=cxu%A2~o>9)vli9>dx-}t5x>LXYNnL4u zue~ty^$q^)OCPEw<-cEgy|ZrZoTi?~o;Gd2H~+lYb%Qtavc0;lf!}kZ{c~?h*IuQX?1Hc~BdjVMV;EJ?LWE=mPb3`Pb9nO2Eg!#S^TPFVdQ&MBb@01M61 A`2YX_ literal 0 HcmV?d00001 diff --git a/assets/images/glasses.png b/assets/images/glasses.png new file mode 100644 index 0000000000000000000000000000000000000000..8c9e7dc276660d6b8284916943c78137a0d6ad2f GIT binary patch literal 1955 zcmeIxSy0nQ90%|PlvtEv>k-A)rmcu5fslj{LWCqFxIf`TYw5WJ|jGOYq72o_PsgWut76xivm4Tx=zw!~aDjL@;2|UjJlVmO>^P6&NT4_pX^uoM6vUn3tzXIDQC@AL=3ua}g?B zMBy&t2)s`u-baP^jdIi8z)?2Y{IeaeefRS}7# zIcGnaA^EzUK%7H4=pUh)*{rkL+uhjr&|IZgw^zLB=ye^rHR39X4?LgaE9;wMr3e#W zUeozll{MYn6= zmlJG)b*wRa4ZS9Ex?$4o+U()wrh(b!srq$4X4>UEeK2L7mS)D6^-1?nx2YvzjWrWh zJ!{hKy|eiBb#-!eX*v0=-?97FY#?y;?C4 zNqbktY`dw90EVFrApUcQcPDj0QT?#-lBTdj|8nnSoTk7OHLIN(n|P;eAhqE3*GN<1 zw_|LtE!5!QgBjN5@9Pf^HcU_K(>>+x(}$n+ZyS7^6kzQ7l5Zl96)`H4L~qvSd0TZ9$Kr)eM0XE2_0MA&7FZ92HNihUXm#z9c4T-Q;#r~bfEtcn+L_hW ztzKf5?4FV9=lwnn9Bbm(G^{jmDVTl)#TLD-7_L0cYWHlqkFbf>YE8SccEh?=2G~Ad!@&is&a+J;MuDmO$ zoaVKS%&ji>3Z62CmwmYH!HbNo^n;<2)4%;xr&L}GX_D<-KR4Y;Z=Eq<&Qf0|*7)zv zfoc7}HX9`h(=8x=v{f_JJJ2zaC1E!UDqk2!yILPHljOb!3nLtFYzz%hC|hQTs7d(o zGt!okBeP+1^;Rq|IkB-~Z6d*Ltxgaf^hW$D{(5y-_8Qw|mtchsC#9;lam6``ar|+S zpmTWWi}GXjEOYaNgv67Nqu>Aj=+*dhIS6y}RSgbHHnl&~Y#6#ea_am&#fKn8t=Nd$ zHG0)(-B>0ob7EN}%(^SYN}J(;Ue>0H>gJ>tk6YZj?TGF$Ei^bbF|;i)A=_d?3Bfx< zp#sn-G#ZIQBhfgq6NZ4s5S(2%p-==Ast}VM^^ZW*_7G)Q!oLTG)%Hdx0GKo`^$I2M Fz~8{u`yl`T literal 0 HcmV?d00001 diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart new file mode 100644 index 000000000..9c890b223 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +class DesktopBalanceToggleButton extends ConsumerWidget { + const DesktopBalanceToggleButton({ + Key? key, + this.onPressed, + }) : super(key: key); + + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SizedBox( + height: 22, + width: 22, + child: MaterialButton( + color: Theme.of(context).extension()!.buttonBackSecondary, + splashColor: Theme.of(context).extension()!.highlight, + onPressed: () { + if (ref.read(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + } else { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + } + onPressed?.call(); + }, + elevation: 0, + highlightElevation: 0, + hoverElevation: 0, + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Center( + child: Image( + image: AssetImage( + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available + ? Assets.png.glassesHidden + : Assets.png.glasses, + ), + width: 16, + ), + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index f4bfed976..d6e99ce70 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -1,13 +1,15 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -33,19 +35,6 @@ class _WDesktopWalletSummaryState extends State { late final String walletId; late final ChangeNotifierProvider managerProvider; - void showSheet() { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => WalletBalanceToggleSheet(walletId: walletId), - ); - } - Decimal? _balanceTotalCached; Decimal? _balanceCached; @@ -59,225 +48,161 @@ class _WDesktopWalletSummaryState extends State { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + return Consumer( + builder: (context, ref, __) { + final Coin coin = + ref.watch(managerProvider.select((value) => value.coin)); + return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( - builder: (_, ref, __) { - final Coin coin = - ref.watch(managerProvider.select((value) => value.coin)); - final externalCalls = ref.watch(prefsChangeNotifierProvider - .select((value) => value.externalCalls)); + Column( + children: [ + Consumer( + builder: (_, ref, __) { + final externalCalls = ref.watch(prefsChangeNotifierProvider + .select((value) => value.externalCalls)); - Future? totalBalanceFuture; - Future? availableBalanceFuture; - if (coin == Coin.firo || coin == Coin.firoTestNet) { - final firoWallet = - ref.watch(managerProvider.select((value) => value.wallet)) + Future? totalBalanceFuture; + Future? availableBalanceFuture; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final firoWallet = ref.watch( + managerProvider.select((value) => value.wallet)) as FiroWallet; - totalBalanceFuture = firoWallet.availablePublicBalance(); - availableBalanceFuture = firoWallet.availablePrivateBalance(); - } else { - totalBalanceFuture = ref.watch( - managerProvider.select((value) => value.totalBalance)); - - availableBalanceFuture = ref.watch(managerProvider - .select((value) => value.availableBalance)); - } - - final locale = ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)); - - final baseCurrency = ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)); - - final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin))); - - final _showAvailable = false; - // ref.watch(walletBalanceToggleStateProvider.state).state == - // WalletBalanceToggleState.available; - - return FutureBuilder( - future: _showAvailable - ? availableBalanceFuture - : totalBalanceFuture, - builder: (fbContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - if (_showAvailable) { - _balanceCached = snapshot.data!; - } else { - _balanceTotalCached = snapshot.data!; - } - } - Decimal? balanceToShow = - _showAvailable ? _balanceCached : _balanceTotalCached; - - if (balanceToShow != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // GestureDetector( - // onTap: showSheet, - // child: Row( - // children: [ - // if (coin == Coin.firo || - // coin == Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Private" : "Public"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // ), - // ), - // if (coin != Coin.firo && - // coin != Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Available" : "Full"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // ), - // ), - // const SizedBox( - // width: 4, - // ), - // SvgPicture.asset( - // Assets.svg.chevronDown, - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // width: 8, - // height: 4, - // ), - // ], - // ), - // ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "${Format.localizedStringAsFixed( - value: balanceToShow, - locale: locale, - decimalPlaces: 8, - )} ${coin.ticker}", - style: STextStyles.desktopH3(context), - ), - ), - if (externalCalls) - Text( - "${Format.localizedStringAsFixed( - value: priceTuple.item1 * balanceToShow, - locale: locale, - decimalPlaces: 2, - )} $baseCurrency", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ); + totalBalanceFuture = firoWallet.availablePublicBalance(); + availableBalanceFuture = + firoWallet.availablePrivateBalance(); } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // GestureDetector( - // onTap: showSheet, - // child: Row( - // children: [ - // if (coin == Coin.firo || - // coin == Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Private" : "Public"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // ), - // ), - // if (coin != Coin.firo && - // coin != Coin.firoTestNet) - // Text( - // "${_showAvailable ? "Available" : "Full"} Balance", - // style: STextStyles.subtitle500(context) - // .copyWith( - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // ), - // ), - // const SizedBox( - // width: 4, - // ), - // SvgPicture.asset( - // Assets.svg.chevronDown, - // width: 8, - // height: 4, - // color: Theme.of(context) - // .extension()! - // .textFavoriteCard, - // ), - // ], - // ), - // ), - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: STextStyles.desktopH3(context).copyWith( - fontSize: 24, - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - if (externalCalls) - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ); + totalBalanceFuture = ref.watch(managerProvider + .select((value) => value.totalBalance)); + + availableBalanceFuture = ref.watch(managerProvider + .select((value) => value.availableBalance)); } + + final locale = ref.watch(localeServiceChangeNotifierProvider + .select((value) => value.locale)); + + final baseCurrency = ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)); + + final priceTuple = ref.watch( + priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + + final _showAvailable = ref + .watch(walletBalanceToggleStateProvider.state) + .state == + WalletBalanceToggleState.available; + + return FutureBuilder( + future: _showAvailable + ? availableBalanceFuture + : totalBalanceFuture, + builder: (fbContext, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + if (_showAvailable) { + _balanceCached = snapshot.data!; + } else { + _balanceTotalCached = snapshot.data!; + } + } + Decimal? balanceToShow = _showAvailable + ? _balanceCached + : _balanceTotalCached; + + if (balanceToShow != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "${Format.localizedStringAsFixed( + value: balanceToShow, + locale: locale, + decimalPlaces: 8, + )} ${coin.ticker}", + style: STextStyles.desktopH3(context), + ), + ), + if (externalCalls) + Text( + "${Format.localizedStringAsFixed( + value: priceTuple.item1 * balanceToShow, + locale: locale, + decimalPlaces: 2, + )} $baseCurrency", + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ], + style: STextStyles.desktopH3(context).copyWith( + fontSize: 24, + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + if (externalCalls) + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ], + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ); + } + }, + ); }, - ); - }, + ), + ], ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const SizedBox( + width: 8, + ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const DesktopBalanceToggleButton(), + const SizedBox( + width: 8, + ), + WalletRefreshButton( + walletId: walletId, + initialSyncStatus: widget.initialSyncStatus, + ) ], - ), - const SizedBox( - width: 8, - ), - WalletRefreshButton( - walletId: walletId, - initialSyncStatus: widget.initialSyncStatus, - ) - ], + ); + }, ); } } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 6fbe61005..149d46b3c 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -231,6 +231,9 @@ class _PNG { String get bitcoincash => "assets/images/bitcoincash.png"; String get namecoin => "assets/images/namecoin.png"; + String get glasses => "assets/images/glasses.png"; + String get glassesHidden => "assets/images/glasses-hidden.png"; + String imageFor({required Coin coin}) { switch (coin) { case Coin.bitcoin: diff --git a/pubspec.yaml b/pubspec.yaml index e8f417586..af4370d99 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -202,6 +202,8 @@ flutter: - assets/images/epic-cash.png - assets/images/bitcoincash.png - assets/images/namecoin.png + - assets/images/glasses.png + - assets/images/glasses-hidden.png - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg From 345ed958e080999ac2696ca24850d0d5944bbc39 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 07:44:45 -0600 Subject: [PATCH 148/225] initial window size linux --- lib/main.dart | 2 +- linux/my_application.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6879b69c5..728152951 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,7 +77,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1220, 1000)); + setWindowMinSize(const Size(1220, 900)); setWindowMaxSize(Size.infinite); } diff --git a/linux/my_application.cc b/linux/my_application.cc index 280895e03..9cb3acebd 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -47,7 +47,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "Stack Wallet"); } - gtk_window_set_default_size(window, 720, 1280); + gtk_window_set_default_size(window, 1220, 900); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); From b22b4195d6bf3cd4f7bec097a58c0cef7fb737d9 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 09:15:13 -0600 Subject: [PATCH 149/225] desktop exchange steps scaffolding --- lib/pages/exchange_view/exchange_form.dart | 23 ++-- .../exchange_steps/step_scaffold.dart | 28 +++-- .../subwidgets/desktop_step_1.dart | 104 ++++++++++++++++++ .../subwidgets/desktop_step_2.dart | 66 +++++++++++ .../subwidgets/desktop_step_3.dart | 91 +++++++++++++++ .../subwidgets/desktop_step_4.dart | 98 +++++++++++++++++ .../subwidgets/step_one_item.dart | 38 +++++++ .../desktop_exchange_steps_indicator.dart | 32 +++--- 8 files changed, 452 insertions(+), 28 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 2d89c7660..148c74920 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -19,12 +19,13 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_op import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -913,10 +914,14 @@ class _ExchangeFormState extends ConsumerState { await showDialog( context: context, builder: (context) { - return const DesktopDialog( - maxWidth: 700, + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, child: StepScaffold( - step: 1, + step: 2, + body: DesktopStep2( + model: model, + ), ), ); }, @@ -936,10 +941,14 @@ class _ExchangeFormState extends ConsumerState { await showDialog( context: context, builder: (context) { - return const DesktopDialog( - maxWidth: 700, + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, child: StepScaffold( - step: 0, + step: 1, + body: DesktopStep1( + model: model, + ), ), ); }, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 09aea9dbf..62a293c27 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -4,8 +4,13 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; class StepScaffold extends StatefulWidget { - const StepScaffold({Key? key, required this.step}) : super(key: key); + const StepScaffold({ + Key? key, + required this.body, + required this.step, + }) : super(key: key); + final Widget body; final int step; @override @@ -24,11 +29,13 @@ class _StepScaffoldState extends State { @override Widget build(BuildContext context) { return Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ Row( children: [ const AppBarBackButton( isCompact: true, + iconSize: 23, ), Text( "Exchange XXX to XXX", @@ -37,17 +44,24 @@ class _StepScaffoldState extends State { ], ), const SizedBox( - height: 32, + height: 12, ), - DesktopExchangeStepsIndicator( - currentStep: currentStep, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: DesktopExchangeStepsIndicator( + currentStep: currentStep, + ), ), const SizedBox( height: 32, ), - Container( - height: 200, - color: Colors.red, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: widget.body, ), ], ); diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart new file mode 100644 index 000000000..7334cae05 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopStep1 extends StatelessWidget { + const DesktopStep1({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Confirm amount", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + const StepOneItem( + label: "Exchange", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You send", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You receive", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "Rate", + value: "lol", + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + buttonHeight: ButtonHeight.l, + onPressed: () { + // todo + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart new file mode 100644 index 000000000..c9072cb76 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class DesktopStep2 extends StatelessWidget { + const DesktopStep2({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Enter exchange details", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Enter your recipient and refund addresses", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + // + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + buttonHeight: ButtonHeight.l, + onPressed: () { + // todo + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart new file mode 100644 index 000000000..1e2743ef5 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopStep3 extends StatelessWidget { + const DesktopStep3({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Confirm exchange details", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + const StepOneItem( + label: "Exchange", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You send", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You receive", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "Rate", + value: "lol", + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Back", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Confirm", + buttonHeight: ButtonHeight.l, + onPressed: () { + // todo + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart new file mode 100644 index 000000000..8604e7c23 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopStep4 extends StatelessWidget { + const DesktopStep4({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + "Confirm amount", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension()!.background, + padding: const EdgeInsets.all(0), + child: Column( + children: [ + const StepOneItem( + label: "Exchange", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You send", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "You receive", + value: "lol", + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + const StepOneItem( + label: "Rate", + value: "lol", + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 20, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Send from Stack Wallet", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Show QR code", + buttonHeight: ButtonHeight.l, + onPressed: () { + // todo + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart new file mode 100644 index 000000000..001383a17 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +class StepOneItem extends StatelessWidget { + const StepOneItem({ + Key? key, + required this.label, + required this.value, + this.padding = const EdgeInsets.all(16), + }) : super(key: key); + + final String label; + final String value; + final EdgeInsets padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + Text( + value, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart index 44831bb4b..ddcd2e6c4 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart @@ -22,9 +22,9 @@ class DesktopExchangeStepsIndicator extends StatelessWidget { } } - static const double verticalSpacing = 4; + static const double verticalSpacing = 6; static const double horizontalSpacing = 16; - static const double barHeight = 6; + static const double barHeight = 4; @override Widget build(BuildContext context) { @@ -35,16 +35,17 @@ class DesktopExchangeStepsIndicator extends StatelessWidget { children: [ Text( "Confirm amount", - style: STextStyles.desktopTextSmall(context).copyWith( - color: getColor(context, 0), + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 1), ), ), const SizedBox( height: verticalSpacing, ), RoundedContainer( - color: getColor(context, 0), + color: getColor(context, 1), height: barHeight, + width: double.infinity, ), ], ), @@ -57,16 +58,17 @@ class DesktopExchangeStepsIndicator extends StatelessWidget { children: [ Text( "Enter details", - style: STextStyles.desktopTextSmall(context).copyWith( - color: getColor(context, 1), + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 2), ), ), const SizedBox( height: verticalSpacing, ), RoundedContainer( - color: getColor(context, 1), + color: getColor(context, 2), height: barHeight, + width: double.infinity, ), ], ), @@ -79,16 +81,17 @@ class DesktopExchangeStepsIndicator extends StatelessWidget { children: [ Text( "Confirm details", - style: STextStyles.desktopTextSmall(context).copyWith( - color: getColor(context, 2), + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 3), ), ), const SizedBox( height: verticalSpacing, ), RoundedContainer( - color: getColor(context, 2), + color: getColor(context, 3), height: barHeight, + width: double.infinity, ), ], ), @@ -101,16 +104,17 @@ class DesktopExchangeStepsIndicator extends StatelessWidget { children: [ Text( "Complete exchange", - style: STextStyles.desktopTextSmall(context).copyWith( - color: getColor(context, 3), + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: getColor(context, 4), ), ), const SizedBox( height: verticalSpacing, ), RoundedContainer( - color: getColor(context, 3), + color: getColor(context, 4), height: barHeight, + width: double.infinity, ), ], ), From 11845b8b05b712cc6a9a931f44c5fb0ab7577de7 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 09:23:11 -0600 Subject: [PATCH 150/225] populate desktop step one trade info --- .../subwidgets/desktop_step_1.dart | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index 7334cae05..1f892dd52 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -class DesktopStep1 extends StatelessWidget { +class DesktopStep1 extends ConsumerWidget { const DesktopStep1({ Key? key, required this.model, @@ -16,7 +19,7 @@ class DesktopStep1 extends StatelessWidget { final IncompleteExchangeModel model; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Column( children: [ Text( @@ -38,33 +41,37 @@ class DesktopStep1 extends StatelessWidget { padding: const EdgeInsets.all(0), child: Column( children: [ - const StepOneItem( + StepOneItem( label: "Exchange", - value: "lol", + value: ref.watch(currentExchangeNameStateProvider.state).state, ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + StepOneItem( label: "You send", - value: "lol", + value: + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + StepOneItem( label: "You receive", - value: "lol", + value: + "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( - label: "Rate", - value: "lol", + StepOneItem( + label: model.rateType == ExchangeRateType.estimated + ? "Estimated rate" + : "Fixed rate", + value: model.rateInfo, ), ], ), From 2654d50e407b74916ad5ef95b4f27327518247ca Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 09:41:16 -0600 Subject: [PATCH 151/225] populate desktop step two trade info --- .../subwidgets/desktop_step_2.dart | 423 +++++++++++++++++- 1 file changed, 421 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index c9072cb76..e1c5a5620 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -1,16 +1,199 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; +import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; +import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; -class DesktopStep2 extends StatelessWidget { +class DesktopStep2 extends ConsumerStatefulWidget { const DesktopStep2({ Key? key, required this.model, + this.clipboard = const ClipboardWrapper(), }) : super(key: key); final IncompleteExchangeModel model; + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => _DesktopStep2State(); +} + +class _DesktopStep2State extends ConsumerState { + late final IncompleteExchangeModel model; + late final ClipboardInterface clipboard; + + late final TextEditingController _toController; + late final TextEditingController _refundController; + + late final FocusNode _toFocusNode; + late final FocusNode _refundFocusNode; + + bool isStackCoin(String ticker) { + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } + } + + void selectRecipientAddressFromStack() { + try { + final coin = coinFromTickerCaseInsensitive( + model.receiveTicker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(value); + + _toController.text = manager.walletName; + model.recipientAddress = await manager.currentReceivingAddress; + } + }); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + } + } + + void selectRefundAddressFromStack() { + try { + final coin = coinFromTickerCaseInsensitive( + model.sendTicker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(value); + + _refundController.text = manager.walletName; + model.refundAddress = await manager.currentReceivingAddress; + } + }); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + } + } + + void selectRecipientFromAddressBook() { + ref.read(exchangeFlowIsActiveStateProvider.state).state = true; + Navigator.of(context) + .pushNamed( + AddressBookView.routeName, + ) + .then((_) { + ref.read(exchangeFlowIsActiveStateProvider.state).state = false; + + final address = + ref.read(exchangeFromAddressBookAddressStateProvider.state).state; + if (address.isNotEmpty) { + _toController.text = address; + model.recipientAddress = _toController.text; + ref.read(exchangeFromAddressBookAddressStateProvider.state).state = ""; + } + }); + } + + void selectRefundFromAddressBook() { + ref.read(exchangeFlowIsActiveStateProvider.state).state = true; + Navigator.of(context) + .pushNamed( + AddressBookView.routeName, + ) + .then( + (_) { + ref.read(exchangeFlowIsActiveStateProvider.state).state = false; + final address = + ref.read(exchangeFromAddressBookAddressStateProvider.state).state; + if (address.isNotEmpty) { + _refundController.text = address; + model.refundAddress = _refundController.text; + } + }, + ); + } + + @override + void initState() { + model = widget.model; + clipboard = widget.clipboard; + + _toController = TextEditingController(); + _refundController = TextEditingController(); + + _toFocusNode = FocusNode(); + _refundFocusNode = FocusNode(); + + final tuple = ref.read(exchangeSendFromWalletIdStateProvider.state).state; + if (tuple != null) { + if (model.receiveTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase()) { + ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .currentReceivingAddress + .then((value) { + _toController.text = value; + model.recipientAddress = _toController.text; + }); + } else { + if (model.sendTicker.toUpperCase() == + tuple.item2.ticker.toUpperCase()) { + ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .currentReceivingAddress + .then((value) { + _refundController.text = value; + model.refundAddress = _refundController.text; + }); + } + } + } + + super.initState(); + } + + @override + void dispose() { + _toController.dispose(); + _refundController.dispose(); + + _toFocusNode.dispose(); + _refundFocusNode.dispose(); + + super.dispose(); + } @override Widget build(BuildContext context) { @@ -30,7 +213,243 @@ class DesktopStep2 extends StatelessWidget { const SizedBox( height: 20, ), - // + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recipient Wallet", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight), + ), + if (isStackCoin(model.receiveTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: selectRecipientAddressFromStack, + ), + ], + ), + const SizedBox( + height: 4, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + onTap: () {}, + key: const Key("recipientExchangeStep2ViewAddressFieldKey"), + controller: _toController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + focusNode: _toFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() {}); + }, + decoration: standardInputDecoration( + "Enter the ${model.receiveTicker.toUpperCase()} payout address", + _toFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _toController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _toController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _toController.text = ""; + model.recipientAddress = _toController.text; + setState(() {}); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + _toController.text = content; + model.recipientAddress = _toController.text; + setState(() {}); + } + }, + child: _toController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_toController.text.isEmpty) + TextFieldIconButton( + key: const Key("sendViewAddressBookButtonKey"), + onTap: selectRecipientFromAddressBook, + child: const AddressBookIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + child: Text( + "This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + const SizedBox( + height: 24, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Refund Wallet (required)", + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight), + ), + if (isStackCoin(model.sendTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: selectRefundAddressFromStack, + ), + ], + ), + const SizedBox( + height: 4, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("refundExchangeStep2ViewAddressFieldKey"), + controller: _refundController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + focusNode: _refundFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() {}); + }, + decoration: standardInputDecoration( + "Enter ${model.sendTicker.toUpperCase()} refund address", + _refundFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _refundController.text.isEmpty + ? const EdgeInsets.only(right: 16) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _refundController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _refundController.text = ""; + model.refundAddress = _refundController.text; + + setState(() {}); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = data.text!.trim(); + + _refundController.text = content; + model.refundAddress = _refundController.text; + + setState(() {}); + } + }, + child: _refundController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_refundController.text.isEmpty) + TextFieldIconButton( + key: const Key("sendViewAddressBookButtonKey"), + onTap: selectRefundFromAddressBook, + child: const AddressBookIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + borderColor: Theme.of(context).extension()!.background, + child: Text( + "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), Padding( padding: const EdgeInsets.only( top: 20, From 648c896b9e9a8a38e00faf63762c1bba5c37280d Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 09:49:27 -0600 Subject: [PATCH 152/225] refactor desktop step item --- .../subwidgets/desktop_step_1.dart | 10 ++-- .../subwidgets/desktop_step_3.dart | 10 ++-- .../subwidgets/desktop_step_4.dart | 10 ++-- .../subwidgets/desktop_step_item.dart | 59 +++++++++++++++++++ .../subwidgets/step_one_item.dart | 38 ------------ 5 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart delete mode 100644 lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index 1f892dd52..942747ea2 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -41,7 +41,7 @@ class DesktopStep1 extends ConsumerWidget { padding: const EdgeInsets.all(0), child: Column( children: [ - StepOneItem( + DesktopStepItem( label: "Exchange", value: ref.watch(currentExchangeNameStateProvider.state).state, ), @@ -49,7 +49,7 @@ class DesktopStep1 extends ConsumerWidget { height: 1, color: Theme.of(context).extension()!.background, ), - StepOneItem( + DesktopStepItem( label: "You send", value: "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", @@ -58,7 +58,7 @@ class DesktopStep1 extends ConsumerWidget { height: 1, color: Theme.of(context).extension()!.background, ), - StepOneItem( + DesktopStepItem( label: "You receive", value: "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", @@ -67,7 +67,7 @@ class DesktopStep1 extends ConsumerWidget { height: 1, color: Theme.of(context).extension()!.background, ), - StepOneItem( + DesktopStepItem( label: model.rateType == ExchangeRateType.estimated ? "Estimated rate" : "Fixed rate", diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart index 1e2743ef5..655c3518e 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -25,7 +25,7 @@ class DesktopStep3 extends StatelessWidget { padding: const EdgeInsets.all(0), child: Column( children: [ - const StepOneItem( + const DesktopStepItem( label: "Exchange", value: "lol", ), @@ -33,7 +33,7 @@ class DesktopStep3 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "You send", value: "lol", ), @@ -41,7 +41,7 @@ class DesktopStep3 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "You receive", value: "lol", ), @@ -49,7 +49,7 @@ class DesktopStep3 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "Rate", value: "lol", ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index 8604e7c23..3b3853efb 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -32,7 +32,7 @@ class DesktopStep4 extends StatelessWidget { padding: const EdgeInsets.all(0), child: Column( children: [ - const StepOneItem( + const DesktopStepItem( label: "Exchange", value: "lol", ), @@ -40,7 +40,7 @@ class DesktopStep4 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "You send", value: "lol", ), @@ -48,7 +48,7 @@ class DesktopStep4 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "You receive", value: "lol", ), @@ -56,7 +56,7 @@ class DesktopStep4 extends StatelessWidget { height: 1, color: Theme.of(context).extension()!.background, ), - const StepOneItem( + const DesktopStepItem( label: "Rate", value: "lol", ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart new file mode 100644 index 000000000..7c777c2dd --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; + +class DesktopStepItem extends StatelessWidget { + const DesktopStepItem( + {Key? key, + required this.label, + required this.value, + this.padding = const EdgeInsets.all(16), + this.vertical = false}) + : super(key: key); + + final String label; + final String value; + final EdgeInsets padding; + final bool vertical; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: ConditionalParent( + condition: vertical, + builder: (child) => Column( + children: [ + child, + const SizedBox( + height: 2, + ), + Text( + value, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + if (!vertical) + Text( + value, + style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart deleted file mode 100644 index 001383a17..000000000 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/step_one_item.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - -class StepOneItem extends StatelessWidget { - const StepOneItem({ - Key? key, - required this.label, - required this.value, - this.padding = const EdgeInsets.all(16), - }) : super(key: key); - - final String label; - final String value; - final EdgeInsets padding; - - @override - Widget build(BuildContext context) { - return Padding( - padding: padding, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - Text( - value, - style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ), - ), - ], - ), - ); - } -} From c9e2c4abb7c3fe41f9ea4c7b8152d5e00f597b76 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 10:14:27 -0600 Subject: [PATCH 153/225] desktop trade steps 3 and 4 mostly laid out --- .../exchange_step_views/step_4_view.dart | 1 - .../subwidgets/desktop_step_3.dart | 170 ++++++++++++++++-- .../subwidgets/desktop_step_4.dart | 155 ++++++++++++++-- 3 files changed, 295 insertions(+), 31 deletions(-) diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index 0921f68e0..a8b403dcf 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -18,7 +18,6 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart index 655c3518e..65b6ed2b3 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -1,13 +1,135 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; +import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_provider.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; -class DesktopStep3 extends StatelessWidget { - const DesktopStep3({Key? key}) : super(key: key); +class DesktopStep3 extends ConsumerStatefulWidget { + const DesktopStep3({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + ConsumerState createState() => _DesktopStep3State(); +} + +class _DesktopStep3State extends ConsumerState { + late final IncompleteExchangeModel model; + + Future createTrade() async { + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Creating a trade", + eventBus: null, + ), + ), + ), + ), + ); + + final ExchangeResponse response = + await ref.read(exchangeProvider).createTrade( + from: model.sendTicker, + to: model.receiveTicker, + fixedRate: model.rateType != ExchangeRateType.estimated, + amount: model.reversed ? model.receiveAmount : model.sendAmount, + addressTo: model.recipientAddress!, + extraId: null, + addressRefund: model.refundAddress!, + refundExtraId: "", + rateId: model.rateId, + reversed: model.reversed, + ); + + if (response.value == null) { + if (mounted) { + Navigator.of(context).pop(); + } + + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to create trade", + message: response.exception?.toString(), + ), + )); + return; + } + + // save trade to hive + await ref.read(tradesServiceProvider).add( + trade: response.value!, + shouldNotifyListeners: true, + ); + + String status = response.value!.status; + + model.trade = response.value!; + + // extra info if status is waiting + if (status == "Waiting") { + status += " for deposit"; + } + + if (mounted) { + Navigator.of(context).pop(); + } + + unawaited(NotificationApi.showNotification( + changeNowId: model.trade!.tradeId, + title: status, + body: "Trade ID ${model.trade!.tradeId}", + walletId: "", + iconAssetName: Assets.svg.arrowRotate, + date: model.trade!.timestamp, + shouldWatchForUpdates: true, + coinName: "coinName", + )); + + if (mounted) { + unawaited(Navigator.of(context).pushNamed( + Step4View.routeName, + arguments: model, + )); + } + } + + @override + void initState() { + model = widget.model; + super.initState(); + } @override Widget build(BuildContext context) { @@ -25,33 +147,55 @@ class DesktopStep3 extends StatelessWidget { padding: const EdgeInsets.all(0), child: Column( children: [ - const DesktopStepItem( + DesktopStepItem( label: "Exchange", - value: "lol", + value: ref.watch(currentExchangeNameStateProvider.state).state, ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( + DesktopStepItem( label: "You send", - value: "lol", + value: + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( + DesktopStepItem( label: "You receive", - value: "lol", + value: + "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( - label: "Rate", - value: "lol", + DesktopStepItem( + label: model.rateType == ExchangeRateType.estimated + ? "Estimated rate" + : "Fixed rate", + value: model.rateInfo, + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + DesktopStepItem( + vertical: true, + label: "Recipient ${model.receiveTicker.toUpperCase()} address", + value: model.recipientAddress!, + ), + Container( + height: 1, + color: Theme.of(context).extension()!.background, + ), + DesktopStepItem( + vertical: true, + label: "Refund ${model.sendTicker.toUpperCase()} address", + value: model.refundAddress!, ), ], ), @@ -77,9 +221,7 @@ class DesktopStep3 extends StatelessWidget { child: PrimaryButton( label: "Confirm", buttonHeight: ButtonHeight.l, - onPressed: () { - // todo - }, + onPressed: createTrade, ), ), ], diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index 3b3853efb..ba9838086 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -1,64 +1,187 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -class DesktopStep4 extends StatelessWidget { - const DesktopStep4({Key? key}) : super(key: key); +class DesktopStep4 extends ConsumerStatefulWidget { + const DesktopStep4({ + Key? key, + required this.model, + }) : super(key: key); + + final IncompleteExchangeModel model; + + @override + ConsumerState createState() => _DesktopStep4State(); +} + +class _DesktopStep4State extends ConsumerState { + late final IncompleteExchangeModel model; + + String _statusString = "New"; + + Timer? _statusTimer; + + bool _isWalletCoinAndHasWallet(String ticker) { + try { + final coin = coinFromTickerCaseInsensitive(ticker); + return ref + .read(walletsChangeNotifierProvider) + .managers + .where((element) => element.coin == coin) + .isNotEmpty; + } catch (_) { + return false; + } + } + + Future _updateStatus() async { + final statusResponse = + await ref.read(exchangeProvider).updateTrade(model.trade!); + String status = "Waiting"; + if (statusResponse.value != null) { + status = statusResponse.value!.status; + } + + // extra info if status is waiting + if (status == "Waiting") { + status += " for deposit"; + } + + if (mounted) { + setState(() { + _statusString = status; + }); + } + } + + @override + void initState() { + model = widget.model; + + _statusTimer = Timer.periodic(const Duration(seconds: 60), (_) { + _updateStatus(); + }); + + super.initState(); + } + + @override + void dispose() { + _statusTimer?.cancel(); + _statusTimer = null; + super.dispose(); + } @override Widget build(BuildContext context) { return Column( children: [ Text( - "Confirm amount", + "Send ${model.sendTicker.toUpperCase()} to the address below", style: STextStyles.desktopTextMedium(context), ), const SizedBox( height: 8, ), Text( - "Network fees and other exchange charges are included in the rate.", + "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", style: STextStyles.desktopTextExtraExtraSmall(context), ), const SizedBox( height: 20, ), + RoundedContainer( + color: Theme.of(context).extension()!.warningBackground, + child: RichText( + text: TextSpan( + text: + "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: 14, + ), + children: [ + TextSpan( + text: + "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: 14, + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), RoundedWhiteContainer( borderColor: Theme.of(context).extension()!.background, padding: const EdgeInsets.all(0), child: Column( children: [ - const DesktopStepItem( - label: "Exchange", - value: "lol", + DesktopStepItem( + vertical: true, + label: "Send ${model.sendTicker.toUpperCase()} to this address", + value: model.trade!.payInAddress, ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( - label: "You send", - value: "lol", + DesktopStepItem( + label: "Amount", + value: + "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( - label: "You receive", - value: "lol", + DesktopStepItem( + label: "Trade ID", + value: model.trade!.tradeId, ), Container( height: 1, color: Theme.of(context).extension()!.background, ), - const DesktopStepItem( - label: "Rate", - value: "lol", + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + Text( + _statusString, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .colorForStatus(_statusString), + ), + ), + ], + ), ), ], ), From 78186358b9de2fc28b989575e1d6bee1c62d1f38 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 10:50:53 -0700 Subject: [PATCH 154/225] WIP: wallet will be deleted dialog --- .../sub_widgets/delete_wallet_keys_popup.dart | 195 ++++++++++++++++++ .../desktop_attention_delete_wallet.dart | 122 +++++++++++ .../desktop_delete_wallet_dialog.dart | 90 +------- lib/route_generator.dart | 47 +++++ 4 files changed, 369 insertions(+), 85 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart new file mode 100644 index 000000000..5f46e0f2f --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -0,0 +1,195 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class DeleteWalletKeysPopup extends ConsumerStatefulWidget { + const DeleteWalletKeysPopup({ + Key? key, + required this.walletId, + required this.words, + }) : super(key: key); + + final String walletId; + final List words; + + static const String routeName = "/desktopDeleteWalletKeysPopup"; + + @override + ConsumerState createState() => + _DeleteWalletKeysPopup(); +} + +class _DeleteWalletKeysPopup extends ConsumerState { + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 614, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Wallet keys", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + }, + ), + ], + ), + const SizedBox( + height: 28, + ), + Text( + "Recovery phrase", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox( + height: 8, + ), + Center( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: Text( + "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", + style: STextStyles.desktopTextExtraExtraSmall(context), + textAlign: TextAlign.center, + ), + ), + ), + const SizedBox( + height: 24, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: MnemonicTable( + words: widget.words, + isDesktop: true, + itemBorderColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + ), + ), + const SizedBox( + height: 24, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: Row( + children: [ + Expanded( + child: PrimaryButton( + label: "Continue", + onPressed: () async { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + + unawaited( + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: () { + int count = 0; + Navigator.of(context) + .popUntil((_) => count++ >= 2); + }, + ), + ], + ), + Column( + children: [ + Text( + "Thanks! " + "\n\nYour wallet will be deleted.", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + int count = 0; + Navigator.of(context) + .popUntil( + (_) => count++ >= 2); + }), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Continue", + onPressed: () async { + // final walletsInstance = + // ref.read(walletsChangeNotifierProvider); + // await ref + // .read(walletsServiceChangeNotifierProvider) + // .deleteWallet(walletId, true); + // + // if (mounted) { + // Navigator.of(context).popUntil( + // ModalRoute.withName(HomeView.routeName)); + // } + + // // wait for widget tree to dispose of any widgets watching the manager + // await Future.delayed(const Duration(seconds: 1)); + // walletsInstance.removeWallet(walletId: walletId); + }), + ], + ) + ], + ), + ], + ), + ); + }), + ); + }, + ), + ), + ], + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart new file mode 100644 index 000000000..30546f60b --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:tuple/tuple.dart'; + +import 'delete_wallet_keys_popup.dart'; + +class DesktopAttentionDeleteWallet extends ConsumerStatefulWidget { + const DesktopAttentionDeleteWallet({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/desktopAttentionDeleteWallet"; + + @override + ConsumerState createState() => + _DesktopAttentionDeleteWallet(); +} + +class _DesktopAttentionDeleteWallet + extends ConsumerState { + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 610, + maxHeight: 530, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + }, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackError, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " + "the only way you can have access to your funds is by using your backup key." + "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." + "\n\nPLEASE SAVE YOUR BACKUP KEY.", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + int count = 0; + Navigator.of(context).popUntil((_) => count++ >= 2); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "View Backup Key", + onPressed: () async { + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + await Navigator.of(context) + .pushNamed(DeleteWalletKeysPopup.routeName, + arguments: Tuple2( + widget.walletId, + words, + )); + }, + ), + ], + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart index e2ab4fa86..087629673 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -12,7 +13,6 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import '../../../../../providers/desktop/storage_crypto_handler_provider.dart'; @@ -41,89 +41,6 @@ class _DesktopDeleteWalletDialog bool hidePassword = true; bool _continueEnabled = false; - Future attentionDelete() async { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) => DesktopDialog( - maxWidth: 610, - maxHeight: 530, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - DesktopDialogCloseButton( - onPressedOverride: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); - }, - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 26), - child: Column( - children: [ - Text( - "Attention!", - style: STextStyles.desktopH2(context), - ), - const SizedBox( - height: 16, - ), - RoundedContainer( - color: Theme.of(context) - .extension()! - .snackBarBackError, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Text( - "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " - "the only way you can have access to your funds is by using your backup key." - "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." - "\n\nPLEASE SAVE YOUR BACKUP KEY.", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ), - ), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SecondaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "Cancel", - onPressed: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); - }, - ), - const SizedBox(width: 16), - PrimaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "View Backup Key", - onPressed: () {}, - ), - ], - ) - ], - ), - ), - ], - ), - ), - ); - } - @override void initState() { passwordController = TextEditingController(); @@ -273,7 +190,10 @@ class _DesktopDeleteWalletDialog if (mounted) { Navigator.of(context).pop(); - attentionDelete(); + await Navigator.of(context).pushNamed( + DesktopAttentionDeleteWallet.routeName, + arguments: widget.walletId, + ); } } else { unawaited( diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 77b5e7d11..cbc4cb343 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -92,6 +92,8 @@ import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; @@ -1193,6 +1195,51 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopAttentionDeleteWallet.routeName: + if (args is String) { + return FadePageRoute( + DesktopAttentionDeleteWallet( + walletId: args, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DeleteWalletKeysPopup.routeName: + if (args is Tuple2>) { + return FadePageRoute( + DeleteWalletKeysPopup( + walletId: args.item1, + words: args.item2, + ), + RouteSettings( + name: settings.name, + ), + ); + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletKeysDesktopPopup( + // words: args, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case QRCodeDesktopPopupContent.routeName: if (args is String) { return FadePageRoute( From 5c7cb8a3c5d033dab7f0614ce090f5d783d53b77 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 12:18:35 -0700 Subject: [PATCH 155/225] WIP: unmounted widget --- .../sub_widgets/delete_wallet_keys_popup.dart | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index 5f46e0f2f..f70c2eadf 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -3,6 +3,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/global/wallets_service_provider.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -28,6 +31,15 @@ class DeleteWalletKeysPopup extends ConsumerStatefulWidget { } class _DeleteWalletKeysPopup extends ConsumerState { + late final String _walletId; + + @override + void initState() { + _walletId = widget.walletId; + + super.initState(); + } + @override Widget build(BuildContext context) { return DesktopDialog( @@ -113,6 +125,7 @@ class _DeleteWalletKeysPopup extends ConsumerState { context: context, builder: (context) { return DesktopDialog( + maxHeight: 350, child: Column( children: [ Row( @@ -128,13 +141,16 @@ class _DeleteWalletKeysPopup extends ConsumerState { ], ), Column( + crossAxisAlignment: + CrossAxisAlignment.center, children: [ Text( "Thanks! " "\n\nYour wallet will be deleted.", style: STextStyles.desktopH2(context), + textAlign: TextAlign.center, ), - const SizedBox(height: 20), + const SizedBox(height: 50), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -155,20 +171,43 @@ class _DeleteWalletKeysPopup extends ConsumerState { buttonHeight: ButtonHeight.xl, label: "Continue", onPressed: () async { - // final walletsInstance = - // ref.read(walletsChangeNotifierProvider); - // await ref - // .read(walletsServiceChangeNotifierProvider) - // .deleteWallet(walletId, true); - // - // if (mounted) { - // Navigator.of(context).popUntil( - // ModalRoute.withName(HomeView.routeName)); - // } + // int count = 0; + // Navigator.of(context) + // .popUntil( + // (_) => count++ >= 2); - // // wait for widget tree to dispose of any widgets watching the manager - // await Future.delayed(const Duration(seconds: 1)); - // walletsInstance.removeWallet(walletId: walletId); + final walletsInstance = ref.read( + walletsChangeNotifierProvider); + final manager = ref + .read( + walletsChangeNotifierProvider) + .getManager(_walletId); + + final _managerWalletId = + manager.walletId; + + await ref + .read( + walletsServiceChangeNotifierProvider) + .deleteWallet( + manager.walletName, + true); + + if (mounted) { + Navigator.of(context) + .popUntil( + ModalRoute.withName( + MyStackView + .routeName)); + } + + // wait for widget tree to dispose of any widgets watching the manager + await Future.delayed( + const Duration( + seconds: 1)); + walletsInstance.removeWallet( + walletId: + _managerWalletId); }), ], ) From d06c4862b1685b19f48686b8c3c27a7542fedd16 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 11:21:44 -0600 Subject: [PATCH 156/225] desktop exchange coin selection ui --- .../fixed_rate_pair_coin_selection_view.dart | 319 +++++++++--------- ...floating_rate_currency_selection_view.dart | 317 ++++++++--------- lib/pages/exchange_view/exchange_form.dart | 143 +++++++- 3 files changed, 459 insertions(+), 320 deletions(-) diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart index 80bdcda62..779d99306 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart @@ -8,6 +8,8 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -16,8 +18,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:tuple/tuple.dart'; -import 'package:stackwallet/utilities/util.dart'; - class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget { const FixedRateMarketPairCoinSelectionView({ Key? key, @@ -120,95 +120,106 @@ class _FixedRateMarketPairCoinSelectionViewState @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Choose a coin to exchange", - style: STextStyles.pageTitleH2(context), - ), - ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + final isDesktop = Util.isDesktop; + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a coin to exchange", + style: STextStyles.pageTitleH2(context), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!isDesktop) const SizedBox( height: 16, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: filter, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: filter, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, ), ), - const SizedBox( - height: 10, - ), - Text( - "Popular coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Builder(builder: (context) { + ), + const SizedBox( + height: 10, + ), + Text( + "Popular coins", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + Flexible( + child: Builder(builder: (context) { final items = _markets .where((e) => Coin.values .where((coin) => @@ -221,6 +232,7 @@ class _FixedRateMarketPairCoinSelectionViewState padding: const EdgeInsets.all(0), child: ListView.builder( shrinkWrap: true, + primary: isDesktop ? false : null, itemCount: items.length, itemBuilder: (builderContext, index) { final String ticker = @@ -282,84 +294,85 @@ class _FixedRateMarketPairCoinSelectionViewState ), ); }), - const SizedBox( - height: 20, - ), - Text( - "All coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Flexible( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: ListView.builder( - shrinkWrap: true, - itemCount: _markets.length, - itemBuilder: (builderContext, index) { - final String ticker = - isFrom ? _markets[index].from : _markets[index].to; + ), + const SizedBox( + height: 20, + ), + Text( + "All coins", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + Flexible( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: ListView.builder( + shrinkWrap: true, + primary: isDesktop ? false : null, + itemCount: _markets.length, + itemBuilder: (builderContext, index) { + final String ticker = + isFrom ? _markets[index].from : _markets[index].to; - final tuple = _imageUrlAndNameFor(ticker); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: GestureDetector( - onTap: () { - Navigator.of(context).pop(ticker); - }, - child: RoundedWhiteContainer( - child: Row( - children: [ - SizedBox( + final tuple = _imageUrlAndNameFor(ticker); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(ticker); + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: SvgPicture.network( + tuple.item1, width: 24, height: 24, - child: SvgPicture.network( - tuple.item1, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ), + placeholderBuilder: (_) => + const LoadingIndicator(), ), - const SizedBox( - width: 10, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tuple.item2, + style: STextStyles.largeMedium14(context), + ), + const SizedBox( + height: 2, + ), + Text( + ticker.toUpperCase(), + style: STextStyles.smallMed12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tuple.item2, - style: STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, - ), - Text( - ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart index e1c1addd2..eb7a99299 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart @@ -6,6 +6,8 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -13,8 +15,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class FloatingRateCurrencySelectionView extends StatefulWidget { const FloatingRateCurrencySelectionView({ Key? key, @@ -76,96 +76,109 @@ class _FloatingRateCurrencySelectionViewState @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Choose a coin to exchange", - style: STextStyles.pageTitleH2(context), - ), - ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + final isDesktop = Util.isDesktop; + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a coin to exchange", + style: STextStyles.pageTitleH2(context), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: [ + if (!isDesktop) const SizedBox( height: 16, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: filter, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: filter, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - filter(""); - }, - ), - ], - ), - ), - ) - : null, ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + filter(""); + }, + ), + ], + ), + ), + ) + : null, ), ), - const SizedBox( - height: 10, - ), - Text( - "Popular coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Builder(builder: (context) { + ), + const SizedBox( + height: 10, + ), + Text( + "Popular coins", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + Flexible( + child: Builder(builder: (context) { final items = _currencies .where((e) => Coin.values .where((coin) => @@ -177,6 +190,7 @@ class _FloatingRateCurrencySelectionViewState padding: const EdgeInsets.all(0), child: ListView.builder( shrinkWrap: true, + primary: isDesktop ? false : null, itemCount: items.length, itemBuilder: (builderContext, index) { return Padding( @@ -234,80 +248,81 @@ class _FloatingRateCurrencySelectionViewState ), ); }), - const SizedBox( - height: 20, - ), - Text( - "All coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Flexible( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: ListView.builder( - shrinkWrap: true, - itemCount: _currencies.length, - itemBuilder: (builderContext, index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: GestureDetector( - onTap: () { - Navigator.of(context).pop(_currencies[index]); - }, - child: RoundedWhiteContainer( - child: Row( - children: [ - SizedBox( + ), + const SizedBox( + height: 20, + ), + Text( + "All coins", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + Flexible( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: ListView.builder( + shrinkWrap: true, + primary: isDesktop ? false : null, + itemCount: _currencies.length, + itemBuilder: (builderContext, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(_currencies[index]); + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: SvgPicture.network( + _currencies[index].image, width: 24, height: 24, - child: SvgPicture.network( - _currencies[index].image, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ), + placeholderBuilder: (_) => + const LoadingIndicator(), ), - const SizedBox( - width: 10, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _currencies[index].name, + style: STextStyles.largeMedium14(context), + ), + const SizedBox( + height: 2, + ), + Text( + _currencies[index].ticker.toUpperCase(), + style: STextStyles.smallMed12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _currencies[index].name, - style: STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, - ), - Text( - _currencies[index].ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 148c74920..cdc6f16b9 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -39,6 +39,7 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:tuple/tuple.dart'; @@ -410,13 +411,65 @@ class _ExchangeFormState extends ConsumerState { } }).toList(growable: false); - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FloatingRateCurrencySelectionView( - currencies: tickers, - ), - ), - ); + final result = isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a coin to exchange", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: FloatingRateCurrencySelectionView( + currencies: tickers, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FloatingRateCurrencySelectionView( + currencies: tickers, + ), + ), + ); if (mounted && result is Currency) { onSelected(result); @@ -490,15 +543,73 @@ class _ExchangeFormState extends ConsumerState { .toList(growable: false); } - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: ref.read(availableChangeNowCurrenciesProvider).currencies, - isFrom: excludedTicker != fromTicker, - ), - ), - ); + final result = isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a coin to exchange", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: FixedRateMarketPairCoinSelectionView( + markets: marketsThatPairWithExcludedTicker, + currencies: ref + .read( + availableChangeNowCurrenciesProvider) + .currencies, + isFrom: excludedTicker != fromTicker, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FixedRateMarketPairCoinSelectionView( + markets: marketsThatPairWithExcludedTicker, + currencies: + ref.read(availableChangeNowCurrenciesProvider).currencies, + isFrom: excludedTicker != fromTicker, + ), + ), + ); if (mounted && result is String) { onSelected(result); From 3e9039ac90b724cb8886e17e6907c677fcdc36d2 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 11:26:34 -0600 Subject: [PATCH 157/225] show to and from tickers in exchange steps flow --- lib/pages/exchange_view/exchange_form.dart | 2 ++ .../desktop_exchange/exchange_steps/step_scaffold.dart | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index cdc6f16b9..7faa83a35 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -1030,6 +1030,7 @@ class _ExchangeFormState extends ConsumerState { maxHeight: double.infinity, child: StepScaffold( step: 2, + model: model, body: DesktopStep2( model: model, ), @@ -1057,6 +1058,7 @@ class _ExchangeFormState extends ConsumerState { maxHeight: double.infinity, child: StepScaffold( step: 1, + model: model, body: DesktopStep1( model: model, ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 62a293c27..8dbaf9580 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -8,10 +9,12 @@ class StepScaffold extends StatefulWidget { Key? key, required this.body, required this.step, + required this.model, }) : super(key: key); final Widget body; final int step; + final IncompleteExchangeModel model; @override State createState() => _StepScaffoldState(); @@ -19,10 +22,12 @@ class StepScaffold extends StatefulWidget { class _StepScaffoldState extends State { int currentStep = 0; + late final IncompleteExchangeModel model; @override void initState() { currentStep = widget.step; + model = widget.model; super.initState(); } @@ -38,7 +43,7 @@ class _StepScaffoldState extends State { iconSize: 23, ), Text( - "Exchange XXX to XXX", + "Exchange ${model.sendTicker.toUpperCase()} to ${model.receiveTicker.toUpperCase()}", style: STextStyles.desktopH3(context), ), ], From 3fca3d8b1e9d7ce15a5610c60342f94363e93cf5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 12:03:15 -0600 Subject: [PATCH 158/225] desktop exchange flow styling and choose addresses from addressbook functionality --- .../subwidgets/desktop_step_1.dart | 23 ++- .../subwidgets/desktop_step_2.dart | 139 +++++++++++++----- 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index 942747ea2..a97b722ef 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -97,8 +100,24 @@ class DesktopStep1 extends ConsumerWidget { child: PrimaryButton( label: "Next", buttonHeight: ButtonHeight.l, - onPressed: () { - // todo + onPressed: () async { + await showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 2, + model: model, + body: DesktopStep2( + model: model, + ), + ), + ); + }, + ); }, ), ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index e1c5a5620..38c01822e 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -2,10 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; -import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; -import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; -import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; @@ -24,6 +22,10 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import '../../../../models/contact_address_entry.dart'; +import '../../../../widgets/desktop/desktop_dialog.dart'; +import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; + class DesktopStep2 extends ConsumerStatefulWidget { const DesktopStep2({ Key? key, @@ -105,42 +107,96 @@ class _DesktopStep2State extends ConsumerState { } } - void selectRecipientFromAddressBook() { - ref.read(exchangeFlowIsActiveStateProvider.state).state = true; - Navigator.of(context) - .pushNamed( - AddressBookView.routeName, - ) - .then((_) { - ref.read(exchangeFlowIsActiveStateProvider.state).state = false; + void selectRecipientFromAddressBook() async { + final coin = coinFromTickerCaseInsensitive( + model.receiveTicker, + ); - final address = - ref.read(exchangeFromAddressBookAddressStateProvider.state).state; - if (address.isNotEmpty) { - _toController.text = address; - model.recipientAddress = _toController.text; - ref.read(exchangeFromAddressBookAddressStateProvider.state).state = ""; - } - }); + final entry = await showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), + ); + + if (entry != null) { + _toController.text = entry.address; + model.recipientAddress = entry.address; + setState(() {}); + } } - void selectRefundFromAddressBook() { - ref.read(exchangeFlowIsActiveStateProvider.state).state = true; - Navigator.of(context) - .pushNamed( - AddressBookView.routeName, - ) - .then( - (_) { - ref.read(exchangeFlowIsActiveStateProvider.state).state = false; - final address = - ref.read(exchangeFromAddressBookAddressStateProvider.state).state; - if (address.isNotEmpty) { - _refundController.text = address; - model.refundAddress = _refundController.text; - } - }, + void selectRefundFromAddressBook() async { + final coin = coinFromTickerCaseInsensitive( + model.sendTicker, ); + + final entry = await showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), + ); + + if (entry != null) { + _refundController.text = entry.address; + model.refundAddress = entry.address; + setState(() {}); + } } @override @@ -198,10 +254,12 @@ class _DesktopStep2State extends ConsumerState { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( "Enter exchange details", style: STextStyles.desktopTextMedium(context), + textAlign: TextAlign.center, ), const SizedBox( height: 8, @@ -209,6 +267,7 @@ class _DesktopStep2State extends ConsumerState { Text( "Enter your recipient and refund addresses", style: STextStyles.desktopTextExtraExtraSmall(context), + textAlign: TextAlign.center, ), const SizedBox( height: 20, @@ -231,7 +290,7 @@ class _DesktopStep2State extends ConsumerState { ], ), const SizedBox( - height: 4, + height: 10, ), ClipRRect( borderRadius: BorderRadius.circular( @@ -321,9 +380,10 @@ class _DesktopStep2State extends ConsumerState { ), ), const SizedBox( - height: 6, + height: 10, ), RoundedWhiteContainer( + borderColor: Theme.of(context).extension()!.background, child: Text( "This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.", style: STextStyles.desktopTextExtraExtraSmall(context), @@ -350,7 +410,7 @@ class _DesktopStep2State extends ConsumerState { ], ), const SizedBox( - height: 4, + height: 10, ), ClipRRect( borderRadius: BorderRadius.circular( @@ -380,6 +440,7 @@ class _DesktopStep2State extends ConsumerState { "Enter ${model.sendTicker.toUpperCase()} refund address", _refundFocusNode, context, + desktopMed: true, ).copyWith( contentPadding: const EdgeInsets.only( left: 16, @@ -441,7 +502,7 @@ class _DesktopStep2State extends ConsumerState { ), ), const SizedBox( - height: 6, + height: 10, ), RoundedWhiteContainer( borderColor: Theme.of(context).extension()!.background, From 7e8f0db96726f509bfb9f20ef18de7b1a5021ed8 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 12:07:15 -0600 Subject: [PATCH 159/225] long address layout fix --- .../sub_widgets/contact_list_item.dart | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart index 7acfaae9e..d7bfefb1f 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -87,35 +87,47 @@ class _ContactListItemState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - WalletInfoCoinIcon(coin: e.coin), - const SizedBox( - width: 12, - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${contactId == "default" ? e.other! : e.label} (${e.coin.ticker})", - style: STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + Flexible( + child: Row( + children: [ + WalletInfoCoinIcon(coin: e.coin), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "${contactId == "default" ? e.other! : e.label} (${e.coin.ticker})", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + Row( + children: [ + Flexible( + child: Text( + e.address, + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ), + ], + ), + ], ), - Text( - e.address, - style: STextStyles - .desktopTextExtraExtraSmall(context), - ), - ], - ), - ], + ), + ], + ), ), BlueTextButton( text: "Select wallet", From 04b982fb250156d3d96e3b44e955a2bd890dfd02 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 13:21:52 -0600 Subject: [PATCH 160/225] desktop exchange choose from stack address ui --- .../subwidgets/desktop_step_2.dart | 50 ++- .../subwidgets/desktop_choose_from_stack.dart | 329 ++++++++++++++++++ 2 files changed, 364 insertions(+), 15 deletions(-) create mode 100644 lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index 38c01822e..270b98fc7 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; -import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; @@ -64,12 +64,21 @@ class _DesktopStep2State extends ConsumerState { final coin = coinFromTickerCaseInsensitive( model.receiveTicker, ); - Navigator.of(context) - .pushNamed( - ChooseFromStackView.routeName, - arguments: coin, - ) - .then((value) async { + + showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack( + coin: coin, + ), + ), + ), + ).then((value) async { if (value is String) { final manager = ref.read(walletsChangeNotifierProvider).getManager(value); @@ -88,12 +97,21 @@ class _DesktopStep2State extends ConsumerState { final coin = coinFromTickerCaseInsensitive( model.sendTicker, ); - Navigator.of(context) - .pushNamed( - ChooseFromStackView.routeName, - arguments: coin, - ) - .then((value) async { + + showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) => DesktopDialog( + maxWidth: 720, + maxHeight: 670, + child: Padding( + padding: const EdgeInsets.all(32), + child: DesktopChooseFromStack( + coin: coin, + ), + ), + ), + ).then((value) async { if (value is String) { final manager = ref.read(walletsChangeNotifierProvider).getManager(value); @@ -366,7 +384,8 @@ class _DesktopStep2State extends ConsumerState { ? const ClipboardIcon() : const XIcon(), ), - if (_toController.text.isEmpty) + if (_toController.text.isEmpty && + isStackCoin(model.receiveTicker)) TextFieldIconButton( key: const Key("sendViewAddressBookButtonKey"), onTap: selectRecipientFromAddressBook, @@ -488,7 +507,8 @@ class _DesktopStep2State extends ConsumerState { ? const ClipboardIcon() : const XIcon(), ), - if (_refundController.text.isEmpty) + if (_refundController.text.isEmpty && + isStackCoin(model.sendTicker)) TextFieldIconButton( key: const Key("sendViewAddressBookButtonKey"), onTap: selectRefundFromAddressBook, diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart new file mode 100644 index 000000000..a3fb91f61 --- /dev/null +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart @@ -0,0 +1,329 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.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/animated_text.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; + +class DesktopChooseFromStack extends ConsumerStatefulWidget { + const DesktopChooseFromStack({ + Key? key, + required this.coin, + }) : super(key: key); + + final Coin coin; + + @override + ConsumerState createState() => + _DesktopChooseFromStackState(); +} + +class _DesktopChooseFromStackState + extends ConsumerState { + late final TextEditingController _searchController; + late final FocusNode searchFieldFocusNode; + + String _searchTerm = ""; + + List filter(List walletIds, String searchTerm) { + if (searchTerm.isEmpty) { + return walletIds; + } + + final List result = []; + for (final walletId in walletIds) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + if (manager.walletName.toLowerCase().contains(searchTerm.toLowerCase())) { + result.add(walletId); + } + } + + return result; + } + + @override + void initState() { + searchFieldFocusNode = FocusNode(); + _searchController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Choose from Stack", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 28, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Search", + searchFieldFocusNode, + context, + desktopMed: true, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 18, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 16, + ), + Flexible( + child: Builder( + builder: (context) { + List walletIds = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getWalletIdsFor(coin: widget.coin), + ), + ); + + if (walletIds.isEmpty) { + return Column( + children: [ + RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .background, + child: Center( + child: Text( + "No ${widget.coin.ticker.toUpperCase()} wallets", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ), + ], + ); + } + + walletIds = filter(walletIds, _searchTerm); + + return ListView.separated( + primary: false, + itemCount: walletIds.length, + separatorBuilder: (_, __) => const SizedBox( + height: 5, + ), + itemBuilder: (context, index) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletIds[index]))); + + return RoundedWhiteContainer( + borderColor: + Theme.of(context).extension()!.background, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 14, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + WalletInfoCoinIcon(coin: widget.coin), + const SizedBox( + width: 12, + ), + Text( + manager.walletName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + const Spacer(), + BalanceDisplay( + walletId: walletIds[index], + ), + const SizedBox( + width: 80, + ), + BlueTextButton( + text: "Select wallet", + onTap: () { + Navigator.of(context).pop(manager.walletId); + }, + ), + ], + ), + ); + }, + ); + }, + ), + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ); + } +} + +class BalanceDisplay extends ConsumerStatefulWidget { + const BalanceDisplay({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => _BalanceDisplayState(); +} + +class _BalanceDisplayState extends ConsumerState { + late final String walletId; + + Decimal? _cachedBalance; + + static const loopedText = [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance..." + ]; + + @override + void initState() { + walletId = widget.walletId; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + return FutureBuilder( + future: manager.availableBalance, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + _cachedBalance = snapshot.data; + } + + if (_cachedBalance == null) { + return AnimatedText( + stringsToLoopThrough: loopedText, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + ); + } else { + return Text( + "${Format.localizedStringAsFixed( + value: _cachedBalance!, + locale: locale, + decimalPlaces: 8, + )} ${manager.coin.ticker}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + textAlign: TextAlign.right, + ); + } + }, + ); + } +} From f75e4ea2faf8e71c385aa05732ace062ee90a324 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 14:52:41 -0600 Subject: [PATCH 161/225] desktop delete routing fixes --- .../sub_widgets/delete_wallet_button.dart | 113 ++++++++-- .../sub_widgets/delete_wallet_keys_popup.dart | 204 +++++++++--------- .../desktop_attention_delete_wallet.dart | 27 ++- 3 files changed, 207 insertions(+), 137 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart index 54f991c37..fd401d613 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'desktop_delete_wallet_dialog.dart'; - class DeleteWalletButton extends ConsumerStatefulWidget { const DeleteWalletButton({ Key? key, @@ -26,8 +26,6 @@ class _DeleteWalletButton extends ConsumerState { @override void initState() { walletId = widget.walletId; - final managerProvider = - ref.read(walletsChangeNotifierProvider).getManagerProvider(walletId); super.initState(); } @@ -38,25 +36,45 @@ class _DeleteWalletButton extends ConsumerState { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(1000), ), - onPressed: () { - showDialog( + onPressed: () async { + final shouldOpenDeleteDialog = await showDialog( context: context, - barrierDismissible: false, - builder: (context) => Navigator( - initialRoute: DesktopDeleteWalletDialog.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - RouteGenerator.generateRoute( - RouteSettings( - name: DesktopDeleteWalletDialog.routeName, - arguments: walletId, - ), - ) - ]; - }, - ), + barrierColor: Colors.transparent, + builder: (context) { + return DeletePopupButton( + onTap: () async { + Navigator.of(context).pop(true); + }, + ); + }, ); + + if (shouldOpenDeleteDialog == true) { + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => Navigator( + initialRoute: DesktopDeleteWalletDialog.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: DesktopDeleteWalletDialog.routeName, + arguments: walletId, + ), + ), + ]; + }, + ), + ); + + if (result == true) { + if (mounted) { + Navigator.of(context).pop(); + } + } + } }, child: Padding( padding: const EdgeInsets.symmetric( @@ -79,3 +97,54 @@ class _DeleteWalletButton extends ConsumerState { ); } } + +class DeletePopupButton extends StatefulWidget { + const DeletePopupButton({ + Key? key, + this.onTap, + }) : super(key: key); + + final VoidCallback? onTap; + + @override + State createState() => _DeletePopupButtonState(); +} + +class _DeletePopupButtonState extends State { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 24, + left: MediaQuery.of(context).size.width - 234, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onTap, + child: Container( + width: 210, + height: 70, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + color: Colors.red, + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + ), + child: Text( + "Delete", + style: STextStyles.desktopButtonSecondaryEnabled(context), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index f70c2eadf..6b638bb75 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; -import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_service_provider.dart'; +import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -61,8 +59,10 @@ class _DeleteWalletKeysPopup extends ConsumerState { ), DesktopDialogCloseButton( onPressedOverride: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); + Navigator.of( + context, + rootNavigator: true, + ).pop(); }, ), ], @@ -117,106 +117,17 @@ class _DeleteWalletKeysPopup extends ConsumerState { child: PrimaryButton( label: "Continue", onPressed: () async { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); - - unawaited( - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 350, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - DesktopDialogCloseButton( - onPressedOverride: () { - int count = 0; - Navigator.of(context) - .popUntil((_) => count++ >= 2); - }, - ), - ], - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Text( - "Thanks! " - "\n\nYour wallet will be deleted.", - style: STextStyles.desktopH2(context), - textAlign: TextAlign.center, - ), - const SizedBox(height: 50), - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - SecondaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "Cancel", - onPressed: () { - int count = 0; - Navigator.of(context) - .popUntil( - (_) => count++ >= 2); - }), - const SizedBox(width: 16), - PrimaryButton( - width: 250, - buttonHeight: ButtonHeight.xl, - label: "Continue", - onPressed: () async { - // int count = 0; - // Navigator.of(context) - // .popUntil( - // (_) => count++ >= 2); - - final walletsInstance = ref.read( - walletsChangeNotifierProvider); - final manager = ref - .read( - walletsChangeNotifierProvider) - .getManager(_walletId); - - final _managerWalletId = - manager.walletId; - - await ref - .read( - walletsServiceChangeNotifierProvider) - .deleteWallet( - manager.walletName, - true); - - if (mounted) { - Navigator.of(context) - .popUntil( - ModalRoute.withName( - MyStackView - .routeName)); - } - - // wait for widget tree to dispose of any widgets watching the manager - await Future.delayed( - const Duration( - seconds: 1)); - walletsInstance.removeWallet( - walletId: - _managerWalletId); - }), - ], - ) - ], - ), - ], - ), - ); - }), + await Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (context) { + return ConfirmDelete( + walletId: _walletId, + ); + }, + settings: const RouteSettings( + name: "/desktopConfirmDelete", + ), + ), ); }, ), @@ -232,3 +143,86 @@ class _DeleteWalletKeysPopup extends ConsumerState { ); } } + +class ConfirmDelete extends ConsumerStatefulWidget { + const ConfirmDelete({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => _ConfirmDeleteState(); +} + +class _ConfirmDeleteState extends ConsumerState { + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxHeight: 350, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Thanks! " + "\n\nYour wallet will be deleted.", + style: STextStyles.desktopH2(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + const SizedBox(width: 16), + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.xl, + label: "Continue", + onPressed: () async { + final walletsInstance = + ref.read(walletsChangeNotifierProvider); + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + final _managerWalletId = manager.walletId; + // + await ref + .read(walletsServiceChangeNotifierProvider) + .deleteWallet(manager.walletName, true); + + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(true); + } + + // wait for widget tree to dispose of any widgets watching the manager + await Future.delayed(const Duration(seconds: 1)); + walletsInstance.removeWallet(walletId: _managerWalletId); + }, + ), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart index 30546f60b..6eb04502e 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart @@ -41,8 +41,10 @@ class _DesktopAttentionDeleteWallet children: [ DesktopDialogCloseButton( onPressedOverride: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); + Navigator.of( + context, + rootNavigator: true, + ).pop(); }, ), ], @@ -87,8 +89,10 @@ class _DesktopAttentionDeleteWallet buttonHeight: ButtonHeight.xl, label: "Cancel", onPressed: () { - int count = 0; - Navigator.of(context).popUntil((_) => count++ >= 2); + Navigator.of( + context, + rootNavigator: true, + ).pop(); }, ), const SizedBox(width: 16), @@ -102,12 +106,15 @@ class _DesktopAttentionDeleteWallet .getManager(widget.walletId) .mnemonic; - await Navigator.of(context) - .pushNamed(DeleteWalletKeysPopup.routeName, - arguments: Tuple2( - widget.walletId, - words, - )); + if (mounted) { + await Navigator.of(context).pushNamed( + DeleteWalletKeysPopup.routeName, + arguments: Tuple2( + widget.walletId, + words, + ), + ); + } }, ), ], From 8a6025db4b308756eaf5cf91d50cdbd1e545a801 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 15:29:09 -0600 Subject: [PATCH 162/225] place node url and port on their own line --- .../add_edit_node_view.dart | 195 +++++++++--------- 1 file changed, 93 insertions(+), 102 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 606c4481f..9062314f0 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -494,6 +494,7 @@ class _AddEditNodeViewState extends ConsumerState { condition: isDesktop, builder: (child) => DesktopDialog( maxWidth: 580, + maxHeight: 500, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -830,110 +831,100 @@ class _NodeFormState extends ConsumerState { const SizedBox( height: 8, ), - Row( - children: [ - Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - key: const Key("addCustomNodeNodeAddressFieldKey"), - readOnly: widget.readOnly, - enabled: enableField(_hostController), - controller: _hostController, - focusNode: _hostFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - (widget.coin != Coin.monero && - widget.coin != Coin.wownero && - widget.coin != Coin.epicCash) - ? "IP address" - : "Url", - _hostFocusNode, - context, - ).copyWith( - suffixIcon: - !widget.readOnly && _hostController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _hostController.text = ""; - _updateState(); - }, - ), - ], - ), - ), - ) - : null, - ), - onChanged: (newValue) { - _updateState(); - setState(() {}); - }, - ), - ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + key: const Key("addCustomNodeNodeAddressFieldKey"), + readOnly: widget.readOnly, + enabled: enableField(_hostController), + controller: _hostController, + focusNode: _hostFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + (widget.coin != Coin.monero && + widget.coin != Coin.wownero && + widget.coin != Coin.epicCash) + ? "IP address" + : "Url", + _hostFocusNode, + context, + ).copyWith( + suffixIcon: !widget.readOnly && _hostController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _hostController.text = ""; + _updateState(); + }, + ), + ], + ), + ), + ) + : null, ), - const SizedBox( - width: 12, + onChanged: (newValue) { + _updateState(); + setState(() {}); + }, + ), + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + key: const Key("addCustomNodeNodePortFieldKey"), + readOnly: widget.readOnly, + enabled: enableField(_portController), + controller: _portController, + focusNode: _portFocusNode, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + keyboardType: TextInputType.number, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Port", + _portFocusNode, + context, + ).copyWith( + suffixIcon: !widget.readOnly && _portController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _portController.text = ""; + _updateState(); + }, + ), + ], + ), + ), + ) + : null, ), - Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - key: const Key("addCustomNodeNodePortFieldKey"), - readOnly: widget.readOnly, - enabled: enableField(_portController), - controller: _portController, - focusNode: _portFocusNode, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - keyboardType: TextInputType.number, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Port", - _portFocusNode, - context, - ).copyWith( - suffixIcon: - !widget.readOnly && _portController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _portController.text = ""; - _updateState(); - }, - ), - ], - ), - ), - ) - : null, - ), - onChanged: (newValue) { - _updateState(); - setState(() {}); - }, - ), - ), - ), - ], + onChanged: (newValue) { + _updateState(); + setState(() {}); + }, + ), ), const SizedBox( height: 8, From 66950ccc5059bbf04187180fb7c60665cd892c41 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 14:40:11 -0700 Subject: [PATCH 163/225] delete popup styled --- .../sub_widgets/delete_wallet_button.dart | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart index fd401d613..f2553c2da 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -129,16 +129,30 @@ class _DeletePopupButtonState extends State { borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), - color: Colors.red, + color: Theme.of(context).extension()!.popupBG, boxShadow: [ Theme.of(context) .extension()! .standardBoxShadow, ], ), - child: Text( - "Delete", - style: STextStyles.desktopButtonSecondaryEnabled(context), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(width: 24), + SvgPicture.asset( + Assets.svg.trash, + ), + const SizedBox(width: 14), + Text( + "Delete wallet", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + ), + ], ), ), ), From e099089d04a3154fbb1cd61fc331d0435e6aeba4 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 14:58:03 -0700 Subject: [PATCH 164/225] loading indicator and delay --- .../desktop_delete_wallet_dialog.dart | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart index 087629673..6729d33b9 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import '../../../../../providers/desktop/storage_crypto_handler_provider.dart'; @@ -177,11 +178,35 @@ class _DesktopDeleteWalletDialog label: "Continue", onPressed: _continueEnabled ? () async { + // add loading indicator + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future.delayed( + const Duration(seconds: 1)); + final verified = await ref .read(storageCryptoHandlerProvider) .verifyPassphrase(passwordController.text); if (verified) { + Navigator.of(context, rootNavigator: true) + .pop(); + final words = await ref .read(walletsChangeNotifierProvider) .getManager(widget.walletId) @@ -190,12 +215,20 @@ class _DesktopDeleteWalletDialog if (mounted) { Navigator.of(context).pop(); - await Navigator.of(context).pushNamed( - DesktopAttentionDeleteWallet.routeName, - arguments: widget.walletId, + unawaited( + Navigator.of(context).pushNamed( + DesktopAttentionDeleteWallet.routeName, + arguments: widget.walletId, + ), ); } } else { + Navigator.of(context, rootNavigator: true) + .pop(); + + await Future.delayed( + const Duration(milliseconds: 300)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, From c935c590c7c5635487503e4587f34d94e8f0ae77 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Nov 2022 16:00:00 -0600 Subject: [PATCH 165/225] desktop exchange flow tweaks and show QR code --- lib/pages/exchange_view/exchange_form.dart | 2 + .../subwidgets/desktop_step_1.dart | 1 + .../subwidgets/desktop_step_2.dart | 30 +++++++-- .../subwidgets/desktop_step_3.dart | 67 ++++++++++++------- .../subwidgets/desktop_step_4.dart | 49 +++++++++++++- .../subwidgets/desktop_step_item.dart | 1 + .../subwidgets/desktop_trade_history.dart | 2 + 7 files changed, 121 insertions(+), 31 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 7faa83a35..7f0d9b5d2 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -1024,6 +1024,7 @@ class _ExchangeFormState extends ConsumerState { if (isDesktop) { await showDialog( context: context, + barrierDismissible: false, builder: (context) { return DesktopDialog( maxWidth: 720, @@ -1052,6 +1053,7 @@ class _ExchangeFormState extends ConsumerState { if (isDesktop) { await showDialog( context: context, + barrierDismissible: false, builder: (context) { return DesktopDialog( maxWidth: 720, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index a97b722ef..031dc0649 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -104,6 +104,7 @@ class DesktopStep1 extends ConsumerWidget { await showDialog( context: context, barrierColor: Colors.transparent, + barrierDismissible: false, builder: (context) { return DesktopDialog( maxWidth: 720, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index 270b98fc7..525d561fc 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; @@ -13,6 +16,8 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; @@ -22,10 +27,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import '../../../../models/contact_address_entry.dart'; -import '../../../../widgets/desktop/desktop_dialog.dart'; -import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; - class DesktopStep2 extends ConsumerStatefulWidget { const DesktopStep2({ Key? key, @@ -552,8 +553,25 @@ class _DesktopStep2State extends ConsumerState { child: PrimaryButton( label: "Next", buttonHeight: ButtonHeight.l, - onPressed: () { - // todo + onPressed: () async { + await showDialog( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 3, + model: model, + body: DesktopStep3( + model: model, + ), + ), + ); + }, + ); }, ), ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart index 65b6ed2b3..284416545 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; import 'package:stackwallet/providers/exchange/exchange_provider.dart'; @@ -16,10 +17,11 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; class DesktopStep3 extends ConsumerStatefulWidget { const DesktopStep3({ @@ -76,14 +78,15 @@ class _DesktopStep3State extends ConsumerState { Navigator.of(context).pop(); } - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to create trade", - message: response.exception?.toString(), + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => SimpleDesktopDialog( + title: "Failed to create trade", + message: response.exception?.toString() ?? ""), ), - )); + ); return; } @@ -106,22 +109,40 @@ class _DesktopStep3State extends ConsumerState { Navigator.of(context).pop(); } - unawaited(NotificationApi.showNotification( - changeNowId: model.trade!.tradeId, - title: status, - body: "Trade ID ${model.trade!.tradeId}", - walletId: "", - iconAssetName: Assets.svg.arrowRotate, - date: model.trade!.timestamp, - shouldWatchForUpdates: true, - coinName: "coinName", - )); + unawaited( + NotificationApi.showNotification( + changeNowId: model.trade!.tradeId, + title: status, + body: "Trade ID ${model.trade!.tradeId}", + walletId: "", + iconAssetName: Assets.svg.arrowRotate, + date: model.trade!.timestamp, + shouldWatchForUpdates: true, + coinName: "coinName", + ), + ); if (mounted) { - unawaited(Navigator.of(context).pushNamed( - Step4View.routeName, - arguments: model, - )); + unawaited( + showDialog( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 720, + maxHeight: double.infinity, + child: StepScaffold( + step: 4, + model: model, + body: DesktopStep4( + model: model, + ), + ), + ); + }, + ), + ); } } diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index ba9838086..7747f570f 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -2,12 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; @@ -148,7 +150,7 @@ class _DesktopStep4State extends ConsumerState { DesktopStepItem( label: "Amount", value: - "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", ), Container( height: 1, @@ -208,7 +210,50 @@ class _DesktopStep4State extends ConsumerState { label: "Show QR code", buttonHeight: ButtonHeight.l, onPressed: () { - // todo + showDialog( + context: context, + barrierColor: Colors.transparent, + barrierDismissible: true, + builder: (_) { + return DesktopDialog( + maxHeight: 720, + maxWidth: 720, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Send ${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker} to this address", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 48, + ), + Center( + child: QrImage( + // TODO: grab coin uri scheme from somewhere + // data: "${coin.uriScheme}:$receivingAddress", + data: model.trade!.payInAddress, + size: 290, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + const SizedBox( + height: 48, + ), + SecondaryButton( + label: "Cancel", + width: 310, + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ], + ), + ); + }, + ); }, ), ), diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart index 7c777c2dd..323517e13 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart @@ -24,6 +24,7 @@ class DesktopStepItem extends StatelessWidget { child: ConditionalParent( condition: vertical, builder: (child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ child, const SizedBox( diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart index 40eeb8c1b..41bf4246a 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -31,6 +31,8 @@ class _DesktopTradeHistoryState extends ConsumerState { if (hasHistory) { return ListView.separated( + shrinkWrap: true, + primary: false, itemBuilder: (context, index) { return TradeCard( key: Key("tradeCard_${trades[index].uuid}"), From a10958b12d02f0073354cb4402504f46ec75efa9 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 15:08:31 -0700 Subject: [PATCH 166/225] delete popup rounded corner fix --- .../wallet_view/sub_widgets/delete_wallet_button.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart index f2553c2da..a2071e7d6 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart @@ -127,7 +127,7 @@ class _DeletePopupButtonState extends State { height: 70, decoration: BoxDecoration( borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + Constants.size.circularBorderRadius * 2, ), color: Theme.of(context).extension()!.popupBG, boxShadow: [ From 7a650c78d39006620e6ba4f96069feb42f6e1153 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 15:17:26 -0700 Subject: [PATCH 167/225] loading indicator and delay for wallet keys --- .../unlock_wallet_keys_desktop.dart | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index 23360c98c..739a3ebc4 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -16,6 +16,7 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; class UnlockWalletKeysDesktop extends ConsumerStatefulWidget { @@ -201,11 +202,32 @@ class _UnlockWalletKeysDesktopState enabled: continueEnabled, onPressed: continueEnabled ? () async { + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future.delayed( + const Duration(seconds: 1)); + final verified = await ref .read(storageCryptoHandlerProvider) .verifyPassphrase(passwordController.text); if (verified) { + Navigator.of(context, rootNavigator: true).pop(); + final words = await ref .read(walletsChangeNotifierProvider) .getManager(widget.walletId) @@ -219,6 +241,11 @@ class _UnlockWalletKeysDesktopState ); } } else { + Navigator.of(context, rootNavigator: true).pop(); + + await Future.delayed( + const Duration(milliseconds: 300)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, From 675977c787d9d987819852199fefe32882e7bfe6 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 20:33:38 -0700 Subject: [PATCH 168/225] copy to clipboard added to wallet keys dialog --- .../sub_widgets/delete_wallet_keys_popup.dart | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index 6b638bb75..70f4a3e13 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -1,9 +1,15 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_service_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -16,10 +22,12 @@ class DeleteWalletKeysPopup extends ConsumerStatefulWidget { Key? key, required this.walletId, required this.words, + this.clipboardInterface = const ClipboardWrapper(), }) : super(key: key); final String walletId; final List words; + final ClipboardInterface clipboardInterface; static const String routeName = "/desktopDeleteWalletKeysPopup"; @@ -30,10 +38,14 @@ class DeleteWalletKeysPopup extends ConsumerStatefulWidget { class _DeleteWalletKeysPopup extends ConsumerState { late final String _walletId; + late final List _words; + late final ClipboardInterface _clipboardInterface; @override void initState() { _walletId = widget.walletId; + _words = widget.words; + _clipboardInterface = widget.clipboardInterface; super.initState(); } @@ -96,12 +108,28 @@ class _DeleteWalletKeysPopup extends ConsumerState { padding: const EdgeInsets.symmetric( horizontal: 32, ), - child: MnemonicTable( - words: widget.words, - isDesktop: true, - itemBorderColor: Theme.of(context) - .extension()! - .buttonBackSecondary, + child: RawMaterialButton( + hoverColor: Colors.transparent, + onPressed: () async { + await _clipboardInterface.setData( + ClipboardData(text: _words.join(" ")), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + child: MnemonicTable( + words: widget.words, + isDesktop: true, + itemBorderColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + ), ), ), const SizedBox( From 6384d66308066645500f516eca9784163a8285d0 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 21 Nov 2022 20:34:36 -0700 Subject: [PATCH 169/225] added delays for floatingFlushBar in settings change password --- .../home/settings_menu/security_settings.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart index f6762afa1..ff7537126 100644 --- a/lib/pages_desktop_specific/home/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/security_settings.dart @@ -61,6 +61,8 @@ class _SecuritySettings extends ConsumerState { if (verified) { if (pwNew != pwNewRepeat) { + await Future.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -77,6 +79,8 @@ class _SecuritySettings extends ConsumerState { ); if (success) { + await Future.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.success, @@ -86,6 +90,8 @@ class _SecuritySettings extends ConsumerState { ); return true; } else { + await Future.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -97,6 +103,8 @@ class _SecuritySettings extends ConsumerState { } } } else { + await Future.delayed(const Duration(seconds: 1)); + unawaited( showFloatingFlushBar( type: FlushBarType.warning, From b32e15a3ea1176eb4f77920fa97b46f1d95c3828 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 07:13:03 -0600 Subject: [PATCH 170/225] desktop login on enter pressed --- lib/pages_desktop_specific/desktop_login_view.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index f865fad47..eb5dec18a 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -165,6 +165,12 @@ class _DesktopLoginViewState extends ConsumerState { obscureText: hidePassword, enableSuggestions: false, autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (_continueEnabled) { + login(); + } + }, decoration: standardInputDecoration( "Enter password", passwordFocusNode, From b512b2cefb4663a0075b749c2cc2d4126060717b Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 07:15:08 -0600 Subject: [PATCH 171/225] consistent decimal places on firo balance selection sheet --- .../firo_balance_selection_sheet.dart | 4 ++-- .../wallet_balance_toggle_sheet.dart | 11 ++++----- lib/utilities/constants.dart | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart index e639a8cf8..d6de3c6ee 100644 --- a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart @@ -161,7 +161,7 @@ class _FiroBalanceSelectionSheetState ConnectionState.done && snapshot.hasData) { return Text( - "${snapshot.data!} ${manager.coin.ticker}", + "${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}", style: STextStyles.itemSubtitle(context), textAlign: TextAlign.left, ); @@ -251,7 +251,7 @@ class _FiroBalanceSelectionSheetState ConnectionState.done && snapshot.hasData) { return Text( - "${snapshot.data!} ${manager.coin.ticker}", + "${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}", style: STextStyles.itemSubtitle(context), textAlign: TextAlign.left, ); diff --git a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart index c9ff64393..74308f2e8 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart @@ -3,14 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; - class WalletBalanceToggleSheet extends ConsumerWidget { const WalletBalanceToggleSheet({ Key? key, @@ -153,7 +152,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -195,7 +194,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -287,7 +286,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) @@ -329,7 +328,7 @@ class WalletBalanceToggleSheet extends ConsumerWidget { snapshot.hasData && snapshot.data != null) { return Text( - "${snapshot.data!}", + "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", style: STextStyles.itemSubtitle12(context) .copyWith( color: Theme.of(context) diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index e27fbaa3d..0a062de67 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -40,6 +40,30 @@ abstract class Constants { static const int currentHiveDbVersion = 3; + static int decimalPlacesForCoin(Coin coin) { + switch (coin) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.litecoinTestNet: + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + case Coin.dogecoin: + case Coin.firo: + case Coin.bitcoinTestNet: + case Coin.dogecoinTestNet: + case Coin.firoTestNet: + case Coin.epicCash: + case Coin.namecoin: + return decimalPlaces; + + case Coin.wownero: + return decimalPlacesWownero; + + case Coin.monero: + return decimalPlacesMonero; + } + } + static List possibleLengthsForCoin(Coin coin) { final List values = []; switch (coin) { From 7afe6940f9dc5f20f08eb59e606d8e4cbb452cd4 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 08:07:22 -0600 Subject: [PATCH 172/225] desktop trade history details updated --- .../exchange_view/trade_details_view.dart | 186 ++++++++++++++---- .../subwidgets/desktop_trade_history.dart | 124 +++++++++++- lib/widgets/trade_card.dart | 134 +++++++------ 3 files changed, 335 insertions(+), 109 deletions(-) diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 602d588da..f28d1d617 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -206,16 +206,57 @@ class _TradeDetailsViewState extends ConsumerState { padding: const EdgeInsets.only( right: 12, ), - child: RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context).extension()!.background - : null, - padding: const EdgeInsets.all(0), - child: ListView( - primary: false, - shrinkWrap: true, - children: children, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RoundedWhiteContainer( + borderColor: + Theme.of(context).extension()!.background, + padding: const EdgeInsets.all(0), + child: ListView( + primary: false, + shrinkWrap: true, + children: children, + ), + ), + if (isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) + const SizedBox( + height: 32, + ), + if (isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) + SecondaryButton( + label: "Send from Stack", + buttonHeight: ButtonHeight.l, + onPressed: () { + final amount = sendAmount; + final address = trade.payInAddress; + + final coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + Navigator.of(context).pushNamed( + SendFromView.routeName, + arguments: Tuple4( + coin, + amount, + address, + trade, + ), + ); + }, + ), + const SizedBox( + height: 32, + ), + ], ), ), ), @@ -350,33 +391,94 @@ class _TradeDetailsViewState extends ConsumerState { padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), - color: Theme.of(context) - .extension()! - .warningBackground, - child: RichText( - text: TextSpan( - text: - "You must send at least ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}. ", - style: STextStyles.label700(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, - ), - children: [ - TextSpan( - text: - "If you send less than ${sendAmount.toStringAsFixed( - trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", - style: STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + color: isDesktop + ? Theme.of(context).extension()!.popupBG + : Theme.of(context) + .extension()! + .warningBackground, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Amount", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + const SizedBox( + height: 2, + ), + Text( + "${trade.payInAmount} ${trade.payInCurrency.toUpperCase()}", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], ), - ), - ]), + IconCopyButton( + data: trade.payInAmount, + ), + ], + ), + const SizedBox( + height: 6, + ), + child, + ], + ), + child: RichText( + text: TextSpan( + text: + "You must send at least ${sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, + )} ${trade.payInCurrency.toUpperCase()}. ", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed) + : STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + children: [ + TextSpan( + text: + "If you send less than ${sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" + ? 12 + : 8, + )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed) + : STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + ), + ]), + ), ), ), if (sentFromStack) @@ -1035,12 +1137,12 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - if (isStackCoin(trade.payInCurrency) && + if (!isDesktop) + const SizedBox( + height: 12, + ), + if (!isDesktop && + isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || trade.status == "waiting" || diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart index 41bf4246a..a8f825911 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -10,7 +10,10 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/trade_card.dart'; -import 'package:tuple/tuple.dart'; + +import '../../../route_generator.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; class DesktopTradeHistory extends ConsumerStatefulWidget { const DesktopTradeHistory({Key? key}) : super(key: key); @@ -64,19 +67,122 @@ class _DesktopTradeHistoryState extends ConsumerState { final tx = txData.getAllTransactions()[txid]; if (mounted) { - unawaited( - Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4( - tradeId, tx, walletIds.first, manager.walletName), + await showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3(context), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: tx, + walletName: manager.walletName, + walletId: walletIds.first, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, ), ); } } else { unawaited( - Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4(tradeId, null, walletIds?.first, null), + showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3(context), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: null, + walletName: null, + walletId: walletIds?.first, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, + ), ), ); } diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index 0ac8e9346..5a14a0777 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class TradeCard extends ConsumerWidget { @@ -49,68 +50,85 @@ class TradeCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return GestureDetector( - onTap: onTap, - child: RoundedWhiteContainer( - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(32), - ), - child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus( - trade.status, - context, + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: onTap, + child: RoundedWhiteContainer( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + ), + child: Center( + child: SvgPicture.asset( + _fetchIconAssetForStatus( + trade.status, + context, + ), + width: 32, + height: 32, ), - width: 32, - height: 32, ), ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - Text( - "${Util.isDesktop ? "-" : ""}${Decimal.tryParse(trade.payInAmount) ?? "..."} ${trade.payInCurrency.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - const SizedBox( - height: 2, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - trade.exchangeName, - style: STextStyles.label(context), - ), - Text( - Format.extractDateFrom( - trade.timestamp.millisecondsSinceEpoch ~/ 1000), - style: STextStyles.label(context), - ), - ], - ), - ], + const SizedBox( + width: 12, ), - ) - ], + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + Text( + "${isDesktop ? "-" : ""}${Decimal.tryParse(trade.payInAmount) ?? "..."} ${trade.payInCurrency.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + const SizedBox( + height: 2, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!isDesktop) + Text( + trade.exchangeName, + style: STextStyles.label(context), + ), + Text( + Format.extractDateFrom( + trade.timestamp.millisecondsSinceEpoch ~/ 1000), + style: STextStyles.label(context), + ), + if (isDesktop) + Text( + trade.exchangeName, + style: STextStyles.label(context), + ), + ], + ), + ], + ), + ) + ], + ), ), ), ); From 6552fc913db3d66782eddc02c54f6eb155e7a730 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 09:11:18 -0600 Subject: [PATCH 173/225] WIP send auth for trade transactions --- lib/pages/exchange_view/send_from_view.dart | 281 ++++++++++++++++-- .../send_view/confirm_transaction_view.dart | 17 +- .../sub_widgets/desktop_auth_send.dart | 32 +- 3 files changed, 301 insertions(+), 29 deletions(-) diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index c87175955..59675e4e4 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -7,6 +7,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; @@ -19,13 +20,18 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_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/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import '../../pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; + class SendFromView extends ConsumerStatefulWidget { const SendFromView({ Key? key, @@ -90,21 +96,68 @@ class _SendFromViewState extends ConsumerState { final walletIds = ref.watch(walletsChangeNotifierProvider .select((value) => value.getWalletIdsFor(coin: coin))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Send from", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Send from Stack", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: false, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: child, + ), + ], + ), ), - title: Text( - "Send from", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -112,15 +165,23 @@ class _SendFromViewState extends ConsumerState { children: [ Text( "You need to send ${formatAmount(amount, coin)} ${coin.ticker}", - style: STextStyles.itemSubtitle(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), ], ), const SizedBox( height: 16, ), - Expanded( + ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded( + child: child, + ), child: ListView.builder( + primary: isDesktop ? false : null, + shrinkWrap: isDesktop, itemCount: walletIds.length, itemBuilder: (context, index) { return Padding( @@ -339,10 +400,67 @@ class _SendFromCardState extends ConsumerState { Constants.size.circularBorderRadius, ), ), - onPressed: () => _send( - manager, - shouldSendPublicFiroFunds: false, - ), + onPressed: () async { + final dynamic unlocked; + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && unlocked && mounted) { + unawaited( + _send( + manager, + shouldSendPublicFiroFunds: false, + ), + ); + } + }, child: Container( color: Colors.transparent, child: Padding( @@ -418,10 +536,67 @@ class _SendFromCardState extends ConsumerState { Constants.size.circularBorderRadius, ), ), - onPressed: () => _send( - manager, - shouldSendPublicFiroFunds: true, - ), + onPressed: () async { + final dynamic unlocked; + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && unlocked && mounted) { + unawaited( + _send( + manager, + shouldSendPublicFiroFunds: true, + ), + ); + } + }, child: Container( color: Colors.transparent, child: Padding( @@ -504,7 +679,63 @@ class _SendFromCardState extends ConsumerState { Constants.size.circularBorderRadius, ), ), - onPressed: () => _send(manager), + onPressed: () async { + final dynamic unlocked; + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && unlocked && mounted) { + unawaited( + _send(manager), + ); + } + }, child: child, ), child: Row( diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 8f7afb0bb..276203804 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -37,6 +37,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { required this.transactionInfo, required this.walletId, this.routeOnSuccessName = WalletView.routeName, + this.isTradeTransaction = false, }) : super(key: key); static const String routeName = "/confirmTransactionView"; @@ -44,6 +45,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { final Map transactionInfo; final String walletId; final String routeOnSuccessName; + final bool isTradeTransaction; @override ConsumerState createState() => @@ -833,8 +835,19 @@ class _ConfirmTransactionViewState ); } - if (unlocked is bool && unlocked && mounted) { - unawaited(_attemptSend(context)); + if (mounted) { + if (unlocked == true) { + unawaited(_attemptSend(context)); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", + context: context), + ); + } } }, ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index 9f863c8a4..a8d1ea497 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -10,6 +12,9 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; +import '../../../../../notifications/show_flush_bar.dart'; +import '../../../../../widgets/loading_indicator.dart'; + class DesktopAuthSend extends ConsumerStatefulWidget { const DesktopAuthSend({Key? key}) : super(key: key); @@ -155,12 +160,35 @@ class _DesktopAuthSendState extends ConsumerState { label: "Confirm", buttonHeight: ButtonHeight.l, onPressed: () async { - // TODO show spinner while verifying passphrase + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future.delayed(const Duration(seconds: 1)); final passwordIsValid = await verifyPassphrase(); if (mounted) { - Navigator.of(context).pop(passwordIsValid); + Navigator.of(context).pop(); + Navigator.of( + context, + rootNavigator: true, + ).pop(passwordIsValid); + await Future.delayed(const Duration( + milliseconds: 100, + )); } }, ), From 0bdf337ffbeed8c7ebb8a3aa00271c6ccadbacf1 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 11:21:43 -0600 Subject: [PATCH 174/225] WIP send from stack desktop trade transaction navigation --- .../confirm_change_now_send.dart | 842 ++++++++++++------ lib/pages/exchange_view/send_from_view.dart | 196 +--- .../exchange_view/trade_details_view.dart | 7 +- .../subwidgets/desktop_step_2.dart | 92 +- .../subwidgets/desktop_step_4.dart | 36 +- 5 files changed, 700 insertions(+), 473 deletions(-) diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index e99cf2df4..9f62bd8ec 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -7,15 +7,23 @@ import 'package:stackwallet/models/trade_wallet_lookup.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/utilities/constants.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/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -52,14 +60,16 @@ class _ConfirmChangeNowSendViewState late final Trade trade; Future _attemptSend(BuildContext context) async { - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return const SendingTransactionDialog(); - }, - )); + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return const SendingTransactionDialog(); + }, + ), + ); final String note = transactionInfo["note"] as String? ?? ""; final manager = @@ -93,6 +103,9 @@ class _ConfirmChangeNowSendViewState // pop back to wallet if (mounted) { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + } Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); } } catch (e) { @@ -129,6 +142,60 @@ class _ConfirmChangeNowSendViewState } } + Future _confirmSend() async { + final dynamic unlocked; + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && unlocked && mounted) { + await _attemptSend(context); + } + } + @override void initState() { transactionInfo = widget.transactionInfo; @@ -142,280 +209,503 @@ class _ConfirmChangeNowSendViewState Widget build(BuildContext context) { final managerProvider = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManagerProvider(walletId))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Confirm transaction", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Send ${ref.watch(managerProvider.select((value) => value.coin)).ticker}", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Send from", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), - Text( - ref - .watch(walletsChangeNotifierProvider) - .getManager(walletId) - .walletName, - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "${trade.exchangeName} address", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), - Text( - "${transactionInfo["address"] ?? "ERROR"}", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Amount", - style: STextStyles.smallMed12(context), - ), - Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["recipientAmt"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( - managerProvider - .select((value) => value.coin), - ).ticker}", - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transaction fee", - style: STextStyles.smallMed12(context), - ), - Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["fee"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( - managerProvider - .select((value) => value.coin), - ).ticker}", - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Note", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), - Text( - transactionInfo["note"] as String? ?? "", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade ID", - style: STextStyles.smallMed12(context), - ), - Text( - trade.tradeId, - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedContainer( - color: Theme.of(context) - .extension()! - .snackBarBackSuccess, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Total amount", - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - ), - Text( - "${Format.satoshiAmountToPrettyString( - (transactionInfo["fee"] as int) + - (transactionInfo["recipientAmt"] as int), - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( - managerProvider - .select((value) => value.coin), - ).ticker}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - textAlign: TextAlign.right, - ), - ], - ), - ), - const SizedBox( - height: 16, - ), - const Spacer(), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () async { - final unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: - "Confirm Transaction", - ), - settings: const RouteSettings( - name: "/confirmsendlockscreen"), - ), - ); - if (unlocked is bool && unlocked && mounted) { - await _attemptSend(context); - } - }, - child: Text( - "Send", - style: STextStyles.button(context), - ), - ), - ], + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), + ); + }, + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + children: [ + const SizedBox( + width: 6, + ), + const AppBarBackButton( + isCompact: true, + iconSize: 23, + ), + const SizedBox( + width: 12, + ), + Text( + "Confirm ${ref.watch(managerProvider.select((value) => value.coin)).ticker} transaction", + style: STextStyles.desktopH3(context), + ) + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .background, + child: child, + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Text( + "Transaction fee", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ), + const SizedBox( + height: 10, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + "${Format.satoshiAmountToPrettyString( + (transactionInfo["fee"] as int), + ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + Text( + "${Format.satoshiAmountToPrettyString( + (transactionInfo["fee"] as int) + + (transactionInfo["recipientAmt"] as int), + ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ), + ], + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ), + ], + ) + ], + ), + ), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ConditionalParent( + condition: isDesktop, + builder: (child) => Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.background, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + child, + ], + ), + ), + ), + child: Text( + "Send ${ref.watch(managerProvider.select((value) => value.coin)).ticker}", + style: isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.pageTitleH1(context), ), ), - ); - }, + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Send from", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + ref + .watch(walletsChangeNotifierProvider) + .getManager(walletId) + .walletName, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "${trade.exchangeName} address", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + "${transactionInfo["address"] ?? "ERROR"}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.smallMed12(context), + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => Row( + children: [ + child, + Builder(builder: (context) { + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).coin)); + final price = ref.watch( + priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + final amount = Format.satoshisToAmount( + transactionInfo["recipientAmt"] as int, + coin: coin, + ); + final value = price.item1 * amount; + final currency = ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)); + + return Text( + " | ${value.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} $currency", + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ); + }) + ], + ), + child: Text( + "${Format.satoshiAmountToPrettyString( + transactionInfo["recipientAmt"] as int, + ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + ], + ), + ), + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction fee", + style: STextStyles.smallMed12(context), + ), + Text( + "${Format.satoshiAmountToPrettyString( + transactionInfo["fee"] as int, + ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Note", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + transactionInfo["note"] as String? ?? "", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: + Theme.of(context).extension()!.background, + height: 1, + ) + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade ID", + style: STextStyles.smallMed12(context), + ), + Text( + trade.tradeId, + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (!isDesktop) + const SizedBox( + height: 12, + ), + if (!isDesktop) + RoundedContainer( + color: Theme.of(context) + .extension()! + .snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + Text( + "${Format.satoshiAmountToPrettyString( + (transactionInfo["fee"] as int) + + (transactionInfo["recipientAmt"] as int), + ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + if (!isDesktop) const Spacer(), + if (!isDesktop) + PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ], + ), ), ); } diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 59675e4e4..7cbf38384 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -7,8 +7,8 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; -import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; @@ -30,8 +30,6 @@ import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -import '../../pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; - class SendFromView extends ConsumerStatefulWidget { const SendFromView({ Key? key, @@ -39,6 +37,7 @@ class SendFromView extends ConsumerStatefulWidget { required this.trade, required this.amount, required this.address, + this.shouldPopRoot = false, }) : super(key: key); static const String routeName = "/sendFrom"; @@ -47,6 +46,7 @@ class SendFromView extends ConsumerStatefulWidget { final Decimal amount; final String address; final Trade trade; + final bool shouldPopRoot; @override ConsumerState createState() => _SendFromViewState(); @@ -142,7 +142,7 @@ class _SendFromViewState extends ConsumerState { DesktopDialogCloseButton( onPressedOverride: Navigator.of( context, - rootNavigator: false, + rootNavigator: widget.shouldPopRoot, ).pop, ), ], @@ -239,12 +239,23 @@ class _SendFromCardState extends ConsumerState { useSafeArea: false, barrierDismissible: false, builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: child, + ), + ), + child: BuildingTransactionDialog( + onCancel: () { + wasCancelled = true; - Navigator.of(context).pop(); - }, + Navigator.of(context).pop(); + }, + ), ); }, ), @@ -290,7 +301,10 @@ class _SendFromCardState extends ConsumerState { // pop building dialog if (mounted) { - Navigator.of(context).pop(); + Navigator.of( + context, + rootNavigator: Util.isDesktop, + ).pop(); } txData["note"] = @@ -304,7 +318,9 @@ class _SendFromCardState extends ConsumerState { builder: (_) => ConfirmChangeNowSendView( transactionInfo: txData, walletId: walletId, - routeOnSuccessName: HomeView.routeName, + routeOnSuccessName: Util.isDesktop + ? DesktopExchangeView.routeName + : HomeView.routeName, trade: trade, shouldSendPublicFiroFunds: shouldSendPublicFiroFunds, ), @@ -401,58 +417,7 @@ class _SendFromCardState extends ConsumerState { ), ), onPressed: () async { - final dynamic unlocked; - - if (Util.isDesktop) { - unlocked = await showDialog( - context: context, - builder: (context) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: const [ - DesktopDialogCloseButton(), - ], - ), - const Padding( - padding: EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: DesktopAuthSend(), - ), - ], - ), - ), - ); - } else { - unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", - ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), - ), - ); - } - - if (unlocked is bool && unlocked && mounted) { + if (mounted) { unawaited( _send( manager, @@ -537,58 +502,7 @@ class _SendFromCardState extends ConsumerState { ), ), onPressed: () async { - final dynamic unlocked; - - if (Util.isDesktop) { - unlocked = await showDialog( - context: context, - builder: (context) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: const [ - DesktopDialogCloseButton(), - ], - ), - const Padding( - padding: EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: DesktopAuthSend(), - ), - ], - ), - ), - ); - } else { - unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", - ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), - ), - ); - } - - if (unlocked is bool && unlocked && mounted) { + if (mounted) { unawaited( _send( manager, @@ -680,57 +594,7 @@ class _SendFromCardState extends ConsumerState { ), ), onPressed: () async { - final dynamic unlocked; - - if (Util.isDesktop) { - unlocked = await showDialog( - context: context, - builder: (context) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: const [ - DesktopDialogCloseButton(), - ], - ), - const Padding( - padding: EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: DesktopAuthSend(), - ), - ], - ), - ), - ); - } else { - unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", - ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), - ), - ); - } - - if (unlocked is bool && unlocked && mounted) { + if (mounted) { unawaited( _send(manager), ); diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index f28d1d617..0b7f4b502 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -219,7 +219,8 @@ class _TradeDetailsViewState extends ConsumerState { children: children, ), ), - if (isStackCoin(trade.payInCurrency) && + if (!hasTx && + isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || trade.status == "waiting" || @@ -227,7 +228,8 @@ class _TradeDetailsViewState extends ConsumerState { const SizedBox( height: 32, ), - if (isStackCoin(trade.payInCurrency) && + if (!hasTx && + isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || trade.status == "waiting" || @@ -1142,6 +1144,7 @@ class _TradeDetailsViewState extends ConsumerState { height: 12, ), if (!isDesktop && + !hasTx && isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index 525d561fc..7b793210c 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -51,6 +51,8 @@ class _DesktopStep2State extends ConsumerState { late final FocusNode _toFocusNode; late final FocusNode _refundFocusNode; + bool enableNext = false; + bool isStackCoin(String ticker) { try { coinFromTickerCaseInsensitive(ticker); @@ -60,13 +62,13 @@ class _DesktopStep2State extends ConsumerState { } } - void selectRecipientAddressFromStack() { + void selectRecipientAddressFromStack() async { try { final coin = coinFromTickerCaseInsensitive( model.receiveTicker, ); - showDialog( + final address = await showDialog( context: context, barrierColor: Colors.transparent, builder: (context) => DesktopDialog( @@ -79,27 +81,31 @@ class _DesktopStep2State extends ConsumerState { ), ), ), - ).then((value) async { - if (value is String) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(value); + ); - _toController.text = manager.walletName; - model.recipientAddress = await manager.currentReceivingAddress; - } - }); + if (address is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(address); + + _toController.text = manager.walletName; + model.recipientAddress = await manager.currentReceivingAddress; + } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Info); } + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); } - void selectRefundAddressFromStack() { + void selectRefundAddressFromStack() async { try { final coin = coinFromTickerCaseInsensitive( model.sendTicker, ); - showDialog( + final address = await showDialog( context: context, barrierColor: Colors.transparent, builder: (context) => DesktopDialog( @@ -112,18 +118,21 @@ class _DesktopStep2State extends ConsumerState { ), ), ), - ).then((value) async { - if (value is String) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(value); + ); + if (address is String) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(address); - _refundController.text = manager.walletName; - model.refundAddress = await manager.currentReceivingAddress; - } - }); + _refundController.text = manager.walletName; + model.refundAddress = await manager.currentReceivingAddress; + } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Info); } + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); } void selectRecipientFromAddressBook() async { @@ -168,7 +177,10 @@ class _DesktopStep2State extends ConsumerState { if (entry != null) { _toController.text = entry.address; model.recipientAddress = entry.address; - setState(() {}); + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); } } @@ -214,7 +226,10 @@ class _DesktopStep2State extends ConsumerState { if (entry != null) { _refundController.text = entry.address; model.refundAddress = entry.address; - setState(() {}); + setState(() { + enableNext = + _toController.text.isNotEmpty && _refundController.text.isNotEmpty; + }); } } @@ -334,7 +349,10 @@ class _DesktopStep2State extends ConsumerState { focusNode: _toFocusNode, style: STextStyles.field(context), onChanged: (value) { - setState(() {}); + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }, decoration: standardInputDecoration( "Enter the ${model.receiveTicker.toUpperCase()} payout address", @@ -363,7 +381,10 @@ class _DesktopStep2State extends ConsumerState { onTap: () { _toController.text = ""; model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }, child: const XIcon(), ) @@ -378,7 +399,11 @@ class _DesktopStep2State extends ConsumerState { final content = data.text!.trim(); _toController.text = content; model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); } }, child: _toController.text.isEmpty @@ -454,7 +479,10 @@ class _DesktopStep2State extends ConsumerState { focusNode: _refundFocusNode, style: STextStyles.field(context), onChanged: (value) { - setState(() {}); + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }, decoration: standardInputDecoration( "Enter ${model.sendTicker.toUpperCase()} refund address", @@ -484,7 +512,10 @@ class _DesktopStep2State extends ConsumerState { _refundController.text = ""; model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }, child: const XIcon(), ) @@ -501,7 +532,11 @@ class _DesktopStep2State extends ConsumerState { _refundController.text = content; model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); } }, child: _refundController.text.isEmpty @@ -552,6 +587,7 @@ class _DesktopStep2State extends ConsumerState { Expanded( child: PrimaryButton( label: "Next", + enabled: enableNext, buttonHeight: ButtonHeight.l, onPressed: () async { await showDialog( diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index 7747f570f..c86713a76 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -1,11 +1,14 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -199,7 +202,38 @@ class _DesktopStep4State extends ConsumerState { child: SecondaryButton( label: "Send from Stack Wallet", buttonHeight: ButtonHeight.l, - onPressed: Navigator.of(context).pop, + onPressed: () { + final trade = model.trade!; + final amount = Decimal.parse(trade.payInAmount); + final address = trade.payInAddress; + + final coin = + coinFromTickerCaseInsensitive(trade.payInCurrency); + + showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: SendFromView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + SendFromView( + coin: coin, + trade: trade, + amount: amount, + address: address, + shouldPopRoot: true, + ), + const RouteSettings( + name: SendFromView.routeName, + ), + ), + ]; + }, + ), + ); + }, ), ), const SizedBox( From c5c0443d000d425b86056d27dda575a3b895fde6 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 22 Nov 2022 08:57:05 -0700 Subject: [PATCH 175/225] button sizing fix --- .../home/settings_menu/currency_settings/currency_settings.dart | 2 +- .../home/settings_menu/language_settings/language_settings.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart index d9c20d8fa..9f5f42b7c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart @@ -107,7 +107,7 @@ class _CurrencySettings extends ConsumerState { 10, ), child: PrimaryButton( - width: 210, + width: 200, buttonHeight: ButtonHeight.m, enabled: true, label: "Change currency", diff --git a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart index acddcb055..3c511236c 100644 --- a/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart +++ b/lib/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart @@ -84,7 +84,7 @@ class _LanguageOptionSettings extends ConsumerState { 10, ), child: PrimaryButton( - width: 210, + width: 200, buttonHeight: ButtonHeight.m, enabled: true, label: "Change language", From 9a47ce349e52a12d2be80ab7f48ede2b9ca7f379 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 22 Nov 2022 11:48:47 -0700 Subject: [PATCH 176/225] submit on enter passphrase --- .../desktop_attention_delete_wallet.dart | 3 +- .../desktop_delete_wallet_dialog.dart | 126 +++++++++--------- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart index 6eb04502e..a614be3a6 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -10,8 +11,6 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:tuple/tuple.dart'; -import 'delete_wallet_keys_popup.dart'; - class DesktopAttentionDeleteWallet extends ConsumerStatefulWidget { const DesktopAttentionDeleteWallet({ Key? key, diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart index 6729d33b9..e8d9c2dc5 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -5,6 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -16,9 +18,6 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; -import '../../../../../providers/desktop/storage_crypto_handler_provider.dart'; -import '../../../../../providers/global/wallets_provider.dart'; - class DesktopDeleteWalletDialog extends ConsumerStatefulWidget { const DesktopDeleteWalletDialog({ Key? key, @@ -42,6 +41,62 @@ class _DesktopDeleteWalletDialog bool hidePassword = true; bool _continueEnabled = false; + Future enterPassphrase() async { + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future.delayed(const Duration(seconds: 1)); + + final verified = await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + + if (verified) { + Navigator.of(context, rootNavigator: true).pop(); + + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + Navigator.of(context).pop(); + + unawaited( + Navigator.of(context).pushNamed( + DesktopAttentionDeleteWallet.routeName, + arguments: widget.walletId, + ), + ); + } + } else { + Navigator.of(context, rootNavigator: true).pop(); + + await Future.delayed(const Duration(milliseconds: 300)); + + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase!", + context: context, + ), + ); + } + } + @override void initState() { passwordController = TextEditingController(); @@ -106,6 +161,12 @@ class _DesktopDeleteWalletDialog obscureText: hidePassword, enableSuggestions: false, autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (_continueEnabled) { + enterPassphrase(); + } + }, decoration: standardInputDecoration( "Enter password", passwordFocusNode, @@ -179,64 +240,7 @@ class _DesktopDeleteWalletDialog onPressed: _continueEnabled ? () async { // add loading indicator - unawaited( - showDialog( - context: context, - builder: (context) => Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: const [ - LoadingIndicator( - width: 200, - height: 200, - ), - ], - ), - ), - ); - - await Future.delayed( - const Duration(seconds: 1)); - - final verified = await ref - .read(storageCryptoHandlerProvider) - .verifyPassphrase(passwordController.text); - - if (verified) { - Navigator.of(context, rootNavigator: true) - .pop(); - - final words = await ref - .read(walletsChangeNotifierProvider) - .getManager(widget.walletId) - .mnemonic; - - if (mounted) { - Navigator.of(context).pop(); - - unawaited( - Navigator.of(context).pushNamed( - DesktopAttentionDeleteWallet.routeName, - arguments: widget.walletId, - ), - ); - } - } else { - Navigator.of(context, rootNavigator: true) - .pop(); - - await Future.delayed( - const Duration(milliseconds: 300)); - - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Invalid passphrase!", - context: context, - ), - ); - } + enterPassphrase(); } : null, ), From 8e2ff3883d3516ee561d9acb36a89a5de94a0400 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 14:42:24 -0600 Subject: [PATCH 177/225] exchange amount field re style --- lib/pages/exchange_view/exchange_form.dart | 362 ++--------------- .../textfields/exchange_textfield.dart | 384 ++++++++++++++++++ 2 files changed, 424 insertions(+), 322 deletions(-) create mode 100644 lib/widgets/textfields/exchange_textfield.dart diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 7f0d9b5d2..921b35bf0 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; @@ -25,6 +24,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -37,10 +37,10 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/textfields/exchange_textfield.dart'; import 'package:tuple/tuple.dart'; class ExchangeForm extends ConsumerStatefulWidget { @@ -1226,146 +1226,33 @@ class _ExchangeFormState extends ConsumerState { SizedBox( height: isDesktop ? 10 : 4, ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( + ExchangeTextField( + controller: _sendController, + focusNode: _sendFocusNode, + textStyle: STextStyles.smallMed14(context).copyWith( color: Theme.of(context).extension()!.textDark, ), - focusNode: _sendFocusNode, - controller: _sendController, - textAlign: TextAlign.right, - enableSuggestions: false, - autocorrect: false, + buttonColor: + Theme.of(context).extension()!.buttonBackSecondary, + borderRadius: Constants.size.circularBorderRadius, + background: + Theme.of(context).extension()!.textFieldDefaultBG, onTap: () { if (_sendController.text == "-") { _sendController.text = ""; } }, onChanged: sendFieldOnChanged, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: selectSendCurrency, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - final image = _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider - .select((value) => value.fromTicker))); - - if (image != null && image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - 18, - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - 18, - ), - child: const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - ref.watch(exchangeFormStateProvider.select((value) => - value.fromTicker?.toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - if (!isWalletCoin(coin, true)) - const SizedBox( - width: 6, - ), - if (!isWalletCoin(coin, true)) - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), + onButtonTap: selectSendCurrency, + isWalletCoin: isWalletCoin(coin, true), + image: _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider.select((value) => value.fromTicker))), + ticker: ref.watch( + exchangeFormStateProvider.select((value) => value.fromTicker)), + ), + SizedBox( + height: isDesktop ? 10 : 4, ), - SizedBox( height: isDesktop ? 10 : 4, ), @@ -1422,79 +1309,20 @@ class _ExchangeFormState extends ConsumerState { ), ], ), - // Stack( - // children: [ - // Positioned.fill( - // child: Align( - // alignment: Alignment.bottomLeft, - // child: Text( - // "You will receive", - // style: STextStyles.itemSubtitle(context).copyWith( - // color: - // Theme.of(context).extension()!.textDark3, - // ), - // ), - // ), - // ), - // Center( - // child: Column( - // children: [ - // const SizedBox( - // height: 6, - // ), - // GestureDetector( - // onTap: () async { - // await _swap(); - // }, - // child: Padding( - // padding: const EdgeInsets.all(4), - // child: SvgPicture.asset( - // Assets.svg.swap, - // width: 20, - // height: 20, - // color: Theme.of(context) - // .extension()! - // .accentColorDark, - // ), - // ), - // ), - // const SizedBox( - // height: 6, - // ), - // ], - // ), - // ), - // Positioned.fill( - // child: Align( - // alignment: ref.watch(exchangeFormStateProvider - // .select((value) => value.reversed)) - // ? Alignment.bottomRight - // : Alignment.topRight, - // child: Text( - // ref.watch(exchangeFormStateProvider - // .select((value) => value.warning)), - // style: STextStyles.errorSmall(context), - // ), - // ), - // ), - // ], - // ), SizedBox( height: isDesktop ? 10 : 4, ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ), + ExchangeTextField( focusNode: _receiveFocusNode, controller: _receiveController, - enableSuggestions: false, - autocorrect: false, - readOnly: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.estimated || - ref.watch(exchangeProvider).name == - SimpleSwapExchange.exchangeName, + textStyle: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + buttonColor: + Theme.of(context).extension()!.buttonBackSecondary, + borderRadius: Constants.size.circularBorderRadius, + background: + Theme.of(context).extension()!.textFieldDefaultBG, onTap: () { if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) && @@ -1503,127 +1331,17 @@ class _ExchangeFormState extends ConsumerState { } }, onChanged: receiveFieldOnChanged, - textAlign: TextAlign.right, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: GestureDetector( - onTap: selectReceiveCurrency, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - final image = _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider - .select((value) => value.toTicker))); - - if (image != null && image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(18), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - 18, - ), - child: const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - ref.watch(exchangeFormStateProvider.select( - (value) => value.toTicker?.toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - if (!isWalletCoin(coin, false)) - const SizedBox( - width: 6, - ), - if (!isWalletCoin(coin, false)) - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), + onButtonTap: selectReceiveCurrency, + isWalletCoin: isWalletCoin(coin, true), + image: _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider.select((value) => value.toTicker))), + ticker: ref.watch( + exchangeFormStateProvider.select((value) => value.toTicker)), + readOnly: ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.estimated || + ref.watch(exchangeProvider).name == + SimpleSwapExchange.exchangeName, ), if (ref .watch( diff --git a/lib/widgets/textfields/exchange_textfield.dart b/lib/widgets/textfields/exchange_textfield.dart new file mode 100644 index 000000000..8d3c5d699 --- /dev/null +++ b/lib/widgets/textfields/exchange_textfield.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; + +class ExchangeTextField extends StatefulWidget { + const ExchangeTextField({ + Key? key, + this.borderRadius = 0, + this.background, + required this.controller, + this.buttonColor, + required this.focusNode, + this.buttonContent, + required this.textStyle, + this.onButtonTap, + this.onChanged, + this.onSubmitted, + this.onTap, + required this.isWalletCoin, + this.image, + this.ticker, + this.readOnly = false, + }) : super(key: key); + + final double borderRadius; + final Color? background; + final Color? buttonColor; + final Widget? buttonContent; + final TextEditingController controller; + final FocusNode focusNode; + final TextStyle textStyle; + final VoidCallback? onTap; + final VoidCallback? onButtonTap; + final void Function(String)? onChanged; + final void Function(String)? onSubmitted; + + final bool isWalletCoin; + final bool readOnly; + final String? image; + final String? ticker; + + @override + State createState() => _ExchangeTextFieldState(); +} + +class _ExchangeTextFieldState extends State { + late final TextEditingController controller; + late final FocusNode focusNode; + late final TextStyle textStyle; + + late final double borderRadius; + + late final Color? background; + late final Color? buttonColor; + late final Widget? buttonContent; + late final VoidCallback? onButtonTap; + late final VoidCallback? onTap; + late final void Function(String)? onChanged; + late final void Function(String)? onSubmitted; + + @override + void initState() { + borderRadius = widget.borderRadius; + background = widget.background; + buttonColor = widget.buttonColor; + controller = widget.controller; + focusNode = widget.focusNode; + buttonContent = widget.buttonContent; + textStyle = widget.textStyle; + onButtonTap = widget.onButtonTap; + onChanged = widget.onChanged; + onSubmitted = widget.onSubmitted; + onTap = widget.onTap; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: background, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TextField( + style: textStyle, + controller: controller, + focusNode: focusNode, + onChanged: onChanged, + onTap: onTap, + enableSuggestions: false, + autocorrect: false, + readOnly: widget.readOnly, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + left: 12, + ), + hintText: "0", + hintStyle: STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + ), + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + ), + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => onButtonTap?.call(), + child: Container( + decoration: BoxDecoration( + color: buttonColor, + borderRadius: BorderRadius.horizontal( + right: Radius.circular( + borderRadius, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + ), + child: Builder( + builder: (context) { + final image = widget.image; + + if (image != null && image.isNotEmpty) { + return Center( + child: SvgPicture.network( + image, + height: 18, + placeholderBuilder: (_) => Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + 18, + ), + child: const LoadingIndicator(), + ), + ), + ), + ); + } else { + return Container( + width: 18, + height: 18, + decoration: BoxDecoration( + // color: Theme.of(context).extension()!.accentColorDark + borderRadius: BorderRadius.circular(18), + ), + child: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ); + } + }, + ), + ), + const SizedBox( + width: 6, + ), + Text( + widget.ticker ?? "-", + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + if (!widget.isWalletCoin) + const SizedBox( + width: 6, + ), + if (!widget.isWalletCoin) + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension()! + .textDark, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +// experimental UNUSED +// class ExchangeTextField extends StatefulWidget { +// const ExchangeTextField({ +// Key? key, +// this.borderRadius = 0, +// this.background, +// required this.controller, +// this.buttonColor, +// required this.focusNode, +// this.buttonContent, +// required this.textStyle, +// this.onButtonTap, +// this.onChanged, +// this.onSubmitted, +// }) : super(key: key); +// +// final double borderRadius; +// final Color? background; +// final Color? buttonColor; +// final Widget? buttonContent; +// final TextEditingController controller; +// final FocusNode focusNode; +// final TextStyle textStyle; +// final VoidCallback? onButtonTap; +// final void Function(String)? onChanged; +// final void Function(String)? onSubmitted; +// +// @override +// State createState() => _ExchangeTextFieldState(); +// } +// +// class _ExchangeTextFieldState extends State { +// late final TextEditingController controller; +// late final FocusNode focusNode; +// late final TextStyle textStyle; +// +// late final double borderRadius; +// +// late final Color? background; +// late final Color? buttonColor; +// late final Widget? buttonContent; +// late final VoidCallback? onButtonTap; +// late final void Function(String)? onChanged; +// late final void Function(String)? onSubmitted; +// +// @override +// void initState() { +// borderRadius = widget.borderRadius; +// background = widget.background; +// buttonColor = widget.buttonColor; +// controller = widget.controller; +// focusNode = widget.focusNode; +// buttonContent = widget.buttonContent; +// textStyle = widget.textStyle; +// onButtonTap = widget.onButtonTap; +// onChanged = widget.onChanged; +// onSubmitted = widget.onSubmitted; +// +// super.initState(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Container( +// decoration: BoxDecoration( +// color: background, +// borderRadius: BorderRadius.circular(borderRadius), +// ), +// child: IntrinsicHeight( +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// Expanded( +// child: MouseRegion( +// cursor: SystemMouseCursors.text, +// child: GestureDetector( +// onTap: () { +// // +// }, +// child: Padding( +// padding: const EdgeInsets.only( +// left: 16, +// top: 18, +// bottom: 17, +// ), +// child: IgnorePointer( +// ignoring: true, +// child: EditableText( +// controller: controller, +// focusNode: focusNode, +// style: textStyle, +// onChanged: onChanged, +// onSubmitted: onSubmitted, +// onEditingComplete: () => print("lol"), +// autocorrect: false, +// enableSuggestions: false, +// keyboardType: const TextInputType.numberWithOptions( +// signed: false, +// decimal: true, +// ), +// inputFormatters: [ +// // regex to validate a crypto amount with 8 decimal places +// TextInputFormatter.withFunction((oldValue, +// newValue) => +// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') +// .hasMatch(newValue.text) +// ? newValue +// : oldValue), +// ], +// cursorColor: textStyle.color ?? +// Theme.of(context).backgroundColor, +// backgroundCursorColor: background ?? Colors.transparent, +// ), +// ), +// ), +// ), +// ), +// ), +// MouseRegion( +// cursor: SystemMouseCursors.click, +// child: GestureDetector( +// onTap: () => onButtonTap?.call(), +// child: Container( +// decoration: BoxDecoration( +// color: buttonColor, +// borderRadius: BorderRadius.horizontal( +// right: Radius.circular( +// borderRadius, +// ), +// ), +// ), +// child: Padding( +// padding: const EdgeInsets.symmetric( +// horizontal: 16, +// ), +// child: buttonContent, +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } From 0b6545645bdd7bdf1939c2de2b42397e47614210 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 14:55:26 -0600 Subject: [PATCH 178/225] macos + windows app icon --- .../AppIcon.appiconset/Contents.json | 132 +++++++++--------- .../AppIcon.appiconset/app_icon_1024.png | Bin 46993 -> 69450 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 3276 -> 4664 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 1429 -> 926 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 5933 -> 10085 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1243 -> 1441 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 14800 -> 26089 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 1874 -> 2443 bytes pubspec.lock | 9 +- pubspec.yaml | 9 +- windows/runner/resources/app_icon.ico | Bin 33772 -> 1968 bytes 11 files changed, 82 insertions(+), 68 deletions(-) diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f19..96d3fee1a 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7ca84f0976aca34b7f2895d65fb94d1ea..55efad011aa22c651bd4eeb225bebd9c6fea31bd 100644 GIT binary patch literal 69450 zcmeEt`9IWO-1cY2*ov}5vd5qZ30cDMEz8K3Y}um{Dp|^IMp|rzv6V2=$QF|9dxcU= zS+XQF_I)>&S)Mc9&+|V#KivJ27xOyvKIgpG>$*;ig_$Ae0l@6M>-(fQG@i{4yrN?fy>YG}J6XM1HMoAT&YX4Cuc_cA^ixpH4j6#q|; zPb%fAq(QM+?4u(~9p`qQI5uBUc;qN`+V4~job&wxD?MuLkNxgfcEyuNSmBD#Hk9Cq zz(|8G-=+kSd|&tuK2ol6s;n>ag?W#+ynF~^^ET7b0eRCQCWSz3tIYYc*KOB(kE{oe zQ{$UX9^@E-H+nn!bcJW?K%Vom!A!!!@Yl?p&MT|S%iE(g?cT)|)DhRgt$J5`jBdOr8M07YSo2A6bZ|>*9WIk?G%&>lchzeQ0FzDV@DL<5UA?8Rc zvv~Q+ep4d3p{;xSxQVgjy8v=9K01~~kTc^)^S{CH|81fu?W^=2Sg$#u=+YFnbO}c&I>QK91t*R1nT4vVDolt@ zgZVh^%@}5l)Fwpd;Dv*aDE<4$1;oUfQA?M)wsr~YR6Mn@G2mS@{#imo@7h9QnfTS&t3LVA`v}(e25dbo%?8+MyDn({HH`B--&ugQ3rWL}=#uY@9KCnKNKtd|@_3PIGR)|RrZVSW+cR{oGxPoY4 zb#*W8?^S0ivDt-^3F**oIb$sMhhC->vy(9AWI5ke)V~xf8{mNtc&9>ZgM;Vo>r%H@771ByTz1vzHrtXnQ70dS z!^0i~PGXymtkSKLTPk27S_W>efq`lwBBH-|xi0My$CCBlja`ZV`}eQPnKQVM(9i{T zh=*xP6wcU8*T2n#cueUwH1#w$<2zbg^)yd055HYp;SLiMk@|SUnC6mZv%L_hnNT_A zIkDD%aCE16_cA8G0&}0T_#w@)hCiWgW2+^*Gm_No?(HpSdz_0>;WRGAhiLN-+26Bt z!C*#peptu5p3%ki-D1=(LZGE{OhON(*M7XAt}f%On%c&IQ>E?;YijRsX9N;*ZjidE zlkghCERx3pcyzU4mwvnHu8RF;Hqy-$7A7Qr`V_FxYu+nU?UUd7>Q#gO_$gi(j&S2D zqhdBD+x;CI9qpFf?!N3s6pw!MK_`7H9ZuhTY)v(|d-kfuvQT34jA|Q3jLL3fbobWn zyk~7~4f;7{Wh?!^BD=fy3gOalB>HJLYy&U%_+Dp1K}*D& z*l~KkEJvpy`!7s>qxMqrPC?eG7R*4`OSO&Jl@%hr@u#9V{a6_-=OCpl5jpqi1*3NR zq|}p-y1TneCY*RZqhU<>Z7f11F4y|7=jCM6+i!{*Nqrt3wl58_b8#Vt!)K444x2C# zJSJj#yRwswu|orQ#t!>anS$Wqb!2mM^L!6t_7tBFOY2>gPjczstBqIbZ|KjA?s5%Y zW^bjd{j46p>7$t7LfIQA#JGyzJ3{mw$?1pw-$K_rD$fMkR38&>nbPQ_vU`p7nUuwL zcEEdNavJN;EiEohU1R5iA!w3ncL+b-EL2;-MvDGzJ2E zms6@Q&zt+#SD3DCPVCTQujv|^n-32TcgCdrkH=b~RCRHLVmGjNLafA&M?dDy9R=;6 z)tn~MVIJu84hAz2r;9JIZOpRy^{dANAD+g9DLTRMvr-(RtFm`Qtd(Q%()Z(+By{t> zie0B8Xe{|Na(INr;Mkb?!Gi~PL#voL4jp>)YWNpa1h;6D=fe@cdHnl6ahUE9s$+ng za(TP?{d>#9#!gEpmjUk%7!DHu>gVT&>mWvKR|xNrLGF1*qVHc@RYcbz8T<6oD-XnT z!)AZ|vJurXJcE_R-n8&o#gyCql{wZT$^N`{XsGW}m^s`vC_p9Q>@&W%ss}$v=;8vn z8J73`$0q#Eu#bE8@-(qLVq;^Y_DGf_bub~KHh68F36`j_{ss^>mFFXf}_ z(ASGy@*-coN{Jn(YnG|*q$8cUR`zhnO11~on~NRd;~wumnsXWNVgks4k9OxjdmhyI zf!RHyb})=a2_EJ7;SgV1TH4qDC(DI59Dzh@9{#rlw<$ghJZ$^<<#pmXTb)lc_3WR9 zVC6q`_vF-eC1I|$^#0uM&6yEH!$V)A+OfjNjLx^8tzx~Kp(_uwparHO=gu?Sr4X$j zgL%8d@Ylk^!jNjrHg&8qv^s*48A=rrLchG(Q&U^}LP+_{;G5P0+e1dU&B%XeaG^7A z|I)g-GU`D`M}kuA{cXBOFW zb@lcB7%Yu6mV)`6DL%gWxTg>X(#b(ahpws)|HyU#4GD{gOqIW{%V44#CWub;(|`W_ zsS?77(PLj-I#vWJYELSrsJEExu3QQ37k*c*_D5LgTPvzxB&z{i0;BC4<3^(x9w}&{ zao2P}-IKqV3=5H7u+VsP4PJb6LoiUlSlwLD{qE~ZysK75#pvOJMQ|J zu8n>%gxwFAWJwHd+n-2zElMW)OGNA8p+v-ZWh*%fd6F^v%*9@CJ?ccgvgGl$tZYW_ z9dZ0QiU%$}+w2RjKuGAD3-3;wlJDt3@7t99b)PO-jLJj874Z&MzyBb~43>7^#GjJl z5iU3@Cx&m=!OQK%I7cNC-4o~|BaWiv7`OMgO^YGw`IUifWk>fjK!^53V;m5Knjj>u@{Ss3TvB*MA#vKHp zJM(^XJWw^RzOsbR$S}sxHt@YJwDol1P36IkE>Zf4ztTJTJXi!IM_+4*bX0BpC6r^s z-4Ys0;E6);I$W%~DQbtHQ`crg3#C?9S8tR@#6NoU=tE7-^BW!>8?KM=2;E!U(Z;aO zddVHJW5)``Hu%euElSyD3$plI_Lrh>!;pA0vvuwVx5V^>MQ-{)=%Pc6h|7BI*FyA- z;IO&M{#m^zVVTCgYN!vD#e~zi`;^(bgP!?ew*7eDaZ}Rw*^GQF|1F5P>lczS7S%8o zGpm{dpoWi(LufgKxKaS9@m@l$4Z`q-A9M3sI4%$fL#d zsc{q!q-DNJoJWn-ua^Z{N1$*BaaaqQw?T1^Pxps8PtJ(BM2OW64Xk*Lv0}mzJ$||3 z{7UgSy~MWiTL_9dUZ)G?Ap}L`#Pw5eez7@334W&Mhs9w;IcZx;LB!AdDdZC?rnt+S zx#GuLLeEqkN__6-D0$qqG6q?Bgt75(!eFvFm;ULtCK?Em8S%Ka@z$u#Plr)`Omc8} z9>^oAi0#A9hTB$iCUlysw*%O>uVQvET3$9OC6=rtV=;+<*qNL$A@loYIQxf;0MyZ~ zt*7tJdkx#UnAh*`dzJ~j-9`ZirP&ujY&*X%8ooXY<=&57TmAb< zMMo!_Y^$7`G-l3hwl;%ITFxrqrDcUM4(!SsL^kTAv*DT8$s=VOA0V`s86({H*r#4oC1ASA&2w{e zD$2^q+1lY59iD6UEOIo&+KQ0yT4)N@5RP4u40g@Qi}S0Jz{b}`I5uyT2{B8h9YQ5OA_$Y?u# zxc1ksa6i36tEi}`t#wJEmA!pS=m+mYDjT+W2pkRD*^d#tXPLFl8aXQ2G zpWP4iUnOqykG_mAFE6JwJ(ZL-*Ou4C31%})^^%y)fBVAqMsTo3*$(gGWgb(w>zzBL zqfFRN?4TM|iL`jHlYUHeH{ZaL=D3&aj0x6rit)P}9R?a*E7LZ}^1F!tYK zZs0N=yFOWu(A@L6b!+lHrV2XUS%ylG++)k$+uK_g$KnLhR)`C3r!FXR=qQIB(%E0E zP*G8_GKU7mfJq5p70bNT`u)q@Vsf;2kdzi*=(SvSUZVe)*zF9ZpUiqQiEqJ&_ur@5 zO3F3Qu&)-fd`Za955D-_b1mFZ{`imHfpj^C^>ahy3ZyCmL=K@dwr6@L1G*6wrX{bS z@CH;a$L;2diey`Dc0UPMlp^KITbkhp>KQf8(!20yrfE}v6f7Skqv)Rjqg~IB5 znJvDRxpH$IbU2l|H9kg2Lpo{yTR8?d?H7n?|NQwvMrP()RvP7$v$-qnl^o3{*%s}c z)pwFU6Z-89KmNr;Z?El&kNLqw!ed6Nv^vR6;f?1S=bbe- zHJuvA+_)i~PVLy8_SG8eIJ+Avv~31_a2ms6;3e(B>!uh>UXHVm(jDzU(Dzwc=cc<} z78X}jM106YE1>H%8GbFV_#mP)^;kK{JRQ?X8zK{3r>!JV0HE)DTzBqJsE}z2xe3rd(lW1JvF)H_}$&z&B?FH4J2YT z-`BxV%(n36hQSZsP`W8pV4Z?e4l`Zh;DHeIu&SUk<@WY=zvj60neRv}(ut9{327Hx zxuv$=Zl~Apb(BYNJV^O=NaqAq28!*$7{8XM+seg~12#v#G^)mSZ1wl6SX^WaxPR{j zBAIZJ(ct&_xm{O!;MKW5G(-OrIF`54OxIx@`FXdjVcsR8!Dwn4gM>zSOKjHP5V?py z{i7z}>@@R3hkNtED3~6&OaS9xCfC6Te&ig9(J@rUv+WFhBRjA556 z=z9HC;xBAcQc|ByLz+A+37D0o$XM)6U{KKWXR&LGbG=$sWb%gV3Py6VToHeY%aA_R zNX*JgJ6GI83OH005HvD4Vsifc`QSw4x!|m9+wFo|HC+x?m(`L)# zU%J6`rj;9<&j-V8{{3gqfk-$z^z4X(y?tjSy_&q77uq(vun<~F8gtdVhme~>K(E6N zL|WbZNm1m1RFc=(!t(ohC_2c#iHQX1s^F%OH21mPF_27Xz-j# z2c0IdvvT6Wyum>lw_I9^vLToITr(IKGk-9RkAq87P?(O*t|BQbYo1MG{R#&-(q;t1 zCPB+ut?Y4>8r$!|FOe;_F5#(7JEXXfKnxsaLlaWXnQoS{p%+?4zj=roeKwv=JP;1! zPfzRl+ij*f^2fB9f}(?*fDhF%_kEj>ocqJ@DOKTZGVf-^N8%*?iCDxJ$s*W^!#B)N z!=R^Ig7*}|cMJ4r*_7}?sAm2$JK@G9ItSnD#$q{4+IH;1$IZan;9 zCes5(Juew0PxYZ6mpL38-jcF^d6b^sOp&5)S2vpitbh^p!PgTJx)q2Ymtb5DkP&1Q zqNb6esB`|-+T1Cf^GK*FKfiZ&_GT%E1UHxAErb8wT}pm{3+QL;zf`z*&*T^T-ise& z%^1|-RSLit>caQ$htsu^DK|1kI-IB0jjt7-z!4m<1_%hTAGAdIwfdBy=0B?`k?Su-3a zUD`O1eeYPx>hz(pg9klBykxa^{yxzP2?_B7<}pnT#yOfnMw()4yRip^N~Nq2pSPwq zh$teT+LEuM_}9eP zC0=W^mY$)Ic{3+<36E&?1Ov-uPDs>KqSF&LFm%QuK1oPzU7Y)92Wy?Gu4Vtf-9CSw zXqb%Jp0|8c|NcFW|LDP7S}1SVGZVX$iEZ?G#_-&2D{91(yoJjxV9KxEPHXZ0YdO z=zv#ffP*O#>TKlTI7^62kJ=|y1eR#thJv|F3NU%34HYRLa5}UDAdy*!$)JAsgCpRHd9iUO)N3W27yPVT6e?TGytSs54a>f7G&xpQZl57WIUt(=l# z3lW)KD*Dwh17W}n89ucH&U&x?B)s(+mSAJ~^hG*y?&D`!wWx87PaxSkxp`?xg|>Qi zk4B?@XlTg%MrCDX-Ej5mx>kRH^xY2*#`2!2a}Ev{;I8#GI{L~jU=+{f5e-^8c&SL6-H(kUa387_84tQ{SNK6g*w zv)JxldE;f4!G6YVrTGs%ula*UFU z52&iA(`XTQ7CS~G+WY#fbVd`x=}2ZfjRi45iFrUv?xn^w(h(dWfCX)9KjRTlD}C+f zB^`xCU(i+##|~`%5==K>)^j{8RrrEoXXB;C{+YFc=N!sa-Y^|bs*SB}FNx$Ps?wh; zi^VoajAZdngpdfv;fDz~tPV{Mk@2=WabJzd#dr4svcMqlxqKu{o1~t{h^LT|I0>`^pz;QxB5aFrh6C6-7$T=60;s$lokGgUqbHg zyMit3CxwOV?i`v`weGQIFhlaCj?YB@>;vq$x2MOlu^uD56lf*qsVy)4!Q|GT=Djr% zJrrFcQ<_jaoS(KBdP&PLS+}%gygbZaH?+hSo6l@icYtvnk5b9I6+9w;isu&TV8RZ2 zYi$L4w`cxC%y6%KuPniRRk5;ZAOvwFQoGACAdt$&PhFc`dmhoJ&sdG87nJ+EVyE)>h zsjg%8B;E~oERU9bQ4wt`IPLJh=B&?AV!5@eN1$3sN!iQ=h#iKj%;p!LYXB`1 zLE1{;_e|0_UV)Y%hBBL&nDA`e33>h8sHxyK`?zWfoCt*NJ z$?<%s_8IGTfD0gD^QlY#amZp~+}{PaEuFeufOpP-&`mmL(mUwW$VTyhT{w*7E#0|j z=`Qh3re#L6%bofG`|KIWH)YMJt)mkmw-2U+_E(3|y?XJKYko%D_3)6n?c^mnFj1v5H0EBuM4pRGGi1I6wuQtrj`$&>L{0&eU23%t###T_R2lr{*7o+aF_t#$2^*0Sz->x1!Y6X^Xe+&aFC>Jr z<0~j8MnpqZ(7U=%oz4H?ni(nC#@v&qT2Tq0844Z&G=?ox;;Cm7aH+Xd;P3*N-Q4&) zw##wnbDH11OYCTGHyA#}Qml1pxN!RXI1%v&0Sw?TrZ~D32*38==wLOF(%oc>E`i|~ z_(Ooc5u9thB6fqayU{RDUmU8Ss1~bpBtjPcvA3bp&uC4bOY=n4$D!=4DE2AfXIg$3 zY^j3tO}MUQ7C5{*1UF5!4}^!Juq zI!aEN+TB$-t&+mR%6i$>*JmzQLVQTwrs#~vbATSR!G{hubH-07RflaaIdc_5BuWd{d`xx0WSUS7)k9k}x5T$_Nfs9Ba(TDjDC5&b=`l)*35 zK_0f0lU*Z2L+Aui?arv2#MUDEU8R!?^YecCZ}iuk$F+4y9gQ_Og^Qsc9!;adS(${X z{{8?v$EZUI&-`#Nl9>i|xE#JWD4_cp&6tAG+a1H?=C-!BO%#0tH5b^t?^NeMVK;KN_`qHbZg0VXGMN4_ri&>(Vt0xqW6cp0wmd2QBz&(J6X8&eCDa%8yKk2 z4>(SbMr%{0xGZ`V@s+BDSu5U~dkCgG&(rw51KgmkT-!7;s_>wot? z-FvdnIUruteN*5|q4Fm4mqu7@C~u=ju0+d2Y~OI^BcmQeS=Ke*MWHDgC|GlcF+1n? z$QU|#bHRnKdDzBLhfdkp>wH%fNk;UGv(Rx0Lga#3&K7a*uARwb^L27k;xx@_lxJMq z*vqv00&du9ji>h$!XE`&EH)9YAM%MAwQup*vug8Pfo?(rWUZ4bL? z@y(Pe#kp^a&EG>$dKP`YcCv2&+!f1OMz+xdU?Fdd5hM)U@wFR;vZH>FygA8j>RsZB z%fK@Xi?dq;f$^0)A%1Ob4NQU9H<-l@4+-nh1;tc3C@6(sa9Kw_r=lj|C&%5gnt`tG z-P51XMj>Gih@C5${Ns&gUOK?}=Q!Q+B&E*=n7nx<7O5jg$50nsdcoCV1`;miKE$yY zn?2l=o|1=_^HTewNlgPbh`p?~383W|&dBQOzvbAxx4*wp8FwMjRvxVj$1Y?Hs1yb> zt|Y@R-@UVvefZ{bM>HQM+x>k-RaIKOlrlS@tR*H9MjmaU5@xY?4`Kv+G7J?L{aNoQ8BvDXp1KUGaZM z%;8uEpnzGxW^(r(n7V_L1e!CyM#q;!Jj&Zv_};ggdkZ#A5x$B@cC+icIwY0l*ER?v z&MuI=jN@|fE%Dr;)PaFtJ$rj0887if!+m*B_V^M8_rK_1WYqUgP94A&$d)_WSSrWt zU)!wyD~guV)x!ASI^{AjYy|Zcu^6!hWhDzHgDXkplPiz6`|FBFoGQ3a7<6cI>{?@9|vO@$0@$6QV;l#R9VnE5tF@vOr6B z(AgDQx=EZD=MSZv+ybrk4qom-gbk(Id(3BLvG1h_TCAvn6DylsfJ`FXSO+=3lBPCt zzShSQ)*=8D68UY~XJuhQw=?;H%A_s?oSyXo-lNi|kdQ#YmWP!4_3KgzfJ2_)PCg=i zdMcG;?)+**tSn^H_54Wv(+if$vHK^YypOSiTOWvBxZ)5}IaOFv)AUnFyo&R)Cv3G& z#z`KeczgTWzk=>QK4m&ruD&A?bB4sMA+0l4*-LxowJyN12cS_*QyH@Ifg`u>9Exn( zq>%ml&zSkeix;;J)ZgS6$Y&m8v#zrT##Mf(kfR}?o6biVjHoF{1~V_-o<`3tFPG9w zHpK zD9^DVT0I05Pn{xwq>a7xImzKID zpKT{A{C7+5_-Z~Yl#te9>?Jidd9LOcH^s0SYXog>ZRJI@kGsw<&lS3`f}Eo8q{0a; zfBXSW-9y`WFF!xuzl9iafrTBGJyU7&pBpMSAgTK->;tHQ#c}dMz6nz_A!`fPws3VH zzQI3T{jgdkuM$JZ1YvZv132&-Tjw13 zUHR4E;NX{5l;IxGOJ!r4W>ig1g9+bEA>c}yngpDjB()$;afgFGd!4Qn=kSBvX!;`hQ!ZMB#Ir9 z;i)rjaFN_0dR$Mh^SEd!C+D)lwrTFod8yXiKS8H(%ix%>@LL&3V4Eeeo58c0N=h?D zPdswV!@FX_!?n|?8^YIraY%Ejkd?n!N}eFYqBqKq;jC0|SJI%L%}=grll}HKEouP> zg%dvEBGZ_DQS9HzammpJj5`i;uQEzcIL!(={aod=+s78~*M*4z{Um@Q!d68U)V}yx z$`u{}=%1rpX`~~sxp!A+?b$oGZ%=tofJqey9ne%Jro!U-mvPsq#MO|K;p!kR1nqa# zU)imBq@s-XC+wO}6yJE1+R`j+hklwHcEr^RGto=5k~Nw z>gs`H5a0H-g+9VlLQ$cZTs7Ubt$cv~KeLZ&*|xlnA26Szf|T5M*G;0O3!j5uME#*0 zMF888O^p~dVIp0*mKqwP7<>u&c zM_7))OCZ*3uDR1Ag!T4taTzOKg4*8wEZbZL@ml9M)eq49tcGUH^$8^1lmmMFOzLM7TSvq*Xc-)d zqd=ubkh4A%R&$kiQW20|pfYaG{$$7z4Yza)gOID$z8M)#z<;^<#M1YB9mA9>L#N~crD=p`Og?ur2o{G~g^ z{VBHZI@z&CLAAzG=#@9Z-#cVcM1ZRmVK0}KwC%K&VX{~Z4J?KFpE!F+G3+^{Myff9 z!qBu#0h4DP!2^Yf!B9b2FM_T0HDE0>k%lsPxXrbiwCP$_Ioi^ar+?@Q zo|`ptP&qpK`y!wPwqzrhm+yo!6}$)A(hMB2aQC5q{P>?J5FmtG<7^L78s~~S-EuoU zA`y@^6sB{NWwtW~nT0Q4%Ta>{Mz*%L9GOp_26JP_yCTNPXsS(Ve@7^Tk#mkl-Jh#p zX)0c!D%!Htkji8ysA9kuPf2?6PZi!hBa0S0Uc_}w(Rotx@v%#{w}w3MQxD|^+(b9L z8<&e1Zxb|+UK$3Z?zO=IpvPNZU-vfcMpBuIEpIW2v+F)M^#FtJX^XC9>w<%Fu%Hcl z{P=Ot_;^k}bHNmdxXqt6mv|-te*6YVtnxwui}dvAQ;K%pz4)Lk0w#LtbUCLu-syI_ z7g{rLO2bI_sKCaNDu_WP1z&6#8Za(t2O&Y`iDWnZ#^#)smbNZSXCRX))XQ!i#M;7; z6#V9Wj|8Ske|L9TbX^c)SIO|NW&^0xMS#>T;M=uPXZ<8tsm#&{9pgOEYU zrpRB`Aydg?0|~neb8}yr%GVX=?kT-M4kp}&rh0+j9W53f^cQhe8J3#9v%h2)Ofn=bw(RSpSLNb`BRSl$Ga-QPh*+yL6h% z{4v4KsF$Nph149gQgnFm^?qX5S!Qu`UDy;>hZuGC?E01loX(hvS>0ap$2Hu z#uIG!ucmN~f&V%;I#SP!{e(d&$BELRfmrW*8pp*|LEKqBO7*cQhoe_phia0OlRZHf zhBDI$O(V981hlyTisevWSQXpeE=SMZ*cfR!6B8Zk2)d^~+7ffxTw-TNGkoD!iPmgX zpEE^^%<7tmD7bw*Q80-eDtwSTS<|rcTu3kqOTWUsdYQdt{D$G`*mXm{xsRE*Sp=f` z0f>epw6~z4Tg}i#{AXj?$@)V3C@e_jP)6X(5i3(Et?Y zhhlwes?+a;?|JSK1U^Yh7QPPR=O5Qrm-!UH2O%n$4pgrB?|_D$c>XG;1af`zxQV)B7J$Z@g+kE;yB!%eb&dK zXX+#w9(_RsZ{2CKI~wyEshSqnuTc6&#L20!qW+wSs4yX`zdrE^{#vpWs&r8qEfA&Zx-qwJP|9lBhw^;9utzk5Tglg`0 zq+Q%QnbbE`(87>x4vgW4&uKb$4cz3hJI8ELcS;%}@N-9^)LwwL_2tW#8cYC8&SEvB zw8dg}qm_JgEGMFQH9NZ?7j<+B;js-EoW6Igmt!mCpMP(>A6ITqxfO8h)SnXuLLCRI zDyO`#exGdTa)GbdLuyY4B~5;qPrqovmjnG>2Vew55j4ic?Q(FBQ{8zQeV$2s*7LLe6R`}b+B6^wCw;(z)CKaY zvt_^a_c4PZiMxI{A?7o~UNNv4ueuZ99HgzB*Ahzq25iqf=8>ZbFOh+KEKK?8R-%at zJLGf1aw0bM^?G?05@MqjvW(A!rA?@U$VoeTE4JfoC#4<`Jdtvl{CDprn#Fo+wPKWB zU5q+^UZeG!s?K!as(snU4Zs-jcqD9m+y8n$a3S;|D2*8a0G~Jg6cpc0^HpAxmQa0S zv<}Ea0WGn-TpVHgqt>a?6_H9E6VTjydIoFQvfS-jdAo&(zNEwQD>$nYs`x&?;p z1?_`cN(t`VGG^GASn%?XA+jd0@T;-6FL#>_|Ndhds{fAYT8Z)VJHyHvUs+k%H!zUy z!_RStAD7|qcIUGV6bdu4To@PZ4twf-jvcDW zt{CVG`*P-wk?iLH9tsH0;S+Zby=>c|?XUEb$twdcWBvW9Ai3IK)PU)(ntxQ3j$ZRe zTl%~Gd@PQiH!I!HI>SaYH#f6nWyiY3kJKd9#f3t-v~LT|IzUla82R}j4R4GAd8=>o zT8MLe32FLWB@kLs*!P&!4x>w?a9GEKv%8m;nYp?kDRfs!L#13Nt#-L$ytmz7`0z#l zL6WLdr99jwbpHG?B_qyU$7LdO79Xm!8Ng%y&kYZYz3FHhRCs{Rl7Pf_@`X(-7eX#8 zLLm`@2X%qXjw^b1iF42s2Z!C7mJ3X?wu;68|J!oU3U+!YsJjB_#DKQk6gA_f6ZByK zTnY&ZZ5d$VE?)G!7px(21O^?&JfJTCSU>f=TjBz|m@d5HsR+Pp({k}m96|T?)tg|> zLK#Rr-xVjrALldAIvvw9vOS@&eC;kf?aI^yNk>gBJuNl|Q_Hq@l%L;^M_ggLq3K&H zrV#gh=O;4O8OmJ0`ftIhSm>@j6g7^QznTOIh=_D0BtfemASw`g_DIRO{HWEf$Oya- zlyvyA7qcA^V}2M2-Oq#H_fv$VBe&s+1h1>*(F-Ut6Yk@8sf1_Gz*kj0`BfdwPma#y zIDI^ahp|o^i{iDLdWk5&15VG(Y}f()F(+-1PK=&#Aq7fVo7WzNcpHtNySK;f{qE<1 zrv>&TK{4&Cq&jmR>?5S?Cjl$OCcrq_h$U=4t^kL;XyM$LpfHNqP1*0AOvpj8B!?<2 zyZZS&u|<0%2@|}c5yyF()D1e(*~rSO3cgpDxLa&2rxR-}Z+YZ9b0->>NVJ4L1RQyt zt4IAlFXb``1!T6YR~!sf=#sM@F6nuVxKX4{m=VN9QQoaMibYy~8j39|;5ZjcgKo*) z_94nJpMGp;0AwR0LJfvrz-8)nO4W0K)T6TFe(J{`%+*(;l!Sw4BAexCxY0Kw2eIA; zR!3g_N!CLa`ng52j~rFkNvxH+O1tD+rXIW|A#!xg&<6qvyb9(y_~dx={YdRR?}fit z?<}intERqrlfUsrj@IUyif>&>1TbE8u!AD>03&_>-}!6&cFY*Z1F}4p9J~Jsy{3(h z1hS{Su-;yH5vX$k&r192O8hSU85snN$bI`3jHla|)Q*L$QS^NEBCa$@+fcIJT?`RZ z`YX9JQ)AKe%o*Z9TdMZU!EZu#-_eH;9iCIqN^bX!zvbfMLj8>05lMzIfO1V4-`1z! z7ecrf85lm}m5&~uF4%t!NpbFcYG~}cPTnkh@aPRJ5TGZCKjXn^-)r)KD8z%@;p{SkyuJsBT*3Z;=?7LTTEZ@t&P;126BnQY97UMet;a z4{~-#qL1TXNnGO*tqrLfvQObabFtuHIkDvhB=dyBR@tr_Uzk%5&QbTty7PAy*4_-H(8i^Mb1R(Uz5|D=;AXio{qE-mBl6>COsU0ebIj{vUgw z_gFcpP=KJMY=H+ZIUMN+=f8h9(H>9WImHx#J}MOgOT=Dt;Fi<4#k5){iI$a8%QR{ zwe*0Wq>u~$;Q)NK)#3dNS-5yhAIeQPS-xBeo)gCHf0?1LD;5@?&A)yTn+lZxsATim ziDb9$TA1t(Gv8MuB}==CD{WIhsnr1FPyPVL_OQtlU0HUuCuJn0(3)~)Wu%??1t1*^ z4OBA)4_qQ!TQjYsxqtNb`uAf@vm`-;o^$0fUBv_hJpL56-Lhi|<#+CY%T$ZTMX+R5g8bDpzz`&zWRdgou?#Nqq>@+B`no($F&P#*z3x=4ce7Ja^QAt*r zJo@Pgn>#gkl4=P5#TU2Nz?Gop>7re zkAfqD?l{6wMs4=ny7bHRY1QFvEG6Su2uMg>FfsfkcxGwViA0MbWnBspcFxXbzkmM@ zzPhN^yldL{_ibYtgW|KcQBitzEo}|3Z(43`n23lb(VcU_X?-RD%&kfBKE_+c5c?ED z&P1?~Y*lK~wFnwTLjH`BZYg+;3*a@Ym_6r6Eu zLuKrj51UV)siRbkp%m;Ie#NB|Zrm&B`y8Xt-PqVz>&r`pp4X(hNTPf8qc!f0ivIqY zB&JqX)srk^?$t$-W}tH!ev9m_%~Ld-y{U?#6nje{HL!u$0aCKy%p*iRfAZDtbN8lPEahTLOne(o zwl?R(jd0x#Ktx6oB)^>1_XoYZFlA=azeLY$MiI6X>2`?_%cNx9iQPmX%L+i-?Fd zYf527Vdu}6$-<4;`u5mn-Hx#ro=lY~0+OK#|S+q6Zz<;Glj%u|!+kjLzG+;P( zR`o(7m$Yfj=24o!3!z6Ok1B4q?DK4 ztYf2S^sKz}Y5F<=D5|+%B@P;KM(`GnTx26ps4_amQb~W+{Lzu*QlMhR1zaKt*iWw; zX7*IpzPKN!K?Uf*IBzFu$dlYje34T~uSJ%DTZVjn zc$=ilWzLqG8~QD^O0b;Ad~()FP|gV6T5g-uH}lJYOT>$&-TC4^WtEhW$4@RBhK2&I zY!$C9cs-&N10Ij4@{K9~BxU>t%`;}Y(u%PGozIPr^|g4QQ`_Z<9Rdo|9N*Xea#Dsdvu=R3$A=jS?z3}_b7Y=g}7zO6Y_>S(fDaU?)7(m~> z1U!hGQaduMGP5%hr-AUJW23NeWK}VctSDXlmAwcVp2)U}hE4-PV!jNN;CrnIK;Aw2 zF}0XYmgw8f2)ZFFD+4nU5fz=j4OiicN2)qf_JI~VU`k?jCQ77>dg7wg>=>X&im3?N z;=#_NSduD&UVAlFECjuG(-DJ)EFOGyz9?95Cih?k?d@!VP1da{QrfA@RR2*A&R&l1X@S7C{oF|p@E#@o^%+r}u;=X^9WEt1hYw93-g8~6 z6bnG_sh6++2gjCIR;H5O*27d>Vx>);{iV?9$5(dqcU^$sKy7Jp`Ass(mp(2|M!0ev zy)Y)YY~32#Z>|i!g1d{FJJ+$jeLuX6KGBh|5zT%w2G)rIG_p1-qDE?tGWGsjR+iQ) zH~mKwuou_bzRKc|PHF1$*ibGdrh|`s@wu|kLEBX;E3SA7S5vxG^AiP@y?9iL9+r6Q z?GCZKd-^@5eT+^cGb&adXbgeghJEVi;c}!?yx)YB!6KbiPCm>hCJ#CHzRfohD7KKg zVIu6<0-iU5;yzAt&WBeV=-~amg6XwY64SR%)YO~9qFP@htKb&wcL732xms1QM08K5`kk0oZWA*C5^ei;(Gw(#X!lPsib2bod`xWu zwZ0Q#4sfr0mY|i8Hej3yg8K6hzMm%Wme%_RKS3GnINu^>ss(y00AqC?$S=PjrK{qC z1Hym{nOwh_g5vEY8-tR*(KWQOdiv!KL(bqO_0c!rJqdUg`xS;tMz;f5f5`UYm1UKs zSfGg#<$>P5{rouwZIfd4aiIzbAU=nmST;SS-eX01SOT?$%ke;)ED%}2{9q8Ur`tN^ z$(gfp;`nmIK)Rb&_@0WXiAmqZdnbO!#5<;<9^(26YR)Pby8X5ZBW`JDD4>B9-np2q zz?v64AS9pOV|v&2+-suXnk3Ej)%ta2^Mj{hIsUv-bRh7izh&F2zJI^RMk!cWOw632 zco|aDc^I~8M@ z*t}_bjG+;i)siS^ki_IOyjNlF4+Q>DW#Yy$>tB;U{2!XW!;$L$`~P*XYi3=NM8>tr zy2{A7*B)7AlnB{ERw?t|s9Ym6ViXG{cIz5EELQsj3x$pjt!VzuUkiDCYidaE6)qd%*DK((64JS|Vy!8W!vv-JP1;Qw(aNZ_d5)uYrXO6^$OuVgkm1UNWR^0=a1p((KE!< zv5dID+F>xnM^{SkuhzMvTM+n3r)UByxeiFT1<9{oy_#>`xkeszVvzZ#6uS@^cpcQC z(e8IJuMZ@kk&QdxP$IDRZOp+|%~1Jv|f(69=rrz2N+!3dnq&= ztRx<5w#6aCl>Nz0c_#;qOY-(zlth982~!qbBT~LE0m zTXC~13K)A<<^RUk{@d`A^CS^KIRU+^JPgRa$g3j9p9vNh|1(~gm;z!Eww+VFP-HpF zZcmEJkKy4kQu8_a^|Uv&O_y*nFM%TQqN^P|fPd$f?_riGu+0yQQZN0scbMohWm#Tb zdG>Ec^8ur=m*BmAfedcnUv!Envi$eo!H0$PO#!F;>Hiz8Jwx%}vTQKNW_}x+bYN}S zoKSbOrtkAXNfLhSqx3pg*Ml%S(Vc3iw^!%8O4)pWU7LhlTAH?6J7fq`=Hp*pWi-oBO1grp$3*Nry-iSqp+5%n9l zF=zJR(Ze%g9fTHuN0Dg&$y`zqgH^tN|1ciM0}~e)uUL5$aF-PrX}z1g|1#BspY8t#L8FkOX6LGVT4Pq`@Ra!YYwzlEtdbT|lj>gp^>MLOqa|cF zG&)Ne8ANMp5`}dl?+asM^LGL}BXZXx%Wq}Sj5FXAgkzk*f4U- zqwj4SXG@$a@vp7EuW2up$|7|1Xm=YuAgCrsU6?##v&2!^(BK$!WQ3p-YysIB?%f3Q z&p>{^SYGkLoKD#n-2U8=S@_5jI@v2|4=(JCFb^L@5Qh`F9qG}}JEI$vt+E;t??<=Q zPM^Lga_Vgl5>U6CJfw z=ylSK@gqEX(GA}U)rF%d0?|#jQK{-h2Z{huC|EsyY#2%>{;K5Qe=R|?5m@8M%%J09 z~{`SZ6}#vL#ksz6BOix1#6Ie0Tnv&QY)Qg$ULo!5=C zK)!FEllp55N->Zy%%AYIkX$prEt|qCX@y^dz+4gwl?&vV>AzR^b6qKN*ZBr_wzgd& zoxR~jj6lcZm7PKt51%b?Gipf8k^?tej1}83H#B79BKtTDy&%)|rWTk|ly5jJrC!vo ztA3kXpo1$g23A56Yy-j~H_wJVWW^t8d6sj8 zTAp^>0!y7&`1+Ki_YBozf=%(ZZ$5>AUS-3v)GPSXN!`_h0#>{fZ>x4Z0^)tzap%rf zKq#8Bwsu)~`EetF;CjsxWwM6Do`3!9s@Lm5<^yx58@CCGHWD?GW>>-`YDW*O8G-c2 zs%v_3hu)Ar5DS?wI>mevHY9PAw-UwkjM`{c0O)_ZEIEAfuWQPym6b>aNpR`TRVV7u zBE}HWBg-p5KU>WNDNg zS+Gu^(dDN9Q3QIp`#=^*KQnQWJkWh39&)v$=xW9ZDg8IciyBS71YV^-GBLp6prP3a z;E;w9xEq*)pUIyaVeWZ2y*}Hu__^3*&tJ+Il;QvGA}wwQ4l{r%huZ z#}M0~j;H(gc%O5jr2SDBcKkL^HJ*3Mp9!C|fKKLZyy237E-+qZynU{l-Z=-hJG9%$wM?X;N1Wp;T;_VM5wv#{t?{)XO{YO1=GPqenWk4q*34cLF zw&F=1HyCG3suu~Nx!Y_Eie~)0#9kM&^X;z}du%we6_X+=h_$d|q5j9_0<~HL(5(hX zYjaJqSX`4Zdl46EFZ4tmYQ+qmMfK1rbFa+L{6*33cu=&QD<-h5`d%5gMm@}=fkD7t z*5%}PI3iS!ntyJzytr@=8-`($r?7*GPWS~SrdFIq0C?h)4PD#^DaV1)p3kD{ap}P^<80FI370F z2I~BMdiMlSpG!_KlmRew%zhNOZ34$1NbA7!E~pTnV4$7h-?_Z3bgo(AQr*bR@pU2B zu%IWAD_^ZKuP`EdG$Ur3GA!7vm-h%fmWh}DG`V_gP?$D9KM%w+^PsD1k^`*lniP5d zxMdpQAgS}8wRZqI2kOmC#d9atjBT4W%DjVvmD6sf2L7~TY=5GxC~p=BC-K}rf1=ki z<;ffljJ{LW+PL=mO$M}5c{(?QjmgvgZ;<-Y$Q#0De5^Rbd9W)?@=a*q#y|#nV`9$k}xm|4?Qdif73kgwC;ER`l zviMv?E{RA2x+?K$gv1z72+MLO{>!`BarM~DS zr$SYJ@gE6ozt@~Zwf zktV_L60Ay7^4E)ZZY=*R$--H`>OE1h&A*fLcSe&3{`hR>Cn;#)tcAu+gE>UYq|4Ze zIGW5qVA9eX-_xb1BJh|T91yTW_XGyyh5Evj)RZ}dYH?HD_vIJcd$gmxkVskMl|h8~ z0Lpvlb0KJGXGkCKn2T3G#LMZ@cg65za?9v|E%%GV}Zw`N_`nQaH=2nGc;c>@zBs zFI@K=HK{U6KBke#s}TngA_9t3f-6mvw5K+!a862Zu{@%me@3mw7QA>H&h_4`Xgy}? z;?i5G=afxV*8Pb2nrw)YC8ng#KnmT2EAsD_RH(FKtSk>A!foqTgQBEY?i@yDoPEOA z?4%;9_JB9x?{k4^Y8E04i4*{X`8$M;k=r#XUODV)GtZw-;Evir_2y%uf?c;_KiY!P zYv952PkUz~Y<_Q{aMe=iY81CPBAAB^%x_!f<=|tqAX;Iut7C8BE6ndfd4699TSIn& zX!_t@tl=#xZMD+*8w!QWEuO0LG74&stYZq!8|fbo{yPS{Ritnj>8LSBv?f^xr^s?wE2SaNmETPqiQNOZJmn%$Y`muIWaNu&tZwSfu7SXp8qgqK{!RK;z}3Ao?*opo?mFk zY{>1K{kn#wrW&ERGsOpXRc(KP!SgDYT4ov&y6U&BU2>YU<`xRoW>U4&L*C5yv5Z=V zi451qs@i|f^=}haI}9lr(3Ex9F$~o1-PND(-{$O!L)x4Dsy4y(MwN*3Sm`oQIX#MIF9 zan?eNcMWo_!s+enVYWwKXxo~Y<-tfs{9QHD!nc1hKgD(z8rMD^@)|7AIdx{>@;qSj zE^s7$6;9XPYv^8fJ)<~0J}9rQFNY|NGgXo(2^8>^XD|c{cx%Ks-(cD8F4M1CzIM%Z z@w-1Y!H(MRJ3Yyo=--Trj&7`rsjdEEEcQF-ER-ka7!@~CO7SEUe-B2A#!*TNB3~3B zK9uS?OAO`ga|!19TS8PT*_gRm>JL0SNB>C+m%THL2pGz$gsZ8!?cyi)? zTY72uCZ{ihks(tWVpDd{1QJ~m(Nj+xsFMu%RJjin=9XXuF=_y zf1ZS?JBRqMv`oqAAG#GTtZxW&3Bm8`c>40Fa~Ncei3#MNeeVa3%=G6ZxLwz@wBcdG z@f6CLKIb9MBtpMPU}Nq=*xiDx6_fTr`3G$>OteZ8BVIVOJmY4RX^^M6s9!Sa*J5EzrA=%Q3WdJ7HQqL&2ghYHjkMOG z+~#|;Yvo?oMvY;S84V?t?x-=I^0kzK&C@Cta*q-s-qEtFNC>=Ip5^mGSt&=;fkLy?l<#wUMo96JgC^?NJq(-5O!$q7cVkn`d8>UToeVVrO(0bL!z}+ zRL;iS+m&o|6_i1R2AWEu9cev1JyqVH9!%QN*eO{^e#kw~aoU9GG!fTJkv$k>hf+c# zv2$aYuV5OQWI+kRFINxw2ALig0}+C+GG?SDfW1YfL%MbQEMKkwY{kYMiQEg=s38|c z)on!26MM}hZ!l4wy?E&JGP=E+4O;ngI%Q?uqpLS;Q0II>%=Ty~spVEAoF8F);WY<^ z0dC7gBw8!bSkk5s>ifIm+rl8WkFWo=78oV0+P*lihJ9=i3pFNNU-O-nEbhZEEHum3p8+VOWHnDWo7=ZVp&*bApL_uh!jGSLbLx>+EyL06@BsYoj( zAqCUwT{VC{`V=KfQvIT1RX)#O8h~U94avq&4vP%%J~4Ke5UT!c-3A& zQ2xw&pZTh1XZjRWKl+WEOBN28vOuid)vTmt$>T1nHEq+T;1-yBUc9dMOEk^|a*I5*%~jZuq=9t&y>?>G(d^)`{P=lGi>+U! zc`}WWcU>Toh;|aUBrV^S^5^`&%3o7__CX*=6!NlLG(tC9F)GdfV%vmkzJ{b1v}xl6XorJeU3{a5K?$F zKCrWS+9+}=R{*1nito@?v$xx8LU(Dpp_^PRQnzR0?N`dB+{nPAy}xCNG?_V2d%kML zAs|&+n`KlO@9ykwaFtX#&Xr1uC|t?}`)~LAEza(vilDRt+UEtmFtB^<2oNBubocW;RaGc^X0i7x}*(caY@x2{mAycz- z0-;-mTTj#=bv^9nGfB<^{>wrMqhN0Ym?A$)s#@+wd#}i%7Bc_7JSteOL)8ZG{L;_Z z6#+T_jA?Zi=>Es!8o@={AdA>TByMt;UAsxL29}s(v6iOV-ge8t2+D>uFl-Pw0CMfC zJ-t}Y?6m6~4mY~fr-f!Uh#)4u z+ck9j^tDoLFr7L-+zhPdpnkFv`v%EWmKzRPaR800iP5O$wD(T9#Shin z*wpuI6r8^PLP#gFM*Cp7dj3t9aQRm%D|r#Avg>-&^QeU91sXLok=Pe*lPl4OB>L{5yd?Y2^NANCN`L-zQ_ zvTRkTdr6uq4$&=5S68}fF`kz*mx>?dq+BqRH+`P45u+Q9EduA1Eie%y3VAv~YH2;A zBb|2bjvS(>O`|xP#by_#F~a$##Tp`S@Ro~Lj< zU+lM!2mfG^#LUIVB!(!;!kt@uP`!+@%W|XSChd1|+K*}6{n`)}0dikS#@SN#2K1`NXg0bAhw@BgX*9TVMzfeqh?5S5x8`dTQVkg(9-c~5XD zYHZi)SK%#Qs9w4In|o#IwN@G!5OyB0u#5>9dB~sU%u&WQ#h*4lnM!3nRBDZgk#an7_mCQYueV zbZ)#5Un8l?PweJ@jJxm!OrBjQp!07Bu#_~TS`u;z1a{TYBY4|_EBHDd#cEjar0l|X zYN9Ec93gz-$;HyB)b!~E3DV$mWTkslrNB`bRD3_PhPNv%H`gVytg+I^I%2zF;l-2o zEz>BIxr;-mPXcs?sbOq~&0I`=L0mR_sJDKM4iP2b8gQsOWHR<(hL0{ZWxzse zB9KCU*T$d@rPbXiObc2rV6Jr!@5y|SKcB8WOU#(Wl^k&CGKfLnlAme$lsxY(vn{#u zqp*w?ZK2$WWsS!XIsUW-c=P+IF;&K(;y`>Hb$USQ@djlBI9+e&)A4%jT_PTwwm+Dk zqZRo=-eiwqGCoW0sLzNpWAY#o*SsssPTnu>+*DiBAg%#XPZ3z6=f%X_nnWyLF{bJwEH9@ zWV%Qz(242=BmQk0osSK?0-_t=n(K>;hZ()sPxsHUm%35g`ZoJ{+lO!5kWM=7(ZD*aC?mWTPE+>4qQCg5;tZ-dS24FB_oFAqx zEeU!7W7%H+j20Aq))III@}(Kx6(l=VQ6=7)M#6T~;Vqz87*%V4O^99c}KUHdUev9ryOzG}p4mH<>f;61J*S4>65F9d=Y?I`Mw*UTg8W>;! z+asi;M6?EvlvrCx7kDg`x%5^c+uR7+#nX@9lV%=8m=3(9Bbh%64sCNiG<9;8oAJ#K zTX@^O)liw&3?Un=%08f;$ptF_;B*Lkc_X6+9{xe4$eY0kX z{CbshClF1ZXhuRB>mK7ISL1{TBm`&+Llx&=9sI5*F#Dg=^7oY(EaM`+oy!-^7k>Q} zLEWlfqMJXP%IJG*DOQUHuUnv!Z4Va+Czr)Y^a=x1v zylnbavx4dLDDG)wg`91`0Gk3G4Qobhcn0OAbm}ub3XeJGgoGS(<3zEehgV>-uaXSr z{(+jU&2-td<=3Tm*UF^X+Nsa%MYol}PX7}AYi;$z(dW^Wf-38Y@eBOIVTzF9S$$b! z=;r=nNjcPFM07LvO!q4hHb6i^V*3&id1&Qy3sWm6WKp*y{jtrIw!fEGOA#%dl3Cr{ zDp-Ul$@eogv0rz^flJPA-B$H?TGH_suglX;+t&W!9=y|$e+Y8E;7pL|NZ>pU>|_7g z1u}HUI%Zscs*pV+mfX^DYT(qP6w`KmI1asA$p^OhDIbIT&KD`XLi@HDi`yYv)cI1^S%9 z{yQek42+asGfAv z;o0#cgr>IFpQZ3Dc;#4s*Flg#u!1rhd%h(HVf9^WO0z+APA_)fZI*goWyBMC7PCO? ztP2!*GaLjfZ43}tXE zcM(dlt#EwL8brbJ;#VzxjC6-EmqS<3R0^L~RWQ*OMxqRe+GJMI?y2_{V`veYny2hL zPm2eK+1^E=uWb1k24A~5pU$_a)N@5-hz_Jo<=IFFdQM2Z@{lXCgu>epx+bsL5|ed~ z<_Z2)!Zui;_O>Pf2Br~fCJ^?~F+q{SK1^J9{^!$}=m~GbT_I*d3X46YVd7~lGk9hA z(0=+6uqMULC{h3JbI5d($a|Ah*k#xEUf2Wajh^<3xq{wczrV#m+43W5G3$qQlq+k0 zB9xwdFZq7Vo5rLQCl#exhK@nRycFB^bP1v2ZI~ihiDgfYYcHnVnN_uw6+#C(k$W-q zKAA!@>DY_!!vllHFb3bh&Q_#L6?W!3K$@L+J0J$CEZ}Q4ft`P2^c@#yWJ`mH=*`4o^*((D&UcK%~MB zwie<&%f0Iv_}5+Cq=%4&$2g(SjaeuAGI@L7^FB~To$m#zLz(wrY(RltlS|6aTG9B+ zqR$!MF2XF#bR_F^>dp9)9)_^FZP=m0gb_Ze-s8RAjGAW*8abg7xzn#eO#a?iLLFKG!IT1j6H~^L z_u#>cCt$e954g^j^4?dWspYrnp?!iXOq5fL{+bd z8@HxNj0&M0(dUdHIPmDX3v*Y7kS#iu^pjKr(ENRK;4jQ8`L&FxQWATY@wHbKm2hEc zxMH>T+Vd_~&$>Kb7|CuYJ$Cxva9?xunjQzWV&}<`2C4QQJ(cGsJf}1TGAt~^o$&Os z`%O$uj<>I(0=(>Y@i9oNlv_035KUhHdhU(ccAitwtOH}_URt4<5u}fult!~Wy>~~>GA{P@`BVWM(cjx1B7Dg% z@In%G+_lf0GfwlQ)j=+={)54&SSfu-jeERnZLy#ELgUR4j(3K?G6~kn-e}4h@8~xc zJJt=&-Oj2@aL7Ctj_+RPWh5SOz#!(|TZ>WlArN8)M-rVf7Z<2~(a?{+rzV*J1OX(V z)J~maRoBtE6iBM_>ig5uvgPtNJJ{Q8KAGpTY~oSxbHV7Rp^N<%&z;_#kHhh*pr`s@ zfG}X)@)#0D@3eD=&_ce5n*~X9vUxm(MCpfhoUIE`a~xPvV|}0*SuW7a>t{mdnAY3e z>G{`r_c@CVD*oe3HmJ11Qy2-CxxRi`9*uiyn0fiuL>4hKKM7wa@hOFVn%GwAAjGYa zrv6UwS05(X^3O1{g9|mTp$Z|FGJIQ0PzTnepeiG#dyf1Ni<-^H!;s;x1jHsl|1vkI z7n?9bi&WYE&5%w4Jv4Mi$=|Ewo?#@9g~M7dB}3Y`@dYyO|pJ ztxajq^gR0@+M~BqigbMb{%8C!N#9esr6*AjB^Y-!UD%9sN3KiQjLt+}RLKvAT#!t= zyFZA+E6ZL%fd$*v`figHQw`X4(-ZPjal-A`~h?B@0f*pVJTaub9< z7h{QW_;LollO|fH)HXIypfg^M=uSL}#ST+bP6+XIOACbR3}bIxb*XxKqG9jX*le@= zeD6iZ^!CJgDw-X<{aPDbJW|ATQ#1+xt1$kGdcK>c8mMhsXLl!5a&W3_bIH zbIk~(B(tRFK!!n_+gwq(EdwOn<&7vfcIT8K^kYBcpQ~OoZss9z=Z|2IvT`{b;^9WWHCosoHMWyTk5t4PSq&O?tuO@UtC#DPC;SiM>)ISVV8xUKqzSS@$sTZX&#~eTe}xJah=0r1+DyaL;8nV8!;~= znwUI~hR78XoO3iZ?O;Q~;PWe6mTQeDoidpmA#Q*nVb=;g1=Xe1X&_)Os{CD}8pS4Y zyLLA{1r6myR2K`=#;0SMXy}(RfYRzR`~s@vL>FHjOV*3Hsg?ufuuvFj1~^)p4jc zAdySLB%4@UfLyf7IZ*axnLU%81d!E&k2iit(QkKDFp_!f?H0 zO%RQ4F;8{1xEkL9m*~Vj9)YVfL);Ua=G!9D1d@n-<0k@SWuqlw7B35qrXfz!839K4 z=mcmOEHA7BFa&-H{uGy$DYs0vG|$a-C*Q!QRVrQb>M^f;*kMw8P_!9 z;eJTa&^f#XaEHl%9DBH%sQ%4g3~meu3|%A5+Y6r5--Ej)?#SI8VqRWpxk%4`Nu~;{ z8I_uX0(U0VekHMNPNvsDTMcNEOXhnd6t`awL5@c}Y=aR2+ zPO0HRv1X(~;aEAfQc`y_vX%DWl)uoL^dTlj2)UVlQGmFTjjKk4}R`1o-0qwGq!bJU9`a{%FFEp;L+0kicwK*&0G$<1OyQc}8gpJ2 zm0T!Vc_{d0VQ{ti_z%+_3<%@R64@}+Ax8QOerzYUihV5FNZK?=n;Q>mzBKSiYuKrT zmqDn_-w32Jc2#0n{BaP<1KfTvd;7Y1Gt|5a8T;UGf~KnX2Z+zyNNdiR!pWg`BN0L+ z2YsM%hc$f7s`6YQ`u5ol){g=+fGF1rR~0FuINqZp0>PDle8LoP@&0j^!{n}J`Fv$W z;2C_~YI55u=x{tdJ>`CO=b$P3PaBa`C}31JX^9mp2vwg`t1dv@`86`Hnaw$P=+~9#3p6&`kL4~O;q~vL7R{!YS^^hA!99u z%onW%0*CXcrl|vf;$|0rC7N!jS;Oqcfb2jr{kvHMu_7?hgPWQf2A<|9rl%%f=!uJ69Gn_%sPcLlL3~#_ zEN01~>~!7Ws79fcId$(+=DN?U%|({76LZcORm2d=4rn$$W)>5|>iQU`8T{Tm z`ql^f;i^W6_u5*wC95AbKqJlJS157rzjivNC{2HNYHxZc>+>+KM0Nz&`=$|`3mk)B zB&L(Q{2A6y_z+bF0tqrYnHq)!I59`a092Rge&=CCJ(8jF)`;#4_!m^dih(Gp$x&US z_gnLEl-Z8fULgOjYJbpt5?>P@v&6Ue$VMz<3Pf+Uv1Y*x|68iy47e-?AWVW8(huUqLM2u&VoemfaiKBnD{_iR}7w; zyaZAFO4~0nwv>&4d^;!?IskpUtf)hh@y1h{8cwHo(9?l%5%`R&=&HZvpFAEKx=RhBmn z+Wllo=0B0=&4t}XXz=(0?V8gjlzqir*R9bYe&pikDRE+1kE39oaF2$*#vw=I$z}Sn zbG*65^~nmG2Bkl)=T~xB9&G|HSS}7i?FN3$wByk;ieNv>No&n88IVDy$7)+agyNSIU%$t#1 zYp>W-boH@IHwF_ZvxE6{cWM^IpnyN_w`=6)Vo+#x@Y=x!H?oufUUMG)IWa+Y{3?k; zJJr}`smeoLGJLQtlXJ+S*Va4$rFxHzYS63dC)jbP!a!KqS!DVi0TDBj(|>3o@Cr_KTX#3TFI5owxj^B#mfmB-MSq_E%*5LAV{-an z8I#71zs}a&!T-MwBars;wlyxoHIPg^1l(CM(T`DbOO>z4n5xONLDjsWKUx`kK0o9E zM0+X(G&yQ}Pm@5R7b>z4b7_u^xo(w-e8U6iR^vPDU7zam5AiE*-ChNU^jO|(;m+XK zy+&Gv14N=#hp&veEIlHy9|`*ccMG>PWV)rHp&`S5<}~O`De-0wzm%pg6=Np87@m6D z8F8zhWfxT)aufH0?F%S5M!xEeU`78I@=wUjkQ(=XJ*+iB3$Q(x>T)~_(fb-=n=o!j z13??nl!Iwl%u5F+lp~=p`q-8<4$I_QUQ|^4rG0=cV654mnMq1q zUoE~^TpZj8Lj+82x=J@!tzo3Oj^}Uac3R;y_ZBb}+>TAHJH7I(o8-ZWC!8?l8S%M- zZpC^WbyZ0SEOgNhU0;<|uTndt(=I(M_ z%*7Udk0UMzttp|k>4{2boSsd8&uJQwfW#^Z|GoVDaCN{ax=#=UBu`T}aL5p}vfJWA z3+-vrj}v4nLuNZCE1T_j2;IS#13wVDy-bZDuM8l@SE;Z6Ef zY~+^j&!d%~(;exXoP6Q$m7t7iBLH2VinT_&EZ%!63BiCTlTKy;yXhhw&R2dJQ_(es zTK*o_zF8?O2mi2LTVW4_rnMw#WuldASkHRE47gGLe4l^(^L?Mw>IbLE{ZB!3z8U`a>G$Y9drb z;T4GfWeA9^%LWY#(+PcLAOPmNM8BCB(^0+}da8)PE*27cA>L3DO6AOk5g$Z11Oibr zSQOE8Qsbvxk3NkNG^YX-m@JrmRY9q7O@A+lw$hw!x%A_E*!aMsf1SWji* zzf*WT6dbg({uNl;pW#=^5e%w-e7X}|kKP9_a5_&J|1O}xB zheUizr?T1KS{k)<{B5pY0F=}-z!`4Le2AF`mh9y|9Z7uu3{5LhSkFoua4GkMYDPIY ztqy~r$c0bB11aKu)0Rn{J>s-JNEQ5s5cVnfC$tx*IN?wXEs#X>Ya@^4Km&LFE~FJ` zosXw20c)y_RqD^?^6C(^fDxM*DmzO6V{l+$H(6C04h<+1m0gSsjWd4qw0_p-?ykgE zDU`AM_KE+;@c3KOf-wN7JgyrS5yM}>E1{ckAwb5kNy7&>Ffj#~{ zWVK`6gOmH#z3*Bd{8$=`bLga1U#Rjut3y0q3I2L=a+|2W$GMzkUC=f{=oWm}~;J_TQ)V5iPJjVJ~lnV30wm0L_Bm0lTUt z)O#@AHoQOn`8DJEX3tj1$3UQe(WdUs&I4CWFVKbe0=*=!z8nQFVBmJ0_rfbCUpe?; zxZ$tV2wn&T=!|X6t}X=izAIWHeegU;%v@n-&ml>55Rh(Z*M!GgXba#1Kv5b=S)?p% zS?@GEJWUdPMjg>X;KfZ+zg?+M5RNP@0RIbBKlWk&!{3RC^GI+#h=SmHu#DFQ$l|u{ z1>Lu`wVnSqTJ5!Nv6v22G+%NqY^W%}G>jW(ifWrO1oF0w%+Vl7UB=cG77iI+ssw;tA0J3@}sJ05m-xYzSaYcpJjb$0FpysBRj0M7yV5sUY zKmt#EdmH%kA|oCbOJ1*9U7Nm0tpMa#u|?dThAJyrbIYqrj#Zjd48H?JC>Q7#Md8(@ zmhkj{(nDQ!PrEQ`&o8y93leG8s8@lke}ID2X?t~Q>VPUjDaYAUe?Oce`(pooQa`Un zU>O)(^GU4~Lshrs*Fr5XzL)nMzcF=bd-I*m^TRQs8Ljv}vK#z_IxU zcCd7X%eCkBWe$d!K?CpOGhDr^*3UwGVVWiZ9$y(KrKc?z3r4Cu21HLj-apIG@Q7Lu*+g4vX)r*5lfgTJqfAN=v8yT`5ccBVAF8nQ`xaXE zdDmC1w8SjwzNVc?ruQZ*N=AC99MuKmsgP-5+pBN77tFo{O1~ zcGYQ(FbxAcL^`A1Ny&p-ZMRv5a<2VBBob1jkdd|KF|*0FwAD#&;R<4*X4Sr@$Y67`{S`4cqLh!!B{Nt3WJN zy57gBB@6KIE}#+jPI2cJvv8@;%Y(e?Yok?ispk)F%-(ZgAVgdJvV(pX22-Z{RX~k) z#|n@mLONCAh=Up!X>I!OIaYje`!EU`;SoilCu+jnZ+6=Lum;Vad>0z~ocVW6M^Li!ob~LYGT&IH!u9LFYkQ-gqxQ^Lho&PIB`UDqk*`|1`Vueh2N(gXidqQ zq#lj$Ynh+^e48wtz$3Q>*QXyz^ZcYDgeIX#!)5th^FhWf3l29qPUV1S97=^ZV{j z4AF09^o}*HhP-Bik&D`1F5`m$GC_$HazGj<1-SDZp^!+KM^A;SaBA3CD?>x3-0g-> zn@yJ%yLztCSQ^{%*J&pz9a*4!atso3iC*uRjIy$}{60Kh7gSL}fNDBBCP9h|_3Dpc zgw(fo+YLwSJWK@W9Y0zVq1)!m05c)W)E?qQ={)gF_#!vtdag_rh;}t<+9;XpgBLNk zC+(s_b|+=L$(UpyC*808D};${B;&w$jb2~AKm=`)akxffLfgTc_`L{ZgZAbZILN^U z3jo9cvF_C{$TVNck}=dZ!4^5=f2!Jc)Cc9Fn$~6_H=9PPQ_}h zG=f&cU9Ha@bz`+@=Lak(C6F-Xm%_gEcKDUQWvAjyVtL>A6DC1E zS+Z|Il09}DUS2rMh-Vgwu6*gz^p2|dCK-U7=zL8em)qOGkH7IS*#pxKv!NSvJ(1`B z0El(o7-82P4dow4n?(zCp{rr2!+5~-bT6k96%eJ13)U@ZEG<3k`0#hl(dajhcC{*y z`YRq5C=Els`6^O8wLW-SzZUm)AV9#wpB{0S1}}!3Ky$$%&m>tLExJKhpLOg;*4Wo9 zm@oLsyjohaTK>tH%9F&|bXCFISxc70eXkWI`kBTKZ1 z7Vpb#U&cCDxj={MLyS&T=PJL0;-!lYicSvUKcj+(dHKt=hzlVn4m|?7^N$+9TvgYX zeq4MFzRWd0e9aYY<;N>Jd!MH_fFqbGnb6H&^5}12Z#iPW2S__H_7`*ZTm{i5l*i$u zZ)LXgz!}Src;Q21%8iAIJBPgXXFBL2tJ50Fs-Y-^FU|9@iV_^l^f0*k>UiCXhx#i5 z!CB{(IPuHZ09Ml;Sxzoh*zU3UEFgv!ho9C1Yir${-n*Hj`;RJyV^tgfp|Ce&GujD@ zeaiw>G;Wv^o!>kWj-xG}$?av}(zeM_a&;+Js8YZ)jA0y2`;#GPqDZ4y<8h!s8c_zB z;cxO@H{If}C)=r1KUy7LTqU_Tn?0g`qerFecRG_5&C0Cl5bzy>10iQ8BwC zZ#KMlRwyiFH8sMI!#!cjK?;!2RT~79KN$UUQV5x$WYwHSv@m&Y zBfUEq0Ld|M!^W!Y3fsV3PYF)&(4hT~uOXRx3&dpW@vHFkQE5Jy$PnA`BXJ-}hzV*u z8&9K8?;M!}*fEqw**QOcgTkSH-|1@Oo3XAbIj9@%HYo?;d4%lVYW=M*j$Y83DwEeq zy`f$<4j_+r0{bBe`3`?MhA$Pzx@4EKHb7f^<19%;?-axNaWsxI4!b=dxHTy9PknHI zT_EIPT?$=B&MR$YH+}F4tRL}fN#DC#|Gxd}OMi;6YE02v9>baxOdVXDBRaChR7UB|eF~n`OIG;`7Eh7+rr`o;vwP%JkbI|msj1_Eb`*_Bd z+VlTh`lsq2TsA-SBCf2liYfT5Bexx_4TQS5`4mBryJXUw*a9- z8DO$1#JJL>T6Od#TOK(}pRxj`Oy>hd-XvASJV%H(V4{waXiFrtp|i%sizI5H>*Z1d z9WHwLipFQ%bddj+)GhNxclaC_=GVCtMX{B=-cXn^Ek<(*e7Wx8v&juTA1HX$iqT$c zVVDot(Hw{So_~IYn4_^=TxWfk7{LdHZrY@_anyoBd1C=bU6>zwZ1C?Xv%Mi`g0jHw zH_q&y;~cGH5oMyI&tmM|U9VDCG|f&ANq)F45;L}}I}WFhLcG%?Nn`fRMr-}gH-G}5 z?}dH&l_)Ik%&qQ9k&)1?eB*qFQ~2 z7uk{o%9L>5s2Rr_(;x3VSWkmUVSzP^Q<-1cAkaxs1i-wOhxC4ma+NQR8+2&~Ix{YN z0gd3*qvrod(|5;H`Tqak=O8nyBvE!mD9YZOLdxDsDJxs%IaFpLLbAywvbPh;4B2~R z?>!D@e6REV{C@YJ9v;`Zuj{_9*Yz6D=ksM(6L|HWO4J`vdtN?|0??`ib`jWP>IV-X za2lDm`9P|H%Xz{0x7){j97`GAZ$@rT=gO7Dh_|a+kN1bx2ABQat&gPgD2%23c+P<;a8LZ;jQhN9UQ=I#tqpseIm$EBMG zsjW1=g;Ei1x#MlH652 z7eGuac5CNmc^V9{r7(6G&E=%-VagcW06e#)(UK+;ZS0a|{5k8JbZV+(qsoG!uV3P>m2!>mS7Kt#fFVu36(w_QEs5(46h!C`>i}c-FvW zu4>Y$aM+nk>=_=U{ebDrY;t|;JQ$75MckZSm`@qF4H91u_gVVZQ9!+ok7ADa`7=oS z2>`7rfO;88=V|Uxb8~xICyvEWGxf>Y=Y5rqqj-8blKnS4DiF!`@uwQ8j zhPyaw)nq*vOTi zqZ}5%8FE{W2_cM6CX15xia|4?IjlA&u84gvQ3|1svjy0=lTt8v&+%(=*xdbf@C`>Te2Wt#=m`#Xpe%==QuV*jE8eLFfmJOJbir)i z;XcS^a)!_&v^C4}RUf;59=~bN{o|Dl&aIKI9n*(L%XN?a5lh(X-JU*)D;c_UJ_>j< zJtRcj(4vC&nj8lT^iN(Qcn|x{xKaFnAV&Q^&ex`e1UQ7p+B$o8vk16V=~kD>FEq%p z?(%6@Gdgqw*Y49iR(-Wx?UVu@n2|}W@(bqgzi_Bju4N$vy1Oj%9M`pt`Gr6WJs*s` zUdbmoOjHaxC3BYlktc_ujiD*BDQU-9UYggR&Etf@%rdWE`EuXv^D+lnvW#z)q$0oJ zcTA%r?8S!iw=^5fDSLUWh^6oPFUD@5nm_wr z@F{jdnU)9LsgayT%U1piAo8Or)0l(JztRDZhvwYi7ND~I%$55R6`N{de#V)P`=?Co z6vDMe7N)LlrSdp*KcG!{B#Ps9&eZG;MB-}>J-WWBsgzmap#~|P=F_L>cEBg{+wq+( zJb;rw_of3J??b46Rp!OJ*NAU76NH$ecYlBZt+<;q<7`LcYl)F+q3NHfD8=LqKQbfuDjqa6_GN8f_*~})cwBOG3mo*$Ut&Wqxo&* zrM@ot)U9NyM|LS`2B$}8p|U%3IGt=-1=*05UjSchznm7^xo#Dtej!dWBGb3=DBiSd z!1Of%0Vmieb7Ho0j-QvqgiKJB0!H7N1c!XVKUdb5=ZhwHN_8{@omtxSU_~aC1rN2g zN0Q(k3G^jZ%6b5H$e6k!zAG+nb5Pcr^iV_eg7ZWdj32J78cQre{ssh4Y`U7*C)<5) ztsk1HF4Z=b3qYl|=O;Tth$BOUzT*wW(@~J>-S6Ox8nVaaFuyLk`&TQ15pjsiq!mm{ z1LO_1=VpZ6SZG-C9gy^(_I{bKAj_^$Me?m7!B)EWqP`Us*|$TnNw_OZ441Hv&9d5r zLhXt7jna{*zziFV(mSP3)^9hGJjJM$A*&5zd8%fhpt^y9dIx%M2(DXfoG0NUJBEe( zK~(VO0>$|8)?{}pjhvZ<&sz5WzX%}%532h;5tg7vr*0s}BopkXyWY*8*sKSyku$bA zRL_??kqUuAW7}x{S@|}bl5k}yGVd(9{Y$~`$>G$D#UzaTw!EhGb*}ThIrt-US2`%M zBTjf14lQ!bf_O*Zt^H(`>!@S(uvy8vh>1Vpa{fXZpnQz+c|SQW$_FaJtnkVKzFVY0oykrkxk z%&InTI&2WsfpRDJ0|k2F;mn(?DX&@o zC7}IL@>HCM-~9=ltqRib3K8plH`qeY--TZ)k?f%m>hPh#OXs%$ITQNfMm|}nNUZA| zF+~sQoUe_7Gjd}54hRLQZa6N>{zSk5NxFQd1Yw@np~!ZkZZeZo?>j&CH)|=5h_ovN z{5Wp24_8zLa~Lbln2{U%sn=?aKmA&zBl|HU^s5M2usor4FaT6%PkD@*d$9EY#pA-( z+$!+}7+!!At75a0#ZbNlX$y!=W|Xm&6b%pR=_b_|*kemoU$G z%dQppmwbJ5IQ=_*| zs^ioaIh`yj@=~W*3CMCnn%@|}fQ_r2%*`1>9T(g-IXWL3kRO)}UM5#!hag75-)}!q zuU`pgbc6`j;{G)PlHv#jQoSz+n+2fU5P1$`m1I_J6C(OY--6_A)or%ta9m{Ut1GyN zX$WDQpG<&-X(cB|Gv^6u7>KIUubtJdr1BHk)z#CUrqf?k3lFQdzj1v3t@a?E_uYf2 z3_6I7nxz%Gdy@>>eCrQ7%4|qO?_eOc{poLR^X;oi(H~(Mg`G_G-cc>3cln zvF2MhI_b5UXFIt3=lL_QH>_26$n{N$t_UjeK;Nn_sYNz1F2n8|axnf#ybb~vwR!=_ z7YU*Gd+8$SJmJ=Cy*l786-*qKA3U|y=tYUKQxP_|CKV9gmM`2v&OKHXdgr*EUB1#A z_ZIj7G9t&D{J^;6)Vt-^<0V@SFXhsq+-7gVZ$AvygjPdh04G!w&LZVm^iW49q1?7_ zB&N^v3{(kjvz(oWirl7E3+drh(8vMF7XlFrjR6dc!SGm5mLPl)df^;PB%`tz@d>(- z^trsw#NhQ&fu;FA35tuNXqjjOErv_~CKb zqZU=-U5+bs#zu#vjGf9=MD1_1Xr3ptUg$9a`LiSNTW*N#Ay6!g-QlkY>!BViN=mq$ zpDPR!dnFMOpdUhttT`!hs)Sn(HMw#gfiT&3!PU~_VyW|0O&FV|2)7UwLAUH#?Lwqf zm%CS>jt8S?f*@m6FmOXB9&#FY6tO=jZ2?JwmrNafYgA!=!p7~LjYG2wX+?-_c3-T@ zax{~|T;1NDPns|Pl2gRv%w59! zpR#^KueQ$Uhe%Mm2U&EvIdyMg1vw@>8APSlqS2DW2Cp!^0BF|%YWU-e+dRQhcZcQR z(1~#u_7cEnM)U~iDLoskQDdS~-_%AqpJYj^ z4EDVls8DwPs<1O!Zkuz4=%$tOGV3Ob=!YIzMKR-m?MVWvo=`mLOU(E!BVF}Wg~_zG z?BPT0c>QViPvLA%_fe~N5C7g}Tr6AaF3eKhZAP#Dm4scoFIjlE;iZZ)@ma`qy0&j- z62%a)aL*TZmAdCF)z{bP@rw&^fMJtLvCw1F3apMK#j&Ji{RvOvYilce4& z+U}mO0f@BB{$%@_+_ZGLbQ8l*pP>5w>Qy&} z+`-wMmZYpz+Vr_|ka(g%mIzmtHvi{JCcI|{tK~w|e8Al~Mka@+)u9okj%})+F@uuW zwo5vnO{JK<>}_`<)1-~HwX}wdVO)lEMB?bzFY&=?faJ}VEpYV)F;sS+7*n+WeP$v@tqbP&2h-Kujg4Bm~I|s=~4Q zg$BcaAaRGQoFC!=*)-IQ+y$7~o2bq?dA=@TJ@Oxk;YiGf-slf-y5BM5J89c#eS<5r zpzcP+_`_7M7If%}+oCOOu;TqMrGFM0tD`@!{qVpI06WwMf9oXB}(E2XsiEApEx95)CfB$hf(Q z2SRe7obNmaPLH^t;)jWN0n;wS=CfhD2I1oPA}!wxz}Yt=f5qKhiV~L`V`zI3#Pbf> zlkQ@Qq~c)mNt_QLKbEk;?j_Kl`eMUxKdd?VVNFG;Of?s`(h|1N-aVO8EakRugHG6% z$pzbHzjwLVsIH=DwxQNxtRkQ1_8I1?nW`p)JPw+!^RQ1bNfbpA;y<9|H7cwHE=#)F z#}na52dWNd`5=4+M976#B~?BjkW>)nJ+w?PtFKW1$HPuMH|KVSP*uKigRU$rpSG$H za{i|*(Ry~Y4MbICoYRYdUW5Jb_HIM`E=_mcMP{Ecqki1Qn?PtnUie<3U9IEBi0f?Q zNj!61#L*VO8|?hKDt(EurzulGyVnc0Q(vj8$IC|5YJUsTMEF$CL6`jvbBl}X&&lgD` zBMDIh0`|VITq*h&+`9rMaBB z=u3x5dyJp#6(%*c<^YOD+MbZ0BZS*NO`xB3Ji(TnJWeUbCLvT!Amj|mh`GCz=O*tg zy4R~~Axh5TqSa?-TNvSIZazhYh(ywD!N`dGl9^x?q|WZaizd;qIO4NgCw(>uSvA^S zlS`k*xrSfUT}K?RW-X2VeEWQ(%CS-)X&BI4ibZf=)l@AYGBc~{*#O`125+}U-f{a* zY3Re#CV;~Pq+$7ZYkP9sE3GU!`bi}XuB%@<8QC|mWc%gu!b}n(P8`C9U{`=FW}VV5 z{>{mekj&vD?^S?d!A=`DdOFsoUI}`k8rE~2QWcu}m$|~)9?|)E_Vl;de;O?$NGd0A zB_wJ+j`0Uf4n9>8wy@Z7$WbS|!}gQK1YpPfPFq*SFgH2v3UM5etmy-~wAtYiaJ0w8 zW^cCxp_+^=wQj(HCpdlU;R>5pBh+1+n2upu7;=N$GS~uNl<7YGBeaLd`N_ zPcF(QT`Px)kLBwXhgCRF1J#2%Ii5nS$xromW918B+!2hU2RGm-P-|ThJXJfzosBkI z0Fb$1(_yyI@F~?fBTW892ni1a`dpU2+8EjY_!?2~S7W1*7;x~%wW*$Lt-I~bzfi!@ z=(*WQ-IZeeG_Q^h_^Ps@n$dx(}B{jh}N5T;VjYiqI| z4Q)*IUQToc0!I50mY7TfB4==xy4L}`;p4P#Y2Yv0sr%0qC?MlM72cCoWlL3)UA4WT zH^klZ8fN_+?d;Z{MaF}j?(p|mS`}g|jetsBm%1!e4eKkMf7*7sBa)w&y+{@xs3H)U zY3oBEc~jB8vM0IP!*=4-{F6TrUON-|@j|8srlh5aQUAl%18h4h`IT=FZD!HVRBbtC z1A#5+CY0Lcn(sUzN76oG-~j~)3AXW%58ZiIpM7N3rVr5))g!KmvwNYe8gj}__ii7HL=qQa5a3tUV@w&(kf8LBg-v=%3>xxp7Zv*^4q88tD-W49l-yQzr zhihw36_n@6lYHG|7P*$WlzV;%3gKHyLJy` zV^HlcQfltctsC$Db-cOsRGkifr-o4V6xwL9x_Lo&&VleX863UEm@GGSZ#LV$7OuLr zLBz@Ng@H+yY<*Ad^yC*!rye2*A)7l6+FSuS{~iEg zmxGAkbnXMS0b2FlC|Xnw%YnGa>r!2;*uSiC zOWg^E+!VGE1NFRGz-@8Uc(+a*KE{0$l}nFo>I5K!GkFn?x0LwEPEX)|vb3ezV9iNy zaZrA=Ae)sT+L%fT3!BKnmJ@0Z`(6A5krsZSG^itUY5GLGhiZo}l;fek&`kl)8P%Z|#94AC1gm zznW*MRK(YCD1;<{g#_7+98|VkO7WT*PpbLg5&-o7?72~(QzEvB5vV)#aBn$4)Y^2% zVRNiW0IwLxofutFS-J7A!qiRY+?NN%vyk?fAfGlmNOAuKMnzdxo4z8sqJ|wJXznzg zV18L*R1$$?&L#8Y;{dOCuVY%~F^ahfR20yBq^cEv6iq0=g=onK3p5T&m{Mibto>%b z4+I>AUmqrbW@!Y3oB_$5`U~ukE@aAZmu;Bn#TQ$UFP^N7Og=n4*cp0(!HNMan!9X1 z2XcN3e~!TKObE*hpLAQIHPQPC&1QxFLf6Xf^SI0?ULgWEv^mNW(ld{Nx<%x4Pu{;V(jtPe9Kz#3gFC_p8sA>{;5b zV#(+}@$-o2o$&*{2th1r<{Xy>O221E8}|BunDNQ(13DYr zaYBu!Pm7^b0iuMu6SERKa3vo+%wgpr<8Jj^AIi6|H`Nwqza?Xtr`=!qZr1x9qs67u zgV!i3_M6xPiDvtmjcq$ZZQPXZj;yGG>}u1o!bzo}rEf82$rAT`ywTTd`U(=Hyl(8z zP*FLM1ilY~qij(mpE5Mf%sxMEIvjYgB$h&FIbbetX7lwmsGks@5oreLX!e%hES`Zp=$Ad%~)NbvevTLbnr>d(2PA{z!k} ztxb|3Jn@z+MLKBz1JZ-{yV;vCTs$*9&)Q?)6`MQ3=VnN z9EC>T%uxzwrO%d05h8K>gLErbF94;2Y(xTO%1>LAj;4Sb_8+*A=GpyMI?ZA;fERVY z944$6Rsf4^g$vO%izT;xKI1InI`89uDjzVaeZokW==-#~On|L)081)&s!#&o{-k!} zmgV`xXxBfL2s<;wX4%+&N4!+lJ2*<;`8r>KZJS|<@e>0r?U4wZgWn&Y@8GZ=A}EnQ z=_k~{+@5RJq+4AUx(3!?kRF+{lRS4nd!3+2#o7i_-fS2RM@GGTAAN{EkXi(&M?lr> z)7W?q3<0ZjdF+>qWRN3?SmqYE?(Hj4!V`_7#rzL!t57gem!ZwHku|;!xd2V&n=eK* zZ*K=-8TbwNgZC9y^mLDH39w))KJ42KVOC`N6cw~xr?kn-?jSEpC%mv)8MGR& zPbCBmv5v6(Q0CZEF-aZGP9M#FNss6oJwb$E)KH>`kswBQ!!e$@RF@7~eHDe& zDt&M#*`r#InD#V>#Z}M2RQS|*L#DVx9W!N0$fDkbX;4uH1qFYs5171i$j346&?#*h& zMl@N_4^wf)u{-d8WU%oo(fVGkFJ~tj`&?E}rEa~|aU5ydp8C{LV61h0?a zG!AqiEhW%JE+pc@p-p{*>AgxaXso`1jE%S=@xZ$5HCE{&`7SwT*6-lYi~_Oz<++M% z@SmV;)XEWzrNi`9APM=32F!l<^FLqjKi`_DTsqxbSOSIh{||kB4&c_6`%-~&qEC%B z)8MH4nZ?8?CPgNYf#E}ns}ri=01UyJM1JQsVbyOxoH#~Ok#e2#Jo;Ug&B0bkG-)09 z)?opBX^eT?zhs0=k=kb~7Py1{ZUg2XO8(rHcBO zrlbeyZ`6x*9P7a5t_}7QgBGtg@1i>6rH{v+Eme+MloKW&9E1zQ+jNkJ$8xvF@40lb zz4B-^uAAzkroawj(dWBf*2@Zjr)YwxZ63q|fP0#!C?qQQ)=DQuQop*H7Lz6p;0-_w zCRx@c(}37vyspyeUF(&qz3KGv=5(;)ulQlk@7}1~Qly&meWl@b1ImnHz`mt}NhHa!@AeA*|blPcBFWk~s(X)ESU$ zuKsHP%l_GMdc$$rxYqT#%nt<9cPVvVsD~a%59Nbts%f0%BF=Iro+xrdjIsaitB#7F z&QkY;TKB|>`{^KImRlV1=WYioIJ}Kp$9`^eS`;U71xFLe?1tKO2>g_*x@Z~*?H+%? z)@(#dtv4Xu08DB6WQKpT%5me*y9HZeW~=C@G9*ajOsv{6D{Dw7J)Pi%F7=wL1P#3v zI2)Bs$v)e2;L$06Cw4FqUb&Ul_W@aSaq*C5I*I@Ez}b0ZLKaUY-owZ9HUPL8T&3o1 zb{-5)wnA_G3(IenG~#^Fjwg*`^-(kg9?6a@54zhJutpf#OwYw;pCaKX7I|kK1{CQ1 zw!x*ra#1i9KzmqSSo{B;5K)bAQL&~*X=fV(-?cCy@%4^ky`bt`tJ z@_2VpMH$(5r9ZWuC}%=|WDorx?1+oIXlp*E`?u#+CSG6VwSyLMSi5kP+X^Il=8M+;K?ymmDP`!*s5S zirW2?AxH1~M!*y<$Rj`Zf&F`Kdzu_gu75M(+xl5>I6U(jyZQDH`))g~6qmy1+*{p@ z-7>i@pPb<6%tS3ddKa8hmRJX(NE#_lI+7cHyFb@yu^<%w&PdDn;lHi4X>)d$`*f{5 z_fVDzJyt6uNyLLsBR<16;xKq}Ov*O!zDB>cd3`>OZCi>fNdc$?p3qAh*s5}ch3O3= zFHt}g5Oo~i*HxM7sj8_}gjwB~%bep-fYe17Bc-k)Cu_Z4uJagr^}+p)0YEYXNN!)ZN~##gmX*AcaHNrYq%8-pWLK*Sb%Mub4i zwsjQ1jR`!PKKbp`Sm}o~o#k5qH-H^dMdW~ZS(Eq?^o3^vVa38K_yrJ+hM8co0MGQ? z%O!bTFlO+{lST`=kjn(ox3^d&G9>Yqxco5r`lfZtcj}V4N_|7hz;kED+;i4V=e57n7p?Pb=uV%RSh-?Q`38WO zZAp2>$B8R~BwfI9q2+KTy^Z>nK3=8&0$qoSDti0EbLc)Mg8v41h-5 z=lpQR;8X6&v{)G8ZK(x_Nx!A(5r1ZaA-y41PnYRJz}s=c{EhM{Ci0r`wyoXxUhm`S zEcyt#$|g*_R&`x!y*7&y$134bfXS=p&B@K}{?&WSr{2xg$tG&w4{r^WX}`nBF`dhU zJYmEzE73ChSa64xtki5D`@Y4gQqi#I1LQj#>oNrn)f-eqM`U6tD++AD`G`G2VYrTo$)> zcR@rHU_yRG~61SgF zIqc}_TIMiarw_Br2#i%)CTh1`G4~n{=#T=K%#iwniJ8Vq2d>u03H=8YMBq4YT8$1& zrr{88fmvd@&p}*z4F^D5`~;fae+fL~l>J9IiZK|X*%`R`dy)ho$%JV)EH0#LFT zVmr{;m19nty$l6Y30^f&z5@t%c;?wtRr5<7^Y9FgIrDe$F0O7hxO_&tO)7p0oEPv= zChAoQtOcDX)qin%x;9W&2cYE3V%_+o6@-NfO#5BXIM}o5uk^VtkT?O10_G$Axu1 z)K=BfWC|nLPVjVW$`}3KHSsyWRPsE1tI>Q{r$la#xpSDwmuIF|YH_*6#Lls(nb-P`G zY;MYXCFTBUt}t7x(~4a;_|T!d^AC}IbtoyX9l`qT#*?f8yx8nw(h?wr$f$&sST?-* zaofDE!q6_0AU6b_eKcDt=)rmR)!VVngv(T-zW#I*B-?yaw)-^T@pFjpqmz8lhpRyU zg#RhTIQy1H*2t3lOhPwl9~Y4zI2;ZMY7g-NzB_o(?!rS+>G)}{*@u_QJP= z^guG;e`AK`hSqkR5%5FztDgGL63%ZJUWrCVugY=KVDv1To-J$wO@;v-B*+F4)_-4@ z0m|&z9$-av1AWl0&TVi@CYU-k)rP|en%ph05DIwY|GtkR&+v#1Mgm~qikJYSW};vR zB#M0qTql~@KDJ{8GThHqr?yu@cQm!Z)zVexvGE@)pSodB)jLay7(Z(1WlreT(4C~D=ZfdnUb zguA%j*k(&IA$%|rm&{pIH1J#1vlb#)VoWljj%sM~{gT&Gvj{IQbenA=CYBE>GSPov zK!$q_m{k|>%=K=Yi=D2Rq5S>c$=Bd)j|4hU$P-`vMrriF_ZC&h9QI1>4C${I9V<{ZI|c{(*SB2*LGBDZA1HR3fmIYfoi?t2p*8M(zV-YdUZ7Dw;Ng)@z8v`=m&Rld zo-Gq9MTTYmT15EZ$Z>>QLLw0AzaE4Te7Ou~k@c$)U*3`~ALOSDiIP4V zDFcz#nTa8(XGj-R0IylS74qM<1I;uZGo`HI+C+;ECm-e`7%3fRn;IQve`FPHJ>~kx zMW0FaK<#$Ai4|?mx#cYiXy2Fdw!eVcE-@bPdMtFHxpdFQv-QHGp?Kuln&jSN0Q@FA z!JoL4MW)lN10EjFf<%!wC;;1Wd@j#Czh>Ddilj1Ty-9e*mxVCi1KUQDlEbS0m6!q~ zwxrqO*kc9wm}g@S&@x<{`G2Eb_{1fb=%%Xg_Ky)rKO8cB)}DF`m4ZUYHBtf7rUoa~ z2T}Mt&;Oe?-gc)6u_;;Nix;X|Yh7zRVbZo{^7JTB4)hL7ja_ik{X+(%Y8pt!uI%_q=+Tn?W)G| zxNpf(k*!P`9iOrFdNTz?@rS6NQ8@(yU6l+>j9xPRJ?{&^{Ftus;J?c(&kGSA_2*rV z?uWJXz5+Htg%*I3ytlKma6{N4vaqHrnuCtN5HdhqOmTzXM00z1y(Iig@CBTw$MC+J zTe09-ru*ikt(enH1(0DFGp}q@w)}ACveJ8E62iz14BVJ-dCFwaB;!!vkLDQ-i?|hN zY2L(bk`aerd?$C^5Hp$uA%)^F31%dae*0%kA-8I0vx&__NU+1pH)8>q*tD^wK$xkS zl<1@LXL=f!Ch~` z@e9~*k^RhGdX7j*{kjhel$3q+=~Lp`5BnAE$wvFpD|l1FaG{h-0@t>47n>_8hUz3G zi_}e%vl_DN>nBgp_P-MYbP913T}2IQod`*m4Xz2t@Bq-W=)%Sn@g=0L_}oO zkk0^m3@q+FZj8y6p&)$~yV(8v3dX$REe%EFZ&2)G2q=(HW6~*jRUz2 ze;;Pw;^p0)XDTQa5D@U*FNs9rPZJP75+5WuX!|<7u-$QF?Wg_eh+Be#(M!EQ+vdHO zD>^0B#DN++zQ3Tbph!{^GyJP)ihGQD5Zf$B0gcgatPs6?8B78B$N=(xd{Tp_or(XS_~`>LTHA;AfnfL$PEel&V1ZSZ{9CrijWxyg?(>WwMJ;!}P! z9y^QoXWh)@y>)BPFvTv1q)U9|`emqeK8BP;(^ES#PT8^tv-I55!g%irOYv!6PtVxq zW>MN@XTUCOOJ$ZM%og(dH2eM6KueH-N@sH&OJ04W>;1D*DU!zkbDk zN^z9R>*^Z6G6t_OFVgw8%LRy`IMnT`3?&%F zNmQQO+SnL9d-g0-Dm^FXcdup5932HTZvR9&e~vKu$w@}7lhGFIpA&ZjLe9@Y2jSjo zX8t|H5EuETT{JIb^;jIRd~FM2cq{Tcy2IOkdga$vP_{aU{UkzWQF z3%Ruxk#o+E9(Bi`@vfDWl$^Y8n`&rKwor)N9VU##jnFvC9*(Bg+R?K`0Z66ouV24J z#l%)z)&{tvN~1ys311qyrJ#Wy7>E%N65Prucb*>?7Z=yGUXm=| zhwY+!vy8ZN8H#pQBza`%zPjJS4dy_HH2|}~I+-Y9OFt-k#AJT? z9VKB2q9>CcD!iV4oARDEYOk#Oj-iK`ki#@C$f9`;Ew{>X z9k-N_m*2<#c?8LKQC^2>o-$m+<>sep0=T5)^PQ&i?|?t=dkp;C2>?W~SNZizmZiT& zoG|n)mA)fQ-2GjhbF4>CS_~d^B>L;)>swq>BJ|^zSOY6_2pk&Ft|80g$g@j7zJQjG z<}Kjocbw~DV6O2QE_`2qviU>Cj%nhLr3VkxuTs>mNqtmty>vv2Hv?D`kE>D$*c(M~lqFj|Q zc0)1=j z=g=!yP|TzZkHN24zWE9AbXx~7oLj(*>>E6`?JqkQ9*l%)%>_d5L!XC-htX`WQ&zM4 z$t}Z6??XvoiK-aP<@FSX_5uM4FN~Ae*T)C*4f?gaTN=_wW+h5^k5YC;#gdF^iShA~ zzcbqr?puEgmpxxad+F+alFN$4yuMh*>L6up_wvb;*vg8E(|do{)?N|p@fPTdQ@XVD zyxwIYxH;iJGg^9A@o@=f?E_6szdAfXixp@5_^UMZ@xgwxM@K-Z<9lmPz;NGt0B_X)m<))BZ2(Nfl} zzEf^0&Fct%pec6T$=+~B0Brq#S;`r%%CPV8{BKw(Yf)rAFD%^AldFk_dMNb}Rv zXHnU|R7_1w_R$c<&NUU`63C;$~zA-+C&EwM_w&RxfTI5Z~0C;Trkjnnb1fuEH*Qod4eOhRK2_6>LhI6?kG*_OLUMzL6)~kPyZ5fIW1`lcu^9I%6b_Z%G;7eK>W`|7 z$}cMFk%EVZhi_(eY4Qu)t{DHNl4NXBHa~C89QO2NdwPaEeRSG^4h$~;mCP42T5=lF z)&wkEmbfM^B5TF!;@@Kfd}Ut@CYQC0flcF!-nB?Ez}4Mbv~2(SS(UM=q32;za#DFd z+S~WwkJwfV`oMy>|NJn30bWB3_Bz`z_s(8gMCxnX7cYtm3c9XcyM`GzaQW9pE(Q7m z{CgvpbDk?l0ihVxldsVJRUSP1t@;|BW^T0G(baP(0nPhoWo5~VSoKXfK>opz;4PtV z^xQbgzvV@W8vd_dA<%7Ce~)WF2;W|`;|$WoC}+?=bjFTW|j z^Q(!eql?R5&S%A2mcx&?J`JcG0x56{fj@u#9C^b5;v2lG{%S4gRp{Oo7lSB1V`OCX zJ#VgR;sZ}nS=s)RX(0GKdjrH2mP$3d?}L!^zA0V>T!4vaY`DCx%a7+OjJgXgQHB2sZOMpZ@%^L6hHjr#7-j*q;kCMDhK?SGWXDbHwjk#5NL@EYzm z7<6u9p!{1Zdt66f-=Fcr!wMe!#9&FrFvS+ykarKT(XC1l7_gS2kJRTp*ryw9qm~wz zO~6rRBc%zyIU_r#=%0{Xg*SPZ>?Pt@euW$QkK(aXmP^5Qcz zdthbXsK?k6t?DzHZ?#jz8ro)_nEuV8vpJZU4^4Ca190kW zS^ahcQzYDe=T;U#kMztO#{a5Jt`g2vs0@f<0>LIA;d~#1xOgfRfT>9dRdmFyi|YSN zEB9R2Gm3t?#Ht8`#<)7$3s&LZ6_Lkhg(C9pC3!)GnW*<}E1Qb$^EcAIl>XRrnJoA9 zdMb(}yzn0FmDnqcjQ%anbxKE6<1;hcl$zl9H?=o67XPuVeDm#OGdz_kaXYF{NfhP~ zbCgTMJ3s0NapUdgAMcll81rW>Hs3jG6w^todO$=Jz;4JO#((yK0%q0*Zo_}=8(+63U ze!jN3(+wWo&)DmB?d_4;bY$(Mb~=pUm8(K_KjblhMWCcq2X>ha@!Zs^^aqp@TpjKyvE^5~hIg@FJ(rz zi?9DaV!TMq!opIKk-=W?z81sEnl~Hu{WQMPHtM-b8koPl4<$c-;@SjaHJU-K5BZCm zdt`KUr4WZZ;8hfR2{OXra!nvUmZwl}dTFc7cv!UQpo7~{W%T35Ps_-NN=mNp>EI7_ z0!&amy89(}D*biA^dWU$>m?9#iRj2V7K1B(WM=yuH%wAsfbZXCHNk1%TtFlb-MWdc zfE;OdHNNX+0?VQ0?=M-$l(%F*2ZEf$%3_#LniE-O_#H6Z2p6_!o<- zJk45ZFt7JQys)^us|BpeV>1ws&?Ba4FY`eao}LrHVHQx?V?^vR9s}&)P1#)L2q&&7 z!uwT?{4XOgbJAqX&CT7k&YF7Aa!a@3>{%9A-~sjhi}LLk>>i~DU$C=BfrLrJV`(X= zjf_F~<$00y3+ZfNh*EFqz$gnw#w!lDG;tp9W;yKiN(M6-!L5+8d}U~v!uHq%fo|}E}6GZ2N!q#kr?Upy^5}qtC!%fp94L_sDDVel6ur(4hSX557-i9Y~QP>#QzXwi6)GR z`mM`R^3K8+3zEnW8xy=eOQ79GJX)^t$g=OLNsTTSC%XLwcu@z=I+u6MQ50|=eRg>{ z3k!>?uV1g#gJPTKUJuh?Sos6pmx4%1(wa3e=2Y{YDIL1h+2rUktA?b2K)d@?j2y@&9iMVa)7~F!$}V4&`I8JwM7;ajOFhbDR{*|$!L43|Hw(Q z*6rJ4i~lk~QF}zGnKArAqJ1j~IL5oinuiSQrDn#)dm1<(mUq&K4JV+bl$72st*r1I zqlxl?EiL_%52bgO*&P^4pY@1%?hoB!rOI=AiQgB(4_Npg={HN zf2@x4INYC_O6I|v9PMmwR)By35l_+gr0A404(#WrHy?hzsMbqpi)irZ(VU|_5R;L; zv%A04JwuXCcAao5xKx*x&iP_DHa2AZ@+>cZ-JAu!OYYtG{y2HQf3I)SUdlLQv1qL= z2gLPcg5eEo!&~B(EH^>*;n)#g=bf)k8<_(p7+QE|Q^)+X-QH*cLLJlUrvHT;`o;$i zY0vvhM8I$JqeMLrYP12N;S62+)vH%aK?&x~eJK##%Y(Uylk?+&`!^%weY$JCv9oic z&~2S27bZ6VNUv1XzuastwH$09267bJ zEpCs#_8=1R{_#Niw7Ug{Jb&C4%#>_nFBCRCU;$1^Ob4jr66u{g-WwDbSHxCLwSOALK$j)ci*TZq zw(g2b+JT6#s-nRGxcOlV7v2YfHyoqtEIjeS@!r0D7#~o)9g>erJuSH&lFW?tab4lu z!i_nBtx_YDLI2&xV)($>1Y}<3zkhH0fQ(=i9K&oD6|Y`R$>#3v=uih62N=D#G2V_k zLoM$WNubLAYwyedq5Q(XZ@ZMHWVFaq5t*buMP-{wiL@Y@5iym@o)$~inTkYN676&@j=M!;lfH_Mz0kVn;Ar7dF3qnqxnl zz`GMKe4V~g@r~cNf&-Vwcmay**Oj$Ltu{S&Y&rZxq%f^|MSpm>#M39wW(LSfQIOz= zVTGfko){oviVm#_T;}NykK%c|;hBP!)5UijPG08g=HaJL^77KFEi%YYpC--jyACy- z-ar5JUlo6yu2}d&6mCI=%u*W2lu@Igt+=QcW%@Y7OI2lMWuDM_4QOr|(as0iF3Z)0 zd7Mq?j51f$+6YaTP;8Gzw;r`b@s-4byStm4k6Qxfo&tN+STOI5)7)BoH*ag#%U`0U z6DffFW@@NUyDzj8-z&X++cVaCisTXkk=n&2%S;ndvz#M|Ah#8PfKisQ{qF1BdIX_! zN0CeT;qjcI7TBg4oj%vN zVT3h4_d$xEoUHw53_>?T3Y=x>$zoD@yQ3!7zTk?@p#xflA243VY^pRk z+U<4mB6}NkG|3=eRi|OIZk|TjKa#&8c$YFU-ZfG*0m4%NJuC^RA^>z&@YtITIcp4$CuP zPmhkjmn7lQn2sSH5k`wb)tb0RTb1+bmSD?5Z-vK0?XBB7tDEC{`)t2qg1EbOS*vptXqrc@f;6@eTo<;+6L2t7@?L$)anz$MpJ1N%LhAl zSVhp!VknrjL^T&kBa{H2X>WJWVv3L1h$b30?UPrE(bYVadjuq8KR{dbv6$>b$Jb(TN?x>*Qnwm6!1Y0|O1K8)mtI zl1#csdsaLJ!Q6tCm4LlC5!m{Te5eBO8)?l2{2K~GcDZav-$Y>rh=JMrj+;^067KI1 zl()*1#LkQ#oI4NC*Vka0Hb7oNfuqXT&yN!(glx}SNK&J*uNJFJd_yN#p*orzS+QB5 zkeFb1f%xF}YI!@ifVckQ2@f7@j=`K5{B*`V&NxF`LLMfAdFo6VW21&am(0{u9{p`c zT{^c^lDj2gCU;G0KE;AZ$8LXnyMeFBTmykJQ?{GeDw^#qn)ns>vz?%7WR*}rmvt(? z*7WP!?%o-ax*)1BT9pT|XW!4m$z!zsVB-1b*aJ%h*DH+;0(oy*4;*61QRp1pOi$xj zLlnMn64tLdt`;W}mDIHVhQ5e}#bLf}xCm?QRxPd9FJ5d`R8pF4q;3G1Vi0aeGJ&1O zyk@}xqR6QF5}q~GZXK!wwOLco#iQEVZ9jf^hBxu;=RCU^vwP2Ql_w@3{KWai-$6zU zL=$+s_4GLVKL8^#%Af0<>p;NHMu!Z`&Uyw(|4IFL6Alc;ZAa$X+sfa*-J3E+Gl9Ja z9Lh;3rf{c7%bW^ z6xZ3mPRk5~(UB`As!}tJmvL=?8AgnP=TH0us5dHR z?o-tSqEj-xxTG`Mp>*_ue2YhsNOfV=_F!osVP9QpXuX6F*B_Jb?(VLvqhTrGekTp^RuLi4&0i@a0oK+9*W&gY=d22-oXz)Ew-+YPj~K7NAXeCq z@zh2K&mWLlh86wipPj43BG@W>PD_M`R~}!i!vdyXeERzJYlU-Eo3b60Oq8Wd%47Ok z>nQs}DvSh7XX43|ZdhB}5?bz;h)H)zYRRwgg0caTN~8qM54xPD+(aw{bd$6J3LM?Z zqc{OHeSIIwE=o~{5)SEewky0CgzDAs04gkAhEt_5a9JgQ6I z_$9Ju`j2<0^TPtCS#ocY-ugz^M95i-T47Dif&Ras5P-I8KgRFFnHG8M9koJ0096~J z`k)P;ki8~VoKaEK@=v--=>d#hY_k4&P=6Io|83BCvEIW0ae#o(1)x2q*sCBn_sjV8 z&MsR0+y4Ik8aD&2g!>a>FJmEcF)hZVtwab-3PwM6mC`$=>Z0-wOqNo(rGAsYV!fbJ ze_scXCQ{}1g4=(#Im9x7C+tY!Ki1#_3J_@|sBPhkWE#VK#nm|QGo+r}rF)5@<=!T7 zGcz-z?<-TK2VwbnO7*-}snD7vHNAWJvNk!CIophywu2(+p$f%bLUlc7e)(3#s2DhNTJ%d*dAQLdns|9I%! zySB#0zO=`CwQd*nV=@6SMkdsQIl}s+P@hyM^0l85x!KthCFUQ%+G$iQ^^ZsXDysE$ zgzB@B%~}0>;M#f+j;!nK9Id)8)}~OABSwg%JQ~$e6B0Nu;|O_Uo)3I5E0I35)&^Ac z-Id=_e9(yAL=R7*-HCILJ34?eq!!;RZ)a#)OdL+<{;<(F#ys51O-3=>i}F8>rB4T? z!dP<~48`4~-X#KdU{r5;zJWo0cJ}9S{Q4oOnx*jAOv2A+=dg&+ttmLEN5L^!QeOT{ z6B(R)e{*du9yz^fs7J0OX-jcG1qxT-apoFd(Y3@ihcqUo`hnd=JPQY;wDJozhe-QU zOG?^+X)!9uQ`+>V?Dg5)FJDUb=Wh4F?eJU$51)96wj{DLK2Uv*^C}8~y1sIXK@o;x zlAJPnNvqe@;#ZI%SoduJ2nc#tfg}S}F`yZuVY-HnV)@lie=?-#>Ru8|`WL^0nDc2$ zs}L$p?#~z3!zb71E9!scaJej4b#oc+9`5dPhww0<_u_yu-0h}G$dlsopax~~^_O=; zgMCj|6_~y%Ecaa6B=!R^WPfSbr_>uz(NfiD^SGgQBNH6Qg6C_w1Sf!y;uPFQ#j6X_ zG~;+K$ZZ{T$E*lXR8XHxo*@zKLGNVy;Gu&*>0t$*GxA_9zh`i!INEfFQn(BP>O)vPzgx7|GTs2_z29YxYM5j3R{LU0)OdI7IYuo?=4lN}vBohj>QXJ^r^t%S(w zH`>igK{Fh9I~yr0?rhZ1_(bxmtNT(RoqAtX-a^p2CAH`YcIVEWJ=jz-$qa$_Tytkk z(Vg4wF4zX`-(||5=6QMLp!rtB7*57ldXPUlD>V@SLAOf5JXuy1inO}uKjMR*UqCmz zfKwG$K9H_{AA30=^=|O$yNtP)5XPBKCjFIf-VCP2ym~h3$=H3uY$cJSd#a?QpkC@* z&i*43qTQQZ=cFdiUkijAj|xN2AH?bFrys&x(=QkT-p>y-GnNA1;ua8{SEyd{XMIP$ zd`6=}m(?5m)~#FTkF1f6`LjT8SJdOhrWl z2dGe}5jgmz;Y(3Yokg$mrC%Rq@%j_T(+tK-?>^XEMd-ZjY;0C<4(qC!aX9km!=i$UGileG*v zPJH^t+=9HB0mcx=H)uM))bo<-=Rbe`l;c$M*5Bia5!`L280*xht}b1ww^R3btq=qO zBGTg6jBA`F){eEum!UBTcUEsEoU()K#GcY z?>Z9Rg9W5cH@lU5ZTSmpdwaQDzVZ6)ZX>G^Pc;ZPakor1 zst_ZKr)c8j_Zc%~`uK4T$-52pfy z$I%!-1R6ZU?~Wc`a0*K(DuKE4La~lWHs=;W6o)BwK zE6$g zrm8`T3oxb@b9ko(&kdH?V>H<4y%H2t)8pfVFb}QInJx)+aCi#xx315hZ=?01-=D9K zYq&$w2vsU$B&)0t=I2b_qW-L@<^(WF?y+z{#}e-A;SF}C$`Yh&YSR6II9 zPRd?IxR;VLu0$-8jCiXJfw9nb@v{zK9(v#o2ky~sE*^@HuMx|Q7YYP|noCuv1IJIm z3&7p&d3f&x(l-fqWoASmLGcwRkYOxUAM$mMBC zx|&-2JqmaMNfuKMT%coF@Lp@=b4<@FC>QJ{R14zg<|CFP>KsS|r%OWBIa)dHxrqfY z0g4%MuhNw#X5C%IJddiB8$|eUXnjy)er-i-uCn`VT+UC*lcjM??1XoIgDAqU-}JMNznhcw$O$3k#8Bf>#lXy3YStrERSQ!uF_Y(lSJuW_NvD7>(n zKE3p`fsD2w*MFRoBdq3?mkMzU_<*TQ6d%Z>V8!JhKYm{Z@^X9;%Q$E^vuj=+%U85)l6(9`>K@A)z6VA>j_`4CKDCcbf400n+9okDiW zYTdod4%7*l1GfFN5J7zKH*u;DS}eq^P(C+nl^1aeivSQ+PbW^%j0*p+B%mj7M0O$f3 zs`jY>C6Pp1TLHCv9pB0MYEk#wtDgXa_1d&)lkp~kgcv{6HA#Z{O+n6|HQk=Wh_*?vkB8e;2sAAufpp>;Ab&*-okJ~IMot00 zu^@g8K6{HWzYNoY=s9R2uFT$nflRD|LZC=rlbH%#BPIBs%U@IE1XkJ0pfEiB4qf`! zRBJ(o@SIH3*4C~Rk}==y9TnHFpRqEl8XR=(pWg}v##jMswpU4gc5M)wDWGG802X<; zUu2V5)0nKJ!9ayLo*1(LLJ^{D_b5G|g+xK=paa-oN^)|E5IU?+JxWTNy-L5cNRKh) z&(J6z$GRUpzIR57JzkoX90<}c#rz#m%X;3wq_k;BY6Zx*;n>`qm%62vWJZl?Y96)^ zaC@1ERp?}aE@4%8l*C%=YhPHMK3>TDW)o4cU%Sr%CEP^M%6ivDGhN4SZ|)p+Kur1> zf+D1_8*n<%*cZmX5T%-6x*Z)IlX(vZes?s4E*v%p5|sZQ@Afdrkh;_z2G77Ww-CTB zqvgNMgZQyZ_=`FfGw~@$(r>6CH7LndwSQH8eSwE(v7>c0`M9H3gyplsG?2gViMrQh zt*E4E^PAw!p4&{T6R9Fu=Q4;B?_Y&zqKO5qZ(1VMHyP;MyMp5X z`)<8J%>@S3a~13_6M9WPt?njPNmUbBg1oD#he<7lQLza6Y<7J7_{r12cTYbckN<2$*zu$r^L*NQFYLIxNxCOc@WScHxa-vO7Ck}c7oT;C{!?=613s8 zUf@WS4C1G=M0G)`UqgPG?tl)&+q$Jo^K-I4CkOT~F_^7hS~*T$3Hk8<4EMbHeT5Sg z%@yHu&`af2U7ZDYe)QzKYh%y8X=``zre1rmvwGS&3uM!lY#ps_CA|e$NpqN16mmz* z*QO~FA}iu~CH2R=SxHZx77c0k1pHNsEu6yd-RlN=fJ&L{)J=#v-hz$YyLYcL06$$_ zT_N5KnVS&jh6H7s2ldaW36M*@=srDe!5pDEi*}Wj932OqdpG$1u0b+?fSO_^n z5F1duedS#t?e+Ed@4C0iOfHg9MlGhBjSIsdsbQ`_Ly6mlJkFMtR{b;^(dA0|17awv z?0M}PbJY@l&&>CP=-60p2@)>11OoADx%ubM)yb3XD;OY4uJN~r%{J!dWX#0xt+?RDCQ-UW{`cc99*)f4h5YDec>eybpQ3b3mQorjV! zQ0{uWyT5ic@V9q66lt4~c=CM0xvkrDx|-{vqGO{2%m}VqdgvYxrKr)XUJ^J(#(uC$ zn8%n+LNE8O=Z7E4;bOEzvp+mi6Jy%x-~4r-E}BH-=|0*ij}&`lflwFH#~Ei?;dDbj zoeNMNf&nQRLa;xaESp+mjJMFU@9gyS-gjL#HYRZNTSIAjb#;yMIa!$#X4D=PebHU6 zE2?g`_N~ zZJF@ZqZh6OGvIYJO@&Q~;s}UdGBRPt?ik4^`6~P?r-JmW6$@v2n zb#_P7Tp{QxV`p1VGaj@NbHP6&9lraUgcWpxgF*;hA6*9@$)=uD<3yf^5Onh7V@T!# zmreoSGzt<%frtI|?>o-9+bow5VeM;kL4xVC&=UK~mAn^ao$D0W<-K~<^!JKpaX)!& zNN+Kg;NX2A63-Gqv_iL9N#ei)vWV616fKs6UhzlKlaYj*s;Z(!)NcfWGW!klb%xqU zyJ+#s6U-t;S=n&-u8;_y;FL}t7tY7`e14Bes#R8W$)B(r`}V+&`=5dH2M+9CatH9L zT*~0G#=1=Vczorhf1@%6&b#NYh#f23<|aa@<_w|MkL&qqY5zuCa!*Z>L6nq~ zdVFnGsPj4e?xwjzz3(RiIw2nzLDLIQhuYd(AhbkMQ7GKvYyuA>D;oehM0MVH%3fy@ z&2jqYxwqE+uWz=9Qo|-f?CtC(_C0~DH@R0T9RRn{uaG^fa)cD58_M3VRQg_bKb^N zlGbg9qj7TTi%u8V(pVQ01~l0Rm#hSLhW}y?lBdZD{eGY~`csTbDXQkO$jAg{R9D<; z`k(4^a7~vOVHiCdZdb&;8MF=JwNBBrMYUvH!)4W0|uZ}fj zXBSRs#@`Qxm8FX6%@NgF&H`fp`$RezFmIa0*d6mLQCg!|ls0)8b!`E8zs(wv#$^z4 zT6#Mc78Yuv7@!`%a5}1-doIYlWzv56;^j z0qx}KsIOm-VAve7D)!=o)3&IYY}nE)^U^-ERXHDPqizGmgWf-eo^T_%o_xh}GaA?@ z=Wa=?%aS-f4X0r~8x7oVzps3#Q#qeL%Vyh?$>j7_G9_i!|KzxH86NSpMEB|ZlQxY5 z_TBB)*Fl$pDwBSFitXwEwfR+*kaLW{Sz80j&l^~I?8$?~2duls8LH2#FGw#Da!?%$ zG-(@m0QZG3>6<;yN&?E&DmGdLQ>EnPo`cK@d<|Y+9@8p+S1Z^%m_?w%MB`>R5fEIa z=8K>4KyZ0X^V%TZeklUDuDnT2Y7~BjVc<;mxUjt;aUOUx3PwZ~^;#ekR1;AZ2!ERY z9{=77Dt_VoKqj9d9HDeT!<7f~r)uky1j&sTWIe-l(ArrDjW)p;z3A9*x~zro?al2Q zS4C=2`V7${T#Zcd1n{^?zx(`xIR;O;P6~SsZl})~X`)BBu zAu!rZ+BrJT{=$J;WE5_0>j9H*Y8|8qmK^Nbrrz4^cWyAQg1G~oP3GxNNi>$)Kfz4Q z>}+6yu$F(jD%ZMSn4!WSw8@fi#S&ReAMw7>Fa>bwExmK6hr?KdYT`3T2L}u5>+27Y zqGW8KNF|h`JF{W%uc}#lKf2lhPji1{r>-;MZ34UDI zPENBG+~Ne{baYCmqGU$K7S+%y6Ir4O0>PHab{9x8ReKgEG%iffL;bWq$+4EfJ_CN! z%2Bv?<{H{Ajs35biXB)?7@y3WC)DHTII*#@4$h!G92qTsZx+M62aYIUcZAzst}pS1vtz&?QL!LZZmV{6qkhgk#W1K-VF#9jUlJ+Yep{R7TStir4wyzoUy-u z`=yq)hfXkrrHi)Ixt)rL9Gm0O*bA8x}N_w0o z2_0|?q_6+^2Np%LBO}w&Me5aDaJqmQ8d9B=27Y5REut2OErOquVQt)@mWk{dg-&%I zexL?QQHUw3Y`D_NX|)3T+4f>sJU%zU*Mpv7NQs?!|9g)}xysl7 zSpH`@We6)jL8iq2vzzg%y4u7NIV336fDn~#2|I0k9Pc;!NC_YM!-0?0l5dm{MXoKT z&gT0pCccl+_FcCS*Ii~6DXa|PmMao2kF;sOgZn8eHc7TazzQ|V1;tw>E84_{Pj6sk z6t(E<>j#%HV%ROhk0|TveFF`DTeOx50UTy0_JJ@sh%=NGvaQNsyxbXd6qUPA?HAvI zWa_93b8~YaJiYG>HlNCVT6UxyB|N+9=70(^aTHzssvpALChUhm-eE#@x^pKg?{(hS z%^yEioDbe&vGE~^Xq>3|`lzaJp0M|Tl5*!K8lMkbSVie4eALH}2Xpgtr(>wIDPRJ6 znhJvvqLwZRah=7OLZ%~YYs>C=H@+whZ&|vuorvIU5n+TJaB*?Gk| z!|o$vBhzFGi{8FfC+hISZ5_jPQGoCV=OC)`h#r|)offd%eFXD~gEk^y!*v=z6a}UJ zwC;+a-S_72^TZHkNpy*3zKpZG751vKvS%*A!*&6hRicVAv?d1yPVB-$00t{kT(?zT zT(5_7z-bi^yY5dmX!oFaJO;f!%>`gC!l1M*()^v1covH!oIZB!7-tCaE}y(b(~teH z0R&*=+*%PWxiC@i_&yp|iRS3 zd$i);UV0Ce5)u;TX=F%@FMRySV^)38bZe~% zkbw%pLDS@?Y1h_W{`=nZzDK4B+tv+d4G9{Ixq1FhgN&W|0)(1h-K~sJy)N*FU%#mL zXb3=eeoC$uuBg8v$V+v>NOf}9pC2H%L4-}|tqVC+mw>GN~d9%Ha zX%&KDap?~zB3MO4lhrD{glFe+ej6T1K?S!1s0sAo=FOhxt~4wB`OB&fU)r-IE-8tX z*6JbG+8}eNT#f>*8|v!=r)UdHIo^4D0Bbgz@}WoofQLAsStZhS3YFHJ_Q*JAi{q_o zY0+D2I@bAi=8VoeNl7g>%*BS&?e$4LmC7!h@e+{Nr$q{|SgfG;K$@3=R-WsfaFZZD zxZ}KJRVSs8Oqs=icC~g%wtW*ILay#ptsePj|C-k3Y;GM1Ls)0v)_Q*y+*iORcil_z zHfSr6sL_qIol=oO;AW3xo<|D9UIb3H$BE(5%ZCh%+L2n@9cN0X1 zke241ioAF<8hlv+;=}TdIdgt?L&R#@zo9a4kAUk3#`EXDo&N5ZM_avQM+zJ?gYO)e zc{@;m4vQj&fd9X>b(BEV!PD;>!%w1%5_}Uz-5a=Y6CMslZ2OBWLg~Js@Z^!$4T`)1{EarPB(Cid>6ZctbE%ER>IdYPCs% z(flOk?R$6pbdcO-XO$*;W20k~24|Kyh%+=Y|Pr zpAbS>DvXNARa;+wB5NhDUZMW{IZ;`?(jH5i>a`=ew1dBVPY9=2cGA*xTpsdGqLpHBcWu+4n{?4Tb;hCiZdBBB~VPl2+Jpr4KoZM-Rf%&m8PXZWg} zh*T&%e8@dQO|Le|&CRF3e7Oa0VkI^WQlx#?J3PXvCr|_96`#(1$6{KEBya_Jhp~w!CT*dLAGtIJI}3%TuwT>U`y2ze2SLUI{!Px&v2N0YNzm z&B7*Ba<3AK#J7Hs`whuZEkm%d6Q@; z4MO=!#B(>AH&lgKAAeTucUep90c;04K2Tz(iI`@_@rO`J|Y$e;=2pJEF=9x;HO|lx#xxR$rx>#%KF8bI#LK z(?$z4=xp%^)U%htscAsz`GvMYxU*VkGm$y_`0m}iH7WFaGcFOL;V-xzd(z^C98Ms3 zXG)>xjm8U3?#+L*xDgZ`C|i+|y^CXC4;Oc;Eh5fdot}bRM?xe&d$qhmV0wOf6;SSI zs^Tx6E!~G3H}DS(wn_VjFBX)R6x%r_%tv4;{G9-}s1W)0SA`NG5Oyh{J8dHRTK{@b zzgXlu!5%ce0|=*35V}_(buQdXB#{4N$r~AD)v8rdTV;4t&gER>%TQT!jLwFVF zw*M>z$m#cL4N{F33HmnREI3$MI=yX;>0z^oQVwpiy(A2rmx9LtF5nhl zRhqez8%eI96^Jw$e7uf@05dz+<-wQ_|IO+=KOQo3&+%<&cPy*r6m{b5Y_*dRlI|42 zP>TJ&eb=!Ef8vA-3+QwfE*eFk5tS1cA`phk6K!4$cS{VOVtB9JrsovoyA+nfZqH$AOfd3Qn?0Q zbA`_nsyt4U&41V;dmw|BUA~;NMFfBXWnW+4WOSwGvvcpL2IQhzCxnbjqY&otk^BDY zv%}(O8<2tC*IXn9>ATL8zdkFJN?V;d7X)AdwRy{sgo&R&qKJ*8V zp{hY7?s)wDKw0)uJci3_XMi3)*a!^-gt{eJ5^5TJ*t<{BbOZgXWs5-va%V~O42G{2 za09~P*KHB>^}<&Tf{qDa(8Bk>Z*~yS!v+fXHK~ig|L-vWJ2(HotQ)G3p=7WEUDhiG zyp2AGuSAe#(&!)8EdJ$oRrLGQ1oYP`D*7u`WN|ZyE$$6TuulCy{14Jcx(rPMrr)Td PKlPC5u>%Drr*HfpG~rAC literal 46993 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 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc16421680a50164ba74381b4b35ceaa0ccfc..c8ba3b1a21bf014a0ed55ac2169dc48b4abb240f 100644 GIT binary patch literal 4664 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 literal 3276 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 literal 1429 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>GbI`Jdw*pGcA%L+*Q#&*YQOJ$_%U#(BDn``;rKxi&&)LfRxIZ*98z8UWRslDo@Xu)QVh}rB>bKwe@Bjzwg%m$hd zG)gFMgHZlPxGcm3paLLb44yHI|Ag0wdp!_yD5R<|B29Ui~27`?vfy#ktk_KyHWMDA42{J=Uq-o}i z*%kZ@45mQ-Rw?0?K+z{&5KFc}xc5Q%1PFAbL_xCmpj?JNAm>L6SjrCMpiK}5LG0ZE zO>_%)r1c48n{Iv*t(u1=&kH zeO=ifbFy+6aSK)V_5t;NKhE#$Iz=+Oii|KDJ}W>g}0%`Svgra*tnS6TRU4iTH*e=dj~I` zym|EM*}I1?pT2#3`oZ(|3I-Y$DkeHMN=8~%YSR?;>=X?(Emci*ZIz9+t<|S1>hE8$ zVa1LmTh{DZv}x6@Wz!a}+qZDz%AHHMuHCzM^XlEpr!QPzf9QzkS_0!&1MPx*ICxe}RFdTH+c}l9E`G zYL#4+3Zxi}3=A!G4S>ir#L(2r)WFKnP}jiR%D`ZOPH`@ZhTQy=%(P0}8ZH)|z6jL7 N;OXk;vd$@?2>?>Ex^Vyi diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2f2aaaa0a63c7dabc94e600184229d0d..bfb090fc604d253944fcabee1e019ee460c70201 100644 GIT binary patch literal 10085 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 delta 1224 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

?wGKN5P>m=1%)sND3~`BjaS)39faeQ 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

Ap^y&bmSMNJSYOXrydf$x0rnWoG%}(S)YINx{WS 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 From a8b0901ec664e9f1bee21222cd5d4e319e92aeff Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 15:32:06 -0600 Subject: [PATCH 179/225] desktop exchange navigation flow fix --- lib/pages/exchange_view/confirm_change_now_send.dart | 12 ++++++++++++ lib/pages/exchange_view/send_from_view.dart | 6 ++++++ .../exchange_steps/subwidgets/desktop_step_4.dart | 1 + 3 files changed, 19 insertions(+) diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 9f62bd8ec..540067915 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -37,6 +37,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { this.routeOnSuccessName = WalletView.routeName, required this.trade, this.shouldSendPublicFiroFunds, + this.fromDesktopStep4 = false, }) : super(key: key); static const String routeName = "/confirmChangeNowSend"; @@ -46,6 +47,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { final String routeOnSuccessName; final Trade trade; final bool? shouldSendPublicFiroFunds; + final bool fromDesktopStep4; @override ConsumerState createState() => @@ -105,7 +107,17 @@ class _ConfirmChangeNowSendViewState if (mounted) { if (Util.isDesktop) { Navigator.of(context, rootNavigator: true).pop(); + + // stupid hack + if (widget.fromDesktopStep4) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + } } + Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); } } catch (e) { diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 7cbf38384..7c5f5541c 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -38,6 +38,7 @@ class SendFromView extends ConsumerStatefulWidget { required this.amount, required this.address, this.shouldPopRoot = false, + this.fromDesktopStep4 = false, }) : super(key: key); static const String routeName = "/sendFrom"; @@ -47,6 +48,7 @@ class SendFromView extends ConsumerStatefulWidget { final String address; final Trade trade; final bool shouldPopRoot; + final bool fromDesktopStep4; @override ConsumerState createState() => _SendFromViewState(); @@ -191,6 +193,7 @@ class _SendFromViewState extends ConsumerState { amount: amount, address: address, trade: trade, + fromDesktopStep4: widget.fromDesktopStep4, ), ); }, @@ -210,12 +213,14 @@ class SendFromCard extends ConsumerStatefulWidget { required this.amount, required this.address, required this.trade, + this.fromDesktopStep4 = false, }) : super(key: key); final String walletId; final Decimal amount; final String address; final Trade trade; + final bool fromDesktopStep4; @override ConsumerState createState() => _SendFromCardState(); @@ -323,6 +328,7 @@ class _SendFromCardState extends ConsumerState { : HomeView.routeName, trade: trade, shouldSendPublicFiroFunds: shouldSendPublicFiroFunds, + fromDesktopStep4: widget.fromDesktopStep4, ), settings: const RouteSettings( name: ConfirmChangeNowSendView.routeName, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index c86713a76..5b69f064d 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -224,6 +224,7 @@ class _DesktopStep4State extends ConsumerState { amount: amount, address: address, shouldPopRoot: true, + fromDesktopStep4: true, ), const RouteSettings( name: SendFromView.routeName, From c3a3dd3180358f0f1e2faae0cf16c6d7a8b4ecb4 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 22 Nov 2022 11:48:52 -0600 Subject: [PATCH 180/225] remove Wownero if isDesktop or isLinux or isWindows or isMacOS, respectively --- .../add_wallet_view/sub_widgets/searchable_coin_list.dart | 5 +++++ lib/utilities/enums/coin_enum.dart | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart index d89d42bbf..38181b9e1 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart @@ -32,6 +32,11 @@ class SearchableCoinList extends ConsumerWidget { // remove firo testnet regardless _coins.remove(Coin.firoTestNet); + // Kidgloves for Wownero on desktop + if(isDesktop) { + _coins.remove(Coin.wownero); + } + return _coins; } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 48212bde8..f80c40f52 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -12,6 +12,7 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' as nmc; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; +import 'dart:io' show Platform; enum Coin { bitcoin, @@ -36,8 +37,7 @@ enum Coin { firoTestNet, } -// remove firotestnet for now -const int kTestNetCoinCount = 4; +int kTestNetCoinCount = (Platform.isLinux || Platform.isWindows || Platform.isMacOS) ? 5 : 4; extension CoinExt on Coin { String get prettyName { From 3306cf8b99f4afd54030bdef8746eb5de89adf6c Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 22 Nov 2022 11:56:13 -0600 Subject: [PATCH 181/225] expand the ternary for readability --- lib/utilities/enums/coin_enum.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index f80c40f52..648407809 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -37,7 +37,12 @@ enum Coin { firoTestNet, } -int kTestNetCoinCount = (Platform.isLinux || Platform.isWindows || Platform.isMacOS) ? 5 : 4; +if(Platform.isLinux || Platform.isWindows || Platform.isMacOS) { + int kTestNetCoinCount = 5; // Because we are removing Wownero from Desktop +} else { + // remove firotestnet for now + int kTestNetCoinCount = 4; +} extension CoinExt on Coin { String get prettyName { From 172b3d157bacc84dff2dece8b4fd0c11ca9e1487 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 15:37:47 -0600 Subject: [PATCH 182/225] wownero disable on desktop fix --- lib/utilities/enums/coin_enum.dart | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 648407809..543a193ee 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -12,7 +12,7 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' as nmc; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; -import 'dart:io' show Platform; +import 'package:stackwallet/utilities/util.dart'; enum Coin { bitcoin, @@ -37,12 +37,7 @@ enum Coin { firoTestNet, } -if(Platform.isLinux || Platform.isWindows || Platform.isMacOS) { - int kTestNetCoinCount = 5; // Because we are removing Wownero from Desktop -} else { - // remove firotestnet for now - int kTestNetCoinCount = 4; -} +final int kTestNetCoinCount = Util.isDesktop ? 5 : 4; extension CoinExt on Coin { String get prettyName { From 157829a933da78229e6279aeb3ae1fcc8409307b Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 15:53:16 -0600 Subject: [PATCH 183/225] fixed failing test --- test/widget_tests/address_book_card_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/widget_tests/address_book_card_test.dart b/test/widget_tests/address_book_card_test.dart index 7c53d8d50..07b1387df 100644 --- a/test/widget_tests/address_book_card_test.dart +++ b/test/widget_tests/address_book_card_test.dart @@ -70,7 +70,7 @@ void main() { await widgetTester.tap(find.byType(RawMaterialButton)); expect(find.byType(ContactPopUp), findsOneWidget); } else if (Util.isDesktop) { - expect(find.byType(RawMaterialButton), findsOneWidget); + expect(find.byType(RawMaterialButton), findsNothing); } }); } From 467d43d9f3c3c80b4dfc03c8232b56bcefeebc1d Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 16:18:38 -0600 Subject: [PATCH 184/225] desktop trade history scroll fix --- .../desktop_exchange_view.dart | 16 +- .../subwidgets/desktop_trade_history.dart | 368 ++++++++++-------- 2 files changed, 217 insertions(+), 167 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart index 0f44eb59b..105c485f0 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -63,19 +63,9 @@ class _DesktopExchangeViewState extends State { width: 16, ), Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Exchange details", - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - const SizedBox( - height: 16, - ), - const RoundedWhiteContainer( - padding: EdgeInsets.all(0), + child: Row( + children: const [ + Expanded( child: DesktopTradeHistory(), ), ], diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart index a8f825911..e31a87dd4 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.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/rounded_white_container.dart'; @@ -24,6 +25,28 @@ class DesktopTradeHistory extends ConsumerStatefulWidget { } class _DesktopTradeHistoryState extends ConsumerState { + BorderRadius get _borderRadiusFirst { + return BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + BorderRadius get _borderRadiusLast { + return BorderRadius.only( + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottomRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + @override Widget build(BuildContext context) { final trades = @@ -33,169 +56,206 @@ class _DesktopTradeHistoryState extends ConsumerState { final hasHistory = tradeCount > 0; if (hasHistory) { - return ListView.separated( - shrinkWrap: true, - primary: false, - itemBuilder: (context, index) { - return TradeCard( - key: Key("tradeCard_${trades[index].uuid}"), - trade: trades[index], - onTap: () async { - final String tradeId = trades[index].tradeId; - - final lookup = ref.read(tradeSentFromStackLookupProvider).all; - - debugPrint("ALL: $lookup"); - - final String? txid = ref - .read(tradeSentFromStackLookupProvider) - .getTxidForTradeId(tradeId); - final List? walletIds = ref - .read(tradeSentFromStackLookupProvider) - .getWalletIdsForTradeId(tradeId); - - if (txid != null && walletIds != null && walletIds.isNotEmpty) { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletIds.first); - - debugPrint("name: ${manager.walletName}"); - - // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData - final txData = await manager.transactionData; - - final tx = txData.getAllTransactions()[txid]; - - if (mounted) { - await showDialog( - context: context, - builder: (context) => Navigator( - initialRoute: TradeDetailsView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - DesktopDialog( - // maxHeight: - // MediaQuery.of(context).size.height - 64, - maxHeight: double.infinity, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 16, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade details", - style: STextStyles.desktopH3(context), - ), - DesktopDialogCloseButton( - onPressedOverride: Navigator.of( - context, - rootNavigator: true, - ).pop, - ), - ], - ), - ), - Flexible( - child: TradeDetailsView( - tradeId: tradeId, - transactionIfSentFromStack: tx, - walletName: manager.walletName, - walletId: walletIds.first, - ), - ), - ], - ), - ), - const RouteSettings( - name: TradeDetailsView.routeName, - ), - ), - ]; - }, - ), - ); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + primary: false, + itemBuilder: (context, index) { + BorderRadius? radius; + if (index == tradeCount - 1) { + radius = _borderRadiusLast; + } else if (index == 0) { + radius = _borderRadiusFirst; } - } else { - unawaited( - showDialog( - context: context, - builder: (context) => Navigator( - initialRoute: TradeDetailsView.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - FadePageRoute( - DesktopDialog( - // maxHeight: - // MediaQuery.of(context).size.height - 64, - maxHeight: double.infinity, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 16, + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: radius, + ), + child: TradeCard( + key: Key("tradeCard_${trades[index].uuid}"), + trade: trades[index], + onTap: () async { + final String tradeId = trades[index].tradeId; + + final lookup = + ref.read(tradeSentFromStackLookupProvider).all; + + debugPrint("ALL: $lookup"); + + final String? txid = ref + .read(tradeSentFromStackLookupProvider) + .getTxidForTradeId(tradeId); + final List? walletIds = ref + .read(tradeSentFromStackLookupProvider) + .getWalletIdsForTradeId(tradeId); + + if (txid != null && + walletIds != null && + walletIds.isNotEmpty) { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletIds.first); + + debugPrint("name: ${manager.walletName}"); + + // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData + final txData = await manager.transactionData; + + final tx = txData.getAllTransactions()[txid]; + + if (mounted) { + await showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: tx, + walletName: manager.walletName, + walletId: walletIds.first, + ), + ), + ], + ), ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Trade details", - style: STextStyles.desktopH3(context), - ), - DesktopDialogCloseButton( - onPressedOverride: Navigator.of( - context, - rootNavigator: true, - ).pop, - ), - ], + const RouteSettings( + name: TradeDetailsView.routeName, ), ), - Flexible( - child: TradeDetailsView( - tradeId: tradeId, - transactionIfSentFromStack: null, - walletName: null, - walletId: walletIds?.first, - ), - ), - ], - ), + ]; + }, ), - const RouteSettings( - name: TradeDetailsView.routeName, + ); + } + } else { + unawaited( + showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + // maxHeight: + // MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3( + context), + ), + DesktopDialogCloseButton( + onPressedOverride: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: tradeId, + transactionIfSentFromStack: null, + walletName: null, + walletId: walletIds?.first, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, ), ), - ]; - }, - ), + ); + } + }, ), ); - } - }, - ); - }, - separatorBuilder: (context, index) { - return Container( - height: 1, - color: Theme.of(context).extension()!.background, - ); - }, - itemCount: tradeCount, + }, + separatorBuilder: (context, index) { + return Container( + height: 1, + color: Theme.of(context).extension()!.background, + ); + }, + itemCount: tradeCount, + ), + ), + ], ); } else { return RoundedWhiteContainer( From 4debb0fff9d67087f20743c4c3899996085c6b41 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 16:34:31 -0600 Subject: [PATCH 185/225] desktop block explorer warning dialog navigation fix --- .../transaction_views/transaction_details_view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index dc4e41152..4d45428ff 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -266,7 +266,10 @@ class _TransactionDetailsViewState buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { - Navigator.of(context).pop(false); + Navigator.of( + context, + rootNavigator: true, + ).pop(false); }, ), const SizedBox(width: 20), @@ -275,7 +278,10 @@ class _TransactionDetailsViewState buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { - Navigator.of(context).pop(true); + Navigator.of( + context, + rootNavigator: true, + ).pop(true); }, ), ], From 67d375dbd5ba0b994fa9d370dd6787e197a6dbf5 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 22 Nov 2022 16:37:47 -0600 Subject: [PATCH 186/225] desktop new notifications bell icon indicator --- .../home/desktop_menu.dart | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index 60a424a06..d82d62883 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu_item.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -149,20 +150,27 @@ class _DesktopMenuState extends ConsumerState { ), DesktopMenuItem( icon: SvgPicture.asset( - Assets.svg.bell, + ref.watch(notificationsProvider.select( + (value) => value.hasUnreadNotifications)) + ? Assets.svg.bellNew(context) + : Assets.svg.bell, width: 20, height: 20, - color: DesktopMenuItemId.notifications == - ref - .watch(currentDesktopMenuItemProvider.state) - .state - ? Theme.of(context) - .extension()! - .accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + color: ref.watch(notificationsProvider.select( + (value) => value.hasUnreadNotifications)) + ? null + : DesktopMenuItemId.notifications == + ref + .watch(currentDesktopMenuItemProvider + .state) + .state + ? Theme.of(context) + .extension()! + .accentColorDark + : Theme.of(context) + .extension()! + .accentColorDark + .withOpacity(0.8), ), label: "Notifications", value: DesktopMenuItemId.notifications, From 7719ad32a5973958e4e9ba6793fb5a110effb891 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Tue, 22 Nov 2022 17:56:14 -0700 Subject: [PATCH 187/225] Bump version (1.5.18, build 90) --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index b1312427d..f79879f61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.17+89 +version: 1.5.18+90 environment: sdk: ">=2.17.0 <3.0.0" @@ -410,4 +410,4 @@ import_sorter: ignored_files: # Optional, defaults to [] - \/test\/* - \/crypto_plugins\/* - - \/integration_test\/* \ No newline at end of file + - \/integration_test\/* From 140e68948f084a5435500e195712f3920f523409 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 08:26:03 -0600 Subject: [PATCH 188/225] uppercase tickers on exchange form coin select field buttons --- lib/widgets/textfields/exchange_textfield.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/textfields/exchange_textfield.dart b/lib/widgets/textfields/exchange_textfield.dart index 8d3c5d699..399d077c4 100644 --- a/lib/widgets/textfields/exchange_textfield.dart +++ b/lib/widgets/textfields/exchange_textfield.dart @@ -203,7 +203,7 @@ class _ExchangeTextFieldState extends State { width: 6, ), Text( - widget.ticker ?? "-", + widget.ticker?.toUpperCase() ?? "-", style: STextStyles.smallMed14(context).copyWith( color: Theme.of(context) .extension()! From d50e1e4a1418a4fd1f6b6d697e73ebaf370bb8a6 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Tue, 22 Nov 2022 17:56:14 -0700 Subject: [PATCH 189/225] Bump version (1.5.18, build 90) --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index b1312427d..f79879f61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.17+89 +version: 1.5.18+90 environment: sdk: ">=2.17.0 <3.0.0" @@ -410,4 +410,4 @@ import_sorter: ignored_files: # Optional, defaults to [] - \/test\/* - \/crypto_plugins\/* - - \/integration_test\/* \ No newline at end of file + - \/integration_test\/* From b3a7b19b8e9430dd7ad6dfb851ac09b2e64129e4 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 08:36:27 -0600 Subject: [PATCH 190/225] mobile exchange form layout fixes --- lib/pages/exchange_view/exchange_form.dart | 46 ++++++++++--------- .../sub_widgets/rate_type_toggle.dart | 4 +- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 921b35bf0..d818a73ff 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -1281,28 +1281,30 @@ class _ExchangeFormState extends ConsumerState { condition: isDesktop, builder: (child) => MouseRegion( cursor: SystemMouseCursors.click, - child: RoundedContainer( - padding: const EdgeInsets.all(6), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - radiusMultiplier: 0.75, - child: child, - ), + child: child, ), - child: GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(6) + : const EdgeInsets.all(2), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + radiusMultiplier: 0.75, + child: GestureDetector( + onTap: () async { + await _swap(); + }, + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg.swap, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), ), ), ), @@ -1310,7 +1312,7 @@ class _ExchangeFormState extends ConsumerState { ], ), SizedBox( - height: isDesktop ? 10 : 4, + height: isDesktop ? 10 : 7, ), ExchangeTextField( focusNode: _receiveFocusNode, diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index 31ee01ce2..361d49e65 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -55,7 +55,7 @@ class RateTypeToggle extends ConsumerWidget { child: RoundedContainer( padding: isDesktop ? const EdgeInsets.all(17) - : const EdgeInsets.all(0), + : const EdgeInsets.all(12), color: estimated ? Theme.of(context) .extension()! @@ -136,7 +136,7 @@ class RateTypeToggle extends ConsumerWidget { child: RoundedContainer( padding: isDesktop ? const EdgeInsets.all(17) - : const EdgeInsets.all(0), + : const EdgeInsets.all(12), color: !estimated ? Theme.of(context) .extension()! From 4377c351d304d135303626f74ca7f175705ed2db Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 08:45:29 -0600 Subject: [PATCH 191/225] mobile exchange step 2 only enable next button when all fields are filled out --- .../exchange_step_views/step_2_view.dart | 95 +++++++++++++++---- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart index 800d1e146..030f91cb7 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart @@ -7,8 +7,6 @@ import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; -import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart'; -import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; @@ -20,6 +18,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; @@ -57,6 +56,8 @@ class _Step2ViewState extends ConsumerState { late final FocusNode _toFocusNode; late final FocusNode _refundFocusNode; + bool enableNext = false; + bool isStackCoin(String ticker) { try { coinFromTickerCaseInsensitive(ticker); @@ -207,6 +208,12 @@ class _Step2ViewState extends ConsumerState { _toController.text = manager.walletName; model.recipientAddress = await manager .currentReceivingAddress; + + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController.text.isNotEmpty; + }); } }); } catch (e, s) { @@ -275,7 +282,12 @@ class _Step2ViewState extends ConsumerState { model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); }, child: const XIcon(), ) @@ -295,7 +307,12 @@ class _Step2ViewState extends ConsumerState { model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } }, child: _toController.text.isEmpty @@ -338,6 +355,12 @@ class _Step2ViewState extends ConsumerState { .state) .state = ""; } + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); }); }, child: const AddressBookIcon(), @@ -361,14 +384,24 @@ class _Step2ViewState extends ConsumerState { model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } else { _toController.text = qrResult.rawContent; model.recipientAddress = _toController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } } on PlatformException catch (e, s) { Logging.instance.log( @@ -429,6 +462,11 @@ class _Step2ViewState extends ConsumerState { model.refundAddress = await manager .currentReceivingAddress; } + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }); } catch (e, s) { Logging.instance @@ -495,7 +533,12 @@ class _Step2ViewState extends ConsumerState { model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); }, child: const XIcon(), ) @@ -516,7 +559,12 @@ class _Step2ViewState extends ConsumerState { model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } }, child: @@ -555,6 +603,12 @@ class _Step2ViewState extends ConsumerState { model.refundAddress = _refundController.text; } + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); }); }, child: const AddressBookIcon(), @@ -578,14 +632,24 @@ class _Step2ViewState extends ConsumerState { model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } else { _refundController.text = qrResult.rawContent; model.refundAddress = _refundController.text; - setState(() {}); + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); } } on PlatformException catch (e, s) { Logging.instance.log( @@ -637,20 +701,15 @@ class _Step2ViewState extends ConsumerState { width: 16, ), Expanded( - child: TextButton( + child: PrimaryButton( + label: "Next", + enabled: enableNext, onPressed: () { Navigator.of(context).pushNamed( Step3View.routeName, arguments: model, ); }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Next", - style: STextStyles.button(context), - ), ), ), ], From 7011c6e1f68417d5e5fbee1ac53596bc50b618b0 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 22 Nov 2022 12:31:45 -0700 Subject: [PATCH 192/225] submit on enter for wallet keys --- .../unlock_wallet_keys_desktop.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index 739a3ebc4..e6d0ff390 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -43,6 +43,58 @@ class _UnlockWalletKeysDesktopState bool continueEnabled = false; bool hidePassword = true; + Future enterPassphrase() async { + unawaited( + showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + LoadingIndicator( + width: 200, + height: 200, + ), + ], + ), + ), + ); + + await Future.delayed(const Duration(seconds: 1)); + + final verified = await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + + if (verified) { + Navigator.of(context, rootNavigator: true).pop(); + + final words = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .mnemonic; + + if (mounted) { + await Navigator.of(context).pushReplacementNamed( + WalletKeysDesktopPopup.routeName, + arguments: words, + ); + } + } else { + Navigator.of(context, rootNavigator: true).pop(); + + await Future.delayed(const Duration(milliseconds: 300)); + + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase!", + context: context, + ), + ); + } + } + @override void initState() { passwordController = TextEditingController(); @@ -120,6 +172,12 @@ class _UnlockWalletKeysDesktopState obscureText: hidePassword, enableSuggestions: false, autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (continueEnabled) { + enterPassphrase(); + } + }, decoration: standardInputDecoration( "Enter password", passwordFocusNode, From d7a7c706d2a066d6815088ba6228cdcd8fed2603 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 23 Nov 2022 11:24:07 -0700 Subject: [PATCH 193/225] adjusted restore calendar height --- .../restore_options_view/restore_options_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 1ce5d713a..ac84964ca 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -155,7 +155,7 @@ class _RestoreOptionsViewState extends ConsumerState { final date = await showRoundedDatePicker( context: context, initialDate: DateTime.now(), - height: height * 0.5, + height: height / 3.2, theme: ThemeData( primarySwatch: Util.createMaterialColor(fetchedColor), ), From adee71224b25bf9f8e72521dad0d289d55a16df1 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 12:31:31 -0600 Subject: [PATCH 194/225] Format coin amounts improvements, fixed fee rates display issue, use hard coded xmr estimates for now --- lib/models/paymint/transactions_model.dart | 10 +- .../confirm_change_now_send.dart | 46 ++++---- .../exchange_step_views/step_4_view.dart | 2 +- lib/pages/exchange_view/send_from_view.dart | 35 ++---- .../exchange_provider_options.dart | 31 ++++-- .../send_view/confirm_transaction_view.dart | 36 +++---- lib/pages/send_view/send_view.dart | 83 +++++++++----- .../transaction_fee_selection_sheet.dart | 77 +++++++++---- .../all_transactions_view.dart | 17 +-- .../transaction_details_view.dart | 58 +++++----- .../transaction_search_filter_view.dart | 19 +--- .../sub_widgets/desktop_fee_dropdown.dart | 86 ++++++++------- .../wallet_view/sub_widgets/desktop_send.dart | 24 +++-- .../coins/bitcoin/bitcoin_wallet.dart | 52 +++++---- .../coins/bitcoincash/bitcoincash_wallet.dart | 53 ++++----- .../coins/dogecoin/dogecoin_wallet.dart | 53 ++++----- lib/services/coins/firo/firo_wallet.dart | 81 +++++++------- .../coins/litecoin/litecoin_wallet.dart | 52 +++++---- lib/services/coins/monero/monero_wallet.dart | 101 +++++++++--------- .../coins/namecoin/namecoin_wallet.dart | 52 +++++---- .../coins/wownero/wownero_wallet.dart | 50 ++++----- lib/utilities/constants.dart | 42 ++++++-- lib/utilities/format.dart | 50 +++------ lib/widgets/transaction_card.dart | 18 +--- 24 files changed, 602 insertions(+), 526 deletions(-) diff --git a/lib/models/paymint/transactions_model.dart b/lib/models/paymint/transactions_model.dart index 08b6eb7e2..382459922 100644 --- a/lib/models/paymint/transactions_model.dart +++ b/lib/models/paymint/transactions_model.dart @@ -2,6 +2,7 @@ import 'package:dart_numerics/dart_numerics.dart'; import 'package:decimal/decimal.dart'; import 'package:hive/hive.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; part '../type_adaptors/transactions_model.g.dart'; @@ -220,14 +221,16 @@ class Transaction { (DateTime.now().millisecondsSinceEpoch ~/ 1000), txType: json['txType'] as String, amount: (Decimal.parse(json["amount"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(Coin + .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), aliens: [], worthNow: json['worthNow'] as String, worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "0", fees: (Decimal.parse(json["fees"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(Coin + .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), inputSize: json['inputSize'] as int? ?? 0, @@ -386,7 +389,8 @@ class Output { scriptpubkeyType: json['scriptPubKey']['type'] as String?, scriptpubkeyAddress: address, value: (Decimal.parse(json["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(Coin + .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), ); diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 540067915..0e1eef755 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -342,6 +342,9 @@ class _ConfirmChangeNowSendViewState localeServiceChangeNotifierProvider .select((value) => value.locale), ), + ref.watch( + managerProvider.select((value) => value.coin), + ), )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", @@ -382,6 +385,9 @@ class _ConfirmChangeNowSendViewState localeServiceChangeNotifierProvider .select((value) => value.locale), ), + ref.watch( + managerProvider.select((value) => value.coin), + ), )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", @@ -563,13 +569,12 @@ class _ConfirmChangeNowSendViewState ], ), child: Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["recipientAmt"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + "${Format.satoshiAmountToPrettyString(transactionInfo["recipientAmt"] as int, ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), ref.watch( + managerProvider.select((value) => value.coin), + ))} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -597,13 +602,12 @@ class _ConfirmChangeNowSendViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["fee"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + "${Format.satoshiAmountToPrettyString(transactionInfo["fee"] as int, ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), ref.watch( + managerProvider.select((value) => value.coin), + ))} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -685,14 +689,12 @@ class _ConfirmChangeNowSendViewState ), ), Text( - "${Format.satoshiAmountToPrettyString( - (transactionInfo["fee"] as int) + - (transactionInfo["recipientAmt"] as int), - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + "${Format.satoshiAmountToPrettyString((transactionInfo["fee"] as int) + (transactionInfo["recipientAmt"] as int), ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), ref.watch( + managerProvider.select((value) => value.coin), + ))} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context).copyWith( diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index a8b403dcf..f5975a277 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -487,7 +487,7 @@ class _Step4ViewState extends ConsumerState { final amount = Format.decimalAmountToSatoshis( - model.sendAmount); + model.sendAmount, manager.coin); final address = model.trade!.payInAddress; diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 7c5f5541c..f13971a67 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -61,25 +61,7 @@ class _SendFromViewState extends ConsumerState { late final Trade trade; String formatAmount(Decimal amount, Coin coin) { - switch (coin) { - case Coin.bitcoin: - case Coin.bitcoincash: - case Coin.litecoin: - case Coin.dogecoin: - case Coin.epicCash: - case Coin.firo: - case Coin.namecoin: - case Coin.bitcoinTestNet: - case Coin.litecoinTestNet: - case Coin.bitcoincashTestnet: - case Coin.dogecoinTestNet: - case Coin.firoTestNet: - return amount.toStringAsFixed(Constants.decimalPlaces); - case Coin.monero: - return amount.toStringAsFixed(Constants.decimalPlacesMonero); - case Coin.wownero: - return amount.toStringAsFixed(Constants.decimalPlacesWownero); - } + return amount.toStringAsFixed(Constants.decimalPlacesForCoin(coin)); } @override @@ -233,7 +215,7 @@ class _SendFromCardState extends ConsumerState { late final Trade trade; Future _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async { - final _amount = Format.decimalAmountToSatoshis(amount); + final _amount = Format.decimalAmountToSatoshis(amount, manager.coin); try { bool wasCancelled = false; @@ -464,7 +446,8 @@ class _SendFromCardState extends ConsumerState { "${Format.localizedStringAsFixed( value: snapshot.data!, locale: locale, - decimalPlaces: Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin(coin), )} ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); @@ -549,7 +532,8 @@ class _SendFromCardState extends ConsumerState { "${Format.localizedStringAsFixed( value: snapshot.data!, locale: locale, - decimalPlaces: Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin(coin), )} ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); @@ -657,11 +641,8 @@ class _SendFromCardState extends ConsumerState { "${Format.localizedStringAsFixed( value: snapshot.data!, locale: locale, - decimalPlaces: coin == Coin.monero - ? Constants.decimalPlacesMonero - : coin == Coin.wownero - ? Constants.decimalPlacesWownero - : Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin(coin), )} ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 4dd768403..ebaf68066 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -159,6 +159,14 @@ class ExchangeProviderOptions extends ConsumerWidget { .toDecimal( scaleOnInfinitePrecision: 12); } + Coin coin; + try { + coin = + coinFromTickerCaseInsensitive(to!); + } catch (_) { + coin = Coin.bitcoin; + } + return Text( "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( value: rate, @@ -167,11 +175,9 @@ class ExchangeProviderOptions extends ConsumerWidget { .select( (value) => value.locale), ), - decimalPlaces: to!.toUpperCase() == - Coin.monero.ticker - .toUpperCase() - ? Constants.decimalPlacesMonero - : Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin( + coin), )} ${to!.toUpperCase()}", style: STextStyles.itemSubtitle12(context) @@ -354,6 +360,13 @@ class ExchangeProviderOptions extends ConsumerWidget { .toDecimal( scaleOnInfinitePrecision: 12); + Coin coin; + try { + coin = + coinFromTickerCaseInsensitive(to!); + } catch (_) { + coin = Coin.bitcoin; + } return Text( "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( value: rate, @@ -362,11 +375,9 @@ class ExchangeProviderOptions extends ConsumerWidget { .select( (value) => value.locale), ), - decimalPlaces: to!.toUpperCase() == - Coin.monero.ticker - .toUpperCase() - ? Constants.decimalPlacesMonero - : Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin( + coin), )} ${to!.toUpperCase()}", style: STextStyles.itemSubtitle12(context) diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 276203804..1ddeb3c9f 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -317,13 +317,12 @@ class _ConfirmTransactionViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["recipientAmt"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + "${Format.satoshiAmountToPrettyString(transactionInfo["recipientAmt"] as int, ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), ref.watch( + managerProvider.select((value) => value.coin), + ))} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -344,13 +343,12 @@ class _ConfirmTransactionViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString( - transactionInfo["fee"] as int, - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + "${Format.satoshiAmountToPrettyString(transactionInfo["fee"] as int, ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), ref.watch( + managerProvider.select((value) => value.coin), + ))} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -494,6 +492,7 @@ class _ConfirmTransactionViewState localeServiceChangeNotifierProvider .select((value) => value.locale), ), + coin, )} ${coin.ticker}", style: STextStyles .desktopTextExtraExtraSmall( @@ -638,11 +637,7 @@ class _ConfirmTransactionViewState value: fee, locale: ref.watch(localeServiceChangeNotifierProvider .select((value) => value.locale)), - decimalPlaces: coin == Coin.monero - ? Constants.decimalPlacesMonero - : coin == Coin.wownero - ? Constants.decimalPlacesWownero - : Constants.decimalPlaces, + decimalPlaces: Constants.decimalPlacesForCoin(coin), )} ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); @@ -750,6 +745,9 @@ class _ConfirmTransactionViewState localeServiceChangeNotifierProvider .select((value) => value.locale), ), + ref.watch( + managerProvider.select((value) => value.coin), + ), )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index d91b7a3ea..2539b89ab 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cw_core/monero_transaction_priority.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -30,6 +31,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -41,8 +43,6 @@ import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class SendView extends ConsumerStatefulWidget { const SendView({ Key? key, @@ -211,29 +211,47 @@ class _SendViewState extends ConsumerState { } int fee; + if (coin == Coin.monero) { + MoneroTransactionPriority specialMoneroId; + switch (ref.read(feeRateTypeStateProvider.state).state) { + case FeeRateType.fast: + specialMoneroId = MoneroTransactionPriority.fast; + break; + case FeeRateType.average: + specialMoneroId = MoneroTransactionPriority.regular; + break; + case FeeRateType.slow: + specialMoneroId = MoneroTransactionPriority.slow; + break; + } - if (coin == Coin.firo || coin == Coin.firoTestNet) { + fee = await manager.estimateFeeFor(amount, specialMoneroId.raw!); + cachedFees[amount] = Format.satoshisToAmount(fee, coin: coin) + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + + return cachedFees[amount]!; + } else if (coin == Coin.firo || coin == Coin.firoTestNet) { if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { fee = await manager.estimateFeeFor(amount, feeRate); - cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee) - .toStringAsFixed(Constants.decimalPlaces); + cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee, coin: coin) + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); return cachedFiroPrivateFees[amount]!; } else { fee = await (manager.wallet as FiroWallet) .estimateFeeForPublic(amount, feeRate); - cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee) - .toStringAsFixed(Constants.decimalPlaces); + cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee, coin: coin) + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); return cachedFiroPublicFees[amount]!; } } else { fee = await manager.estimateFeeFor(amount, feeRate); - cachedFees[amount] = - Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces); + cachedFees[amount] = Format.satoshisToAmount(fee, coin: coin) + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); return cachedFees[amount]!; } @@ -296,8 +314,8 @@ class _SendViewState extends ConsumerState { }); } else { setState(() { - _calculateFeesFuture = - calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); + _calculateFeesFuture = calculateFees( + Format.decimalAmountToSatoshis(_amountToSend!, coin)); }); } } @@ -311,8 +329,8 @@ class _SendViewState extends ConsumerState { }); } else { setState(() { - _calculateFeesFuture = - calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); + _calculateFeesFuture = calculateFees( + Format.decimalAmountToSatoshis(_amountToSend!, coin)); }); } } @@ -354,8 +372,8 @@ class _SendViewState extends ConsumerState { }); } else { setState(() { - _calculateFeesFuture = - calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); + _calculateFeesFuture = calculateFees( + Format.decimalAmountToSatoshis(_amountToSend!, coin)); }); } }); @@ -492,7 +510,9 @@ class _SendViewState extends ConsumerState { onTap: () { cryptoAmountController.text = _cachedBalance!.toStringAsFixed( - Constants.decimalPlaces); + Constants + .decimalPlacesForCoin( + coin)); }, child: Container( color: Colors.transparent, @@ -781,8 +801,9 @@ class _SendViewState extends ConsumerState { .read( localeServiceChangeNotifierProvider) .locale, - decimalPlaces: - Constants.decimalPlaces, + decimalPlaces: Constants + .decimalPlacesForCoin( + coin), ); amount.toString(); _amountToSend = amount; @@ -1044,19 +1065,22 @@ class _SendViewState extends ConsumerState { (await firoWallet .availablePrivateBalance()) .toStringAsFixed( - Constants.decimalPlaces); + Constants.decimalPlacesForCoin( + coin)); } else { cryptoAmountController.text = (await firoWallet .availablePublicBalance()) .toStringAsFixed( - Constants.decimalPlaces); + Constants.decimalPlacesForCoin( + coin)); } } else { cryptoAmountController.text = (await ref .read(provider) .availableBalance) - .toStringAsFixed(Constants.decimalPlaces); + .toStringAsFixed( + Constants.decimalPlacesForCoin(coin)); } }, ), @@ -1167,7 +1191,8 @@ class _SendViewState extends ConsumerState { ? Decimal.zero : (baseAmount / _price).toDecimal( scaleOnInfinitePrecision: - Constants.decimalPlaces); + Constants.decimalPlacesForCoin( + coin)); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { @@ -1184,7 +1209,8 @@ class _SendViewState extends ConsumerState { locale: ref .read(localeServiceChangeNotifierProvider) .locale, - decimalPlaces: Constants.decimalPlaces, + decimalPlaces: + Constants.decimalPlacesForCoin(coin), ); _cryptoAmountChangeLock = true; @@ -1506,7 +1532,7 @@ class _SendViewState extends ConsumerState { } final amount = Format.decimalAmountToSatoshis( - _amountToSend!); + _amountToSend!, coin); int availableBalance; if ((coin == Coin.firo || coin == Coin.firoTestNet)) { @@ -1520,18 +1546,21 @@ class _SendViewState extends ConsumerState { Format.decimalAmountToSatoshis( await (manager.wallet as FiroWallet) - .availablePrivateBalance()); + .availablePrivateBalance(), + coin); } else { availableBalance = Format.decimalAmountToSatoshis( await (manager.wallet as FiroWallet) - .availablePublicBalance()); + .availablePublicBalance(), + coin); } } else { availableBalance = Format.decimalAmountToSatoshis( - await manager.availableBalance); + await manager.availableBalance, + coin); } // confirm send all diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 982086d3c..2f5ce2f3e 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/monero_transaction_priority.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -70,16 +71,27 @@ class _TransactionFeeSelectionSheetState final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.fast.raw!); + ref.read(feeSheetSessionCacheProvider).fast[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).fast[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).fast[amount]!; @@ -88,17 +100,27 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.regular.raw!); + ref.read(feeSheetSessionCacheProvider).average[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).average[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).average[amount]!; @@ -107,17 +129,27 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.slow.raw!); + ref.read(feeSheetSessionCacheProvider).slow[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).slow[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).slow[amount]!; @@ -225,7 +257,7 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.fast; } - String? fee = getAmount(FeeRateType.fast); + String? fee = getAmount(FeeRateType.fast, manager.coin); if (fee != null) { widget.updateChosen(fee); } @@ -293,7 +325,7 @@ class _TransactionFeeSelectionSheetState feeRate: feeObject!.fast, amount: Format .decimalAmountToSatoshis( - amount)), + amount, manager.coin)), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), @@ -358,7 +390,8 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.average; } - String? fee = getAmount(FeeRateType.average); + String? fee = + getAmount(FeeRateType.average, manager.coin); if (fee != null) { widget.updateChosen(fee); } @@ -424,7 +457,7 @@ class _TransactionFeeSelectionSheetState feeRate: feeObject!.medium, amount: Format .decimalAmountToSatoshis( - amount)), + amount, manager.coin)), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), @@ -489,7 +522,7 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.slow; } - String? fee = getAmount(FeeRateType.slow); + String? fee = getAmount(FeeRateType.slow, manager.coin); print("fee $fee"); if (fee != null) { widget.updateChosen(fee); @@ -557,7 +590,7 @@ class _TransactionFeeSelectionSheetState feeRate: feeObject!.slow, amount: Format .decimalAmountToSatoshis( - amount)), + amount, manager.coin)), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), @@ -624,10 +657,10 @@ class _TransactionFeeSelectionSheetState ); } - String? getAmount(FeeRateType feeRateType) { + String? getAmount(FeeRateType feeRateType, Coin coin) { try { print(feeRateType); - var amount = Format.decimalAmountToSatoshis(this.amount); + var amount = Format.decimalAmountToSatoshis(this.amount, coin); print(amount); print(ref.read(feeSheetSessionCacheProvider).fast); print(ref.read(feeSheetSessionCacheProvider).average); diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 95dcc8126..b9ea02e79 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -937,13 +937,9 @@ class _DesktopTransactionCardRowState flex: 6, child: Builder( builder: (_) { - final amount = coin == Coin.monero - ? (_transaction.amount ~/ 10000) - : coin == Coin.wownero - ? (_transaction.amount ~/ 1000) - : _transaction.amount; + final amount = _transaction.amount; return Text( - "$prefix${Format.satoshiAmountToPrettyString(amount, locale)} ${coin.ticker}", + "$prefix${Format.satoshiAmountToPrettyString(amount, locale, coin)} ${coin.ticker}", style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) @@ -960,17 +956,12 @@ class _DesktopTransactionCardRowState flex: 4, child: Builder( builder: (_) { - // TODO: modify Format. to take optional Coin parameter so this type oif check isn't done in ui int value = _transaction.amount; - if (coin == Coin.monero) { - value = (value ~/ 10000); - } else if (coin == Coin.wownero) { - value = (value ~/ 1000); - } return Text( "$prefix${Format.localizedStringAsFixed( - value: Format.satoshisToAmount(value) * price, + value: Format.satoshisToAmount(value, coin: coin) * + price, locale: locale, decimalPlaces: 2, )} $baseCurrency", diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 4d45428ff..c2a0590e4 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -78,8 +78,8 @@ class _TransactionDetailsViewState walletId = widget.walletId; coin = widget.coin; - amount = Format.satoshisToAmount(_transaction.amount); - fee = Format.satoshisToAmount(_transaction.fees); + amount = Format.satoshisToAmount(_transaction.amount, coin: coin); + fee = Format.satoshisToAmount(_transaction.fees, coin: coin); if ((coin == Coin.firo || coin == Coin.firoTestNet) && _transaction.subType == "mint") { @@ -418,21 +418,15 @@ class _TransactionDetailsViewState children: [ SelectableText( "$amountPrefix${Format.localizedStringAsFixed( - value: coin == Coin.monero - ? (amount / 10000.toDecimal()) - .toDecimal() - : coin == Coin.wownero - ? (amount / - 1000.toDecimal()) - .toDecimal() - : amount, + value: amount, locale: ref.watch( localeServiceChangeNotifierProvider .select( (value) => value.locale), ), decimalPlaces: - Constants.decimalPlaces, + Constants.decimalPlacesForCoin( + coin), )} ${coin.ticker}", style: isDesktop ? STextStyles @@ -454,11 +448,21 @@ class _TransactionDetailsViewState (value) => value.externalCalls))) SelectableText( - "$amountPrefix${Format.localizedStringAsFixed(value: (coin == Coin.monero ? (amount / 10000.toDecimal()).toDecimal() : coin == Coin.wownero ? (amount / 1000.toDecimal()).toDecimal() : amount) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1)), locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => - value.locale), - ), decimalPlaces: 2)} ${ref.watch( + "$amountPrefix${Format.localizedStringAsFixed( + value: amount * + ref.watch( + priceAnd24hChangeNotifierProvider + .select((value) => value + .getPrice(coin) + .item1), + ), + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => + value.locale), + ), + decimalPlaces: 2, + )} ${ref.watch( prefsChangeNotifierProvider .select( (value) => value.currency, @@ -834,32 +838,22 @@ class _TransactionDetailsViewState final feeString = showFeePending ? _transaction.confirmedStatus ? Format.localizedStringAsFixed( - value: coin == Coin.monero - ? (fee / 10000.toDecimal()) - .toDecimal() - : coin == Coin.wownero - ? (fee / 1000.toDecimal()) - .toDecimal() - : fee, + value: fee, locale: ref.watch( localeServiceChangeNotifierProvider .select( (value) => value.locale)), decimalPlaces: - Constants.decimalPlaces) + Constants.decimalPlacesForCoin( + coin)) : "Pending" : Format.localizedStringAsFixed( - value: coin == Coin.monero - ? (fee / 10000.toDecimal()) - .toDecimal() - : coin == Coin.wownero - ? (fee / 1000.toDecimal()) - .toDecimal() - : fee, + value: fee, locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale)), - decimalPlaces: Constants.decimalPlaces); + decimalPlaces: + Constants.decimalPlacesForCoin(coin)); return Row( mainAxisAlignment: diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index d135ea276..0b3fd3acb 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -79,7 +79,7 @@ class _TransactionSearchViewState String amount = ""; if (filterState.amount != null) { amount = Format.satoshiAmountToPrettyString(filterState.amount!, - ref.read(localeServiceChangeNotifierProvider).locale); + ref.read(localeServiceChangeNotifierProvider).locale, widget.coin); } _amountTextEditingController.text = amount; } @@ -967,22 +967,7 @@ class _TransactionSearchViewState } int? amount; if (amountDecimal != null) { - if (widget.coin == Coin.monero) { - amount = (amountDecimal * Decimal.fromInt(Constants.satsPerCoinMonero)) - .floor() - .toBigInt() - .toInt(); - } else if (widget.coin == Coin.wownero) { - amount = (amountDecimal * Decimal.fromInt(Constants.satsPerCoinWownero)) - .floor() - .toBigInt() - .toInt(); - } else { - amount = (amountDecimal * Decimal.fromInt(Constants.satsPerCoin)) - .floor() - .toBigInt() - .toInt(); - } + amount = Format.decimalAmountToSatoshis(amountDecimal, widget.coin); } final TransactionFilter filter = TransactionFilter( diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart index 9acb3a6f9..25e1f47ab 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/monero_transaction_priority.dart'; import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; @@ -55,16 +56,27 @@ class _DesktopFeeDropDownState extends ConsumerState { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.fast.raw!); + ref.read(feeSheetSessionCacheProvider).fast[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).fast[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).fast[amount]!; @@ -74,16 +86,27 @@ class _DesktopFeeDropDownState extends ConsumerState { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.regular.raw!); + ref.read(feeSheetSessionCacheProvider).average[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).average[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).average[amount]!; @@ -93,47 +116,33 @@ class _DesktopFeeDropDownState extends ConsumerState { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - if ((coin == Coin.firo || coin == Coin.firoTestNet) && + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.slow.raw!); + ref.read(feeSheetSessionCacheProvider).slow[amount] = + Format.satoshisToAmount( + fee, + coin: coin, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && ref.read(publicPrivateBalanceStateProvider.state).state != "Private") { ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount(await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate)); + Format.satoshisToAmount( + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate), + coin: coin); } else { ref.read(feeSheetSessionCacheProvider).slow[amount] = Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate)); + await manager.estimateFeeFor(amount, feeRate), + coin: coin); } } return ref.read(feeSheetSessionCacheProvider).slow[amount]!; } } - String estimatedTimeToBeIncludedInNextBlock( - int targetBlockTime, int estimatedNumberOfBlocks) { - int time = targetBlockTime * estimatedNumberOfBlocks; - - int hours = (time / 3600).floor(); - if (hours > 1) { - return "~$hours hours"; - } else if (hours == 1) { - return "~$hours hour"; - } - - // less than an hour - - final string = (time / 60).toStringAsFixed(1); - - if (string == "1.0") { - return "~1 minute"; - } else { - if (string.endsWith(".0")) { - return "~${(time / 60).floor()} minutes"; - } - return "~$string minutes"; - } - } - @override void initState() { walletId = widget.walletId; @@ -307,7 +316,7 @@ class FeeDropDownChild extends ConsumerWidget { return FutureBuilder( future: feeFor( coin: manager.coin, - feeRateType: FeeRateType.fast, + feeRateType: feeRateType, feeRate: feeRateType == FeeRateType.fast ? feeObject!.fast : feeRateType == FeeRateType.slow @@ -315,6 +324,7 @@ class FeeDropDownChild extends ConsumerWidget { : feeObject!.medium, amount: Format.decimalAmountToSatoshis( ref.watch(sendAmountProvider.state).state, + manager.coin, ), ), builder: (_, AsyncSnapshot snapshot) { diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 336dd7b4e..72690c7e5 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -161,20 +161,22 @@ class _DesktopSendState extends ConsumerState { return; } - final amount = Format.decimalAmountToSatoshis(_amountToSend!); + final amount = Format.decimalAmountToSatoshis(_amountToSend!, coin); int availableBalance; if ((coin == Coin.firo || coin == Coin.firoTestNet)) { if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { availableBalance = Format.decimalAmountToSatoshis( - await (manager.wallet as FiroWallet).availablePrivateBalance()); + await (manager.wallet as FiroWallet).availablePrivateBalance(), + coin); } else { availableBalance = Format.decimalAmountToSatoshis( - await (manager.wallet as FiroWallet).availablePublicBalance()); + await (manager.wallet as FiroWallet).availablePublicBalance(), + coin); } } else { availableBalance = - Format.decimalAmountToSatoshis(await manager.availableBalance); + Format.decimalAmountToSatoshis(await manager.availableBalance, coin); } // confirm send all @@ -642,7 +644,7 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.text = Format.localizedStringAsFixed( value: amount, locale: ref.read(localeServiceChangeNotifierProvider).locale, - decimalPlaces: Constants.decimalPlaces, + decimalPlaces: Constants.decimalPlacesForCoin(coin), ); amount.toString(); _amountToSend = amount; @@ -709,8 +711,8 @@ class _DesktopSendState extends ConsumerState { } else { _amountToSend = baseAmount <= Decimal.zero ? Decimal.zero - : (baseAmount / _price) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces); + : (baseAmount / _price).toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -722,7 +724,7 @@ class _DesktopSendState extends ConsumerState { final amountString = Format.localizedStringAsFixed( value: _amountToSend!, locale: ref.read(localeServiceChangeNotifierProvider).locale, - decimalPlaces: Constants.decimalPlaces, + decimalPlaces: Constants.decimalPlacesForCoin(coin), ); _cryptoAmountChangeLock = true; @@ -752,18 +754,18 @@ class _DesktopSendState extends ConsumerState { "Private") { cryptoAmountController.text = (await firoWallet.availablePrivateBalance()) - .toStringAsFixed(Constants.decimalPlaces); + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); } else { cryptoAmountController.text = (await firoWallet.availablePublicBalance()) - .toStringAsFixed(Constants.decimalPlaces); + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); } } else { cryptoAmountController.text = (await ref .read(walletsChangeNotifierProvider) .getManager(walletId) .availableBalance) - .toStringAsFixed(Constants.decimalPlaces); + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); } } diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index dfd5ea180..25ec6b519 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -200,19 +200,21 @@ class BitcoinWallet extends CoinServiceAPI { Future get availableBalance async { final data = await utxoData; return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed); + data.satoshiBalance - data.satoshiBalanceUnconfirmed, + coin: coin); } @override Future get pendingBalance async { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); } @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -222,13 +224,13 @@ class BitcoinWallet extends CoinServiceAPI { .get(boxName: walletId, key: 'totalBalance') as int?; if (totalBalance == null) { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } else { - return Format.satoshisToAmount(totalBalance); + return Format.satoshisToAmount(totalBalance, coin: coin); } } final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } @override @@ -266,7 +268,8 @@ class BitcoinWallet extends CoinServiceAPI { @override Future get maxFee async { final fee = (await fees).fast as String; - final satsFee = Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin); + final satsFee = + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); return satsFee.floor().toBigInt().toInt(); } @@ -1093,7 +1096,8 @@ class BitcoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis(await availableBalance); + final balance = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1297,7 +1301,7 @@ class BitcoinWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1497,9 +1501,9 @@ class BitcoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1968,7 +1972,7 @@ class BitcoinWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -1978,15 +1982,16 @@ class BitcoinWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -2532,7 +2537,7 @@ class BitcoinWallet extends CoinServiceAPI { if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2545,7 +2550,7 @@ class BitcoinWallet extends CoinServiceAPI { final String address = output["scriptPubKey"]!["address"] as String; final value = output["value"]!; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOutput += _value; @@ -2570,7 +2575,7 @@ class BitcoinWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["address"]; if (address != null) { final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOut += value; @@ -2593,7 +2598,7 @@ class BitcoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2615,7 +2620,7 @@ class BitcoinWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -2625,7 +2630,7 @@ class BitcoinWallet extends CoinServiceAPI { midSortedTx["amount"] = outputAmtAddressedToWallet; final worthNow = ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -3753,7 +3758,8 @@ class BitcoinWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = Format.decimalAmountToSatoshis(await availableBalance); + final available = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 429af898e..59b2454b4 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -170,19 +170,21 @@ class BitcoinCashWallet extends CoinServiceAPI { Future get availableBalance async { final data = await utxoData; return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed); + data.satoshiBalance - data.satoshiBalanceUnconfirmed, + coin: coin); } @override Future get pendingBalance async { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); } @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -192,13 +194,13 @@ class BitcoinCashWallet extends CoinServiceAPI { .get(boxName: walletId, key: 'totalBalance') as int?; if (totalBalance == null) { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } else { - return Format.satoshisToAmount(totalBalance); + return Format.satoshisToAmount(totalBalance, coin: coin); } } final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } @override @@ -232,8 +234,8 @@ class BitcoinCashWallet extends CoinServiceAPI { @override Future get maxFee async { final fee = (await fees).fast; - final satsFee = - Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin); + final satsFee = Format.satoshisToAmount(fee, coin: coin) * + Decimal.fromInt(Constants.satsPerCoin(coin)); return satsFee.floor().toBigInt().toInt(); } @@ -988,7 +990,8 @@ class BitcoinCashWallet extends CoinServiceAPI { } // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis(await availableBalance); + final balance = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1175,7 +1178,7 @@ class BitcoinCashWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1384,9 +1387,9 @@ class BitcoinCashWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1791,7 +1794,7 @@ class BitcoinCashWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -1801,15 +1804,16 @@ class BitcoinCashWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -2332,7 +2336,7 @@ class BitcoinCashWallet extends CoinServiceAPI { if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2345,7 +2349,7 @@ class BitcoinCashWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["addresses"][0]; final value = output["value"]; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOutput += _value; @@ -2370,7 +2374,7 @@ class BitcoinCashWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["addresses"][0]; if (address != null) { final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOut += value; @@ -2394,7 +2398,7 @@ class BitcoinCashWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2416,7 +2420,7 @@ class BitcoinCashWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -2426,7 +2430,7 @@ class BitcoinCashWallet extends CoinServiceAPI { midSortedTx["amount"] = outputAmtAddressedToWallet; final worthNow = ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -3457,7 +3461,8 @@ class BitcoinCashWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = Format.decimalAmountToSatoshis(await availableBalance); + final available = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 41778a9e0..f7372752b 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -167,19 +167,21 @@ class DogecoinWallet extends CoinServiceAPI { Future get availableBalance async { final data = await utxoData; return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed); + data.satoshiBalance - data.satoshiBalanceUnconfirmed, + coin: coin); } @override Future get pendingBalance async { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); } @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -189,13 +191,13 @@ class DogecoinWallet extends CoinServiceAPI { .get(boxName: walletId, key: 'totalBalance') as int?; if (totalBalance == null) { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } else { - return Format.satoshisToAmount(totalBalance); + return Format.satoshisToAmount(totalBalance, coin: coin); } } final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } @override @@ -225,8 +227,8 @@ class DogecoinWallet extends CoinServiceAPI { @override Future get maxFee async { final fee = (await fees).fast; - final satsFee = - Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin); + final satsFee = Format.satoshisToAmount(fee, coin: coin) * + Decimal.fromInt(Constants.satsPerCoin(coin)); return satsFee.floor().toBigInt().toInt(); } @@ -878,7 +880,8 @@ class DogecoinWallet extends CoinServiceAPI { } // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis(await availableBalance); + final balance = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1065,7 +1068,7 @@ class DogecoinWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1247,9 +1250,9 @@ class DogecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1650,7 +1653,7 @@ class DogecoinWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -1660,15 +1663,16 @@ class DogecoinWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -2144,7 +2148,7 @@ class DogecoinWallet extends CoinServiceAPI { if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2157,7 +2161,7 @@ class DogecoinWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["addresses"][0]; final value = output["value"]; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOutput += _value; @@ -2182,7 +2186,7 @@ class DogecoinWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["addresses"][0]; if (address != null) { final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOut += value; @@ -2205,7 +2209,7 @@ class DogecoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2227,7 +2231,7 @@ class DogecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -2237,7 +2241,7 @@ class DogecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = outputAmtAddressedToWallet; final worthNow = ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -3050,7 +3054,8 @@ class DogecoinWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = Format.decimalAmountToSatoshis(await availableBalance); + final available = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 4bd863f2c..3e0cba75d 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -620,7 +620,7 @@ Future isolateCreateJoinSplitTransaction( "txid": txId, "txHex": txHex, "value": amount, - "fees": Format.satoshisToAmount(fee).toDouble(), + "fees": Format.satoshisToAmount(fee, coin: coin).toDouble(), "fee": fee, "vSize": extTx.virtualSize(), "jmintValue": changeToMint, @@ -629,11 +629,11 @@ Future isolateCreateJoinSplitTransaction( "height": locktime, "txType": "Sent", "confirmed_status": false, - "amount": Format.satoshisToAmount(amount).toDouble(), + "amount": Format.satoshisToAmount(amount, coin: coin).toDouble(), "recipientAmt": amount, "worthNow": Format.localizedStringAsFixed( value: ((Decimal.fromInt(amount) * price) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale), @@ -883,7 +883,7 @@ class FiroWallet extends CoinServiceAPI { Future get balanceMinusMaxFee async { final balances = await this.balances; final maxFee = await this.maxFee; - return balances[0] - Format.satoshisToAmount(maxFee); + return balances[0] - Format.satoshisToAmount(maxFee, coin: coin); } @override @@ -919,7 +919,7 @@ class FiroWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1089,8 +1089,8 @@ class FiroWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availablePublicBalance()); + final balance = Format.decimalAmountToSatoshis( + await availablePublicBalance(), coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1176,7 +1176,7 @@ class FiroWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; final balance = - Format.decimalAmountToSatoshis(await availablePrivateBalance()); + Format.decimalAmountToSatoshis(await availablePrivateBalance(), coin); if (satoshiAmount == balance) { // print("is send all"); isSendAll = true; @@ -1222,7 +1222,8 @@ class FiroWallet extends CoinServiceAPI { // temporarily update apdate available balance until a full refresh is done // TODO: something here causes an exception to be thrown giving user false info that the tx failed - Decimal sendTotal = Format.satoshisToAmount(txData["value"] as int); + Decimal sendTotal = + Format.satoshisToAmount(txData["value"] as int, coin: coin); sendTotal += Decimal.parse(txData["fees"].toString()); final bals = await balances; bals[0] -= sendTotal; @@ -1270,7 +1271,7 @@ class FiroWallet extends CoinServiceAPI { // temporarily update apdate available balance until a full refresh is done Decimal sendTotal = - Format.satoshisToAmount(txHexOrError["value"] as int); + Format.satoshisToAmount(txHexOrError["value"] as int, coin: coin); sendTotal += Decimal.parse(txHexOrError["fees"].toString()); final bals = await balances; bals[0] -= sendTotal; @@ -2333,8 +2334,9 @@ class FiroWallet extends CoinServiceAPI { Future _fetchMaxFee() async { final balance = await availableBalance; - int spendAmount = - (balance * Decimal.fromInt(Constants.satsPerCoin)).toBigInt().toInt(); + int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); int fee = await estimateJoinSplitFee(spendAmount); return fee; } @@ -2480,18 +2482,20 @@ class FiroWallet extends CoinServiceAPI { } final int utxosIntValue = utxos.satoshiBalance; - final Decimal utxosValue = Format.satoshisToAmount(utxosIntValue); + final Decimal utxosValue = + Format.satoshisToAmount(utxosIntValue, coin: coin); List balances = List.empty(growable: true); - Decimal lelantusBalance = Format.satoshisToAmount(intLelantusBalance); + Decimal lelantusBalance = + Format.satoshisToAmount(intLelantusBalance, coin: coin); balances.add(lelantusBalance); balances.add(lelantusBalance * price); Decimal _unconfirmedLelantusBalance = - Format.satoshisToAmount(unconfirmedLelantusBalance); + Format.satoshisToAmount(unconfirmedLelantusBalance, coin: coin); balances.add(lelantusBalance + utxosValue + _unconfirmedLelantusBalance); @@ -2503,7 +2507,7 @@ class FiroWallet extends CoinServiceAPI { if (availableSats < 0) { availableSats = 0; } - balances.add(Format.satoshisToAmount(availableSats)); + balances.add(Format.satoshisToAmount(availableSats, coin: coin)); Logging.instance.log("balances $balances", level: LogLevel.Info); await DB.instance.put( @@ -2601,7 +2605,8 @@ class FiroWallet extends CoinServiceAPI { final feesObject = await fees; - final Decimal fastFee = Format.satoshisToAmount(feesObject.fast); + final Decimal fastFee = + Format.satoshisToAmount(feesObject.fast, coin: coin); int firoFee = (dvsize * fastFee * Decimal.fromInt(100000)).toDouble().ceil(); // int firoFee = (vsize * feesObject.fast * (1 / 1000.0) * 100000000).ceil(); @@ -2789,15 +2794,15 @@ class FiroWallet extends CoinServiceAPI { "txid": txId, "txHex": txHex, "value": amount - fee, - "fees": Format.satoshisToAmount(fee).toDouble(), + "fees": Format.satoshisToAmount(fee, coin: coin).toDouble(), "publicCoin": "", "height": height, "txType": "Sent", "confirmed_status": false, - "amount": Format.satoshisToAmount(amount).toDouble(), + "amount": Format.satoshisToAmount(amount, coin: coin).toDouble(), "worthNow": Format.localizedStringAsFixed( value: ((Decimal.fromInt(amount) * price) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!), @@ -3040,9 +3045,9 @@ class FiroWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -3328,7 +3333,7 @@ class FiroWallet extends CoinServiceAPI { if (nFees != null) { nFeesUsed = true; fees = (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -3353,7 +3358,7 @@ class FiroWallet extends CoinServiceAPI { if (value != null) { if (changeAddresses.contains(address)) { inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } else { @@ -3363,7 +3368,7 @@ class FiroWallet extends CoinServiceAPI { } if (value != null) { outAmount += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -3376,7 +3381,7 @@ class FiroWallet extends CoinServiceAPI { final nFees = input["nFees"]; if (nFees != null) { fees += (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -3391,7 +3396,7 @@ class FiroWallet extends CoinServiceAPI { if (allAddresses.contains(address)) { outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); outAddress = address; @@ -3413,7 +3418,7 @@ class FiroWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -3428,7 +3433,7 @@ class FiroWallet extends CoinServiceAPI { final worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -3589,7 +3594,7 @@ class FiroWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -3600,15 +3605,16 @@ class FiroWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -4571,8 +4577,9 @@ class FiroWallet extends CoinServiceAPI { ) async { var lelantusEntry = await _getLelantusEntry(); final balance = await availableBalance; - int spendAmount = - (balance * Decimal.fromInt(Constants.satsPerCoin)).toBigInt().toInt(); + int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); if (spendAmount == 0 || lelantusEntry.isEmpty) { return LelantusFeeData(0, 0, []).fee; } @@ -4633,7 +4640,7 @@ class FiroWallet extends CoinServiceAPI { Future estimateFeeForPublic(int satoshiAmount, int feeRate) async { final available = - Format.decimalAmountToSatoshis(await availablePublicBalance()); + Format.decimalAmountToSatoshis(await availablePublicBalance(), coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index db7c9d1fa..9c4bb2305 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -200,19 +200,21 @@ class LitecoinWallet extends CoinServiceAPI { Future get availableBalance async { final data = await utxoData; return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed); + data.satoshiBalance - data.satoshiBalanceUnconfirmed, + coin: coin); } @override Future get pendingBalance async { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); } @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -222,13 +224,13 @@ class LitecoinWallet extends CoinServiceAPI { .get(boxName: walletId, key: 'totalBalance') as int?; if (totalBalance == null) { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } else { - return Format.satoshisToAmount(totalBalance); + return Format.satoshisToAmount(totalBalance, coin: coin); } } final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } @override @@ -266,7 +268,8 @@ class LitecoinWallet extends CoinServiceAPI { @override Future get maxFee async { final fee = (await fees).fast as String; - final satsFee = Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin); + final satsFee = + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); return satsFee.floor().toBigInt().toInt(); } @@ -1095,7 +1098,8 @@ class LitecoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis(await availableBalance); + final balance = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1299,7 +1303,7 @@ class LitecoinWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1499,9 +1503,9 @@ class LitecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1978,7 +1982,7 @@ class LitecoinWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -1988,15 +1992,16 @@ class LitecoinWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -2543,7 +2548,7 @@ class LitecoinWallet extends CoinServiceAPI { if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2557,7 +2562,7 @@ class LitecoinWallet extends CoinServiceAPI { output["scriptPubKey"]!["addresses"][0] as String; final value = output["value"]!; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOutput += _value; @@ -2582,7 +2587,7 @@ class LitecoinWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["addresses"][0]; if (address != null) { final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOut += value; @@ -2605,7 +2610,7 @@ class LitecoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2627,7 +2632,7 @@ class LitecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -2637,7 +2642,7 @@ class LitecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = outputAmtAddressedToWallet; final worthNow = ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -3769,7 +3774,8 @@ class LitecoinWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = Format.decimalAmountToSatoshis(await availableBalance); + final available = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index f94f0cd2a..58bd36c72 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -44,6 +44,7 @@ import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; @@ -533,7 +534,8 @@ class MoneroWallet extends CoinServiceAPI { @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -542,16 +544,16 @@ class MoneroWallet extends CoinServiceAPI { @override Future exit() async { - await stopSyncPercentTimer(); _hasCalledExit = true; - isActive = false; - await walletBase?.save(prioritySave: true); - walletBase?.close(); + stopNetworkAlivePinging(); moneroAutosaveTimer?.cancel(); moneroAutosaveTimer = null; timer?.cancel(); timer = null; - stopNetworkAlivePinging(); + await stopSyncPercentTimer(); + await walletBase?.save(prioritySave: true); + walletBase?.close(); + isActive = false; } bool _hasCalledExit = false; @@ -562,13 +564,15 @@ class MoneroWallet extends CoinServiceAPI { Future? _currentReceivingAddress; Future _getFees() async { + // TODO: not use hard coded values here return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 10, - numberOfBlocksSlow: 10, - fast: 4, - medium: 2, - slow: 0); + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: MoneroTransactionPriority.fast.raw!, + medium: MoneroTransactionPriority.regular.raw!, + slow: MoneroTransactionPriority.slow.raw!, + ); } @override @@ -868,8 +872,9 @@ class MoneroWallet extends CoinServiceAPI { Future get maxFee async { var bal = await availableBalance; var fee = walletBase!.calculateEstimatedFee( - monero.getDefaultTransactionPriority(), bal.toBigInt().toInt()) ~/ - 10000; + monero.getDefaultTransactionPriority(), + Format.decimalAmountToSatoshis(bal, coin), + ); return fee; } @@ -1372,7 +1377,6 @@ class MoneroWallet extends CoinServiceAPI { } @override - // TODO: implement availableBalance Future get availableBalance async { var bal = 0; for (var element in walletBase!.balance!.entries) { @@ -1421,13 +1425,13 @@ class MoneroWallet extends CoinServiceAPI { try { final feeRate = args?["feeRate"]; if (feeRate is FeeRateType) { - MoneroTransactionPriority feePriority = MoneroTransactionPriority.slow; + MoneroTransactionPriority feePriority; switch (feeRate) { case FeeRateType.fast: - feePriority = MoneroTransactionPriority.fastest; + feePriority = MoneroTransactionPriority.fast; break; case FeeRateType.average: - feePriority = MoneroTransactionPriority.medium; + feePriority = MoneroTransactionPriority.regular; break; case FeeRateType.slow: feePriority = MoneroTransactionPriority.slow; @@ -1440,15 +1444,14 @@ class MoneroWallet extends CoinServiceAPI { bool isSendAll = false; final balance = await availableBalance; final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoinMonero)) - .toDecimal() * - Decimal.fromInt(10000)); + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal()); if (satInDecimal == balance) { isSendAll = true; } Logging.instance .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = moneroAmountToString(amount: amount * 10000); + String amountToSend = moneroAmountToString(amount: amount); Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); monero_output.Output output = monero_output.Output(walletBase!); @@ -1470,10 +1473,9 @@ class MoneroWallet extends CoinServiceAPI { PendingMoneroTransaction pendingMoneroTransaction = await (awaitPendingTransaction!) as PendingMoneroTransaction; - int realfee = (Decimal.parse(pendingMoneroTransaction.feeFormatted) * - 100000000.toDecimal()) - .toBigInt() - .toInt(); + + int realfee = Format.decimalAmountToSatoshis( + Decimal.parse(pendingMoneroTransaction.feeFormatted), coin); debugPrint("fee? $realfee"); Map txData = { "pendingMoneroTransaction": pendingMoneroTransaction, @@ -1506,12 +1508,13 @@ class MoneroWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - MoneroTransactionPriority? priority; - FeeRateType feeRateType = FeeRateType.slow; + MoneroTransactionPriority priority; + FeeRateType feeRateType; + switch (feeRate) { case 1: priority = MoneroTransactionPriority.regular; - feeRateType = FeeRateType.slow; + feeRateType = FeeRateType.average; break; case 2: priority = MoneroTransactionPriority.medium; @@ -1519,7 +1522,7 @@ class MoneroWallet extends CoinServiceAPI { break; case 3: priority = MoneroTransactionPriority.fast; - feeRateType = FeeRateType.average; + feeRateType = FeeRateType.fast; break; case 4: priority = MoneroTransactionPriority.fastest; @@ -1531,27 +1534,29 @@ class MoneroWallet extends CoinServiceAPI { feeRateType = FeeRateType.slow; break; } - var aprox; + // int? aprox; - await estimateFeeMutex.protect(() async { - { - try { - aprox = (await prepareSend( - // This address is only used for getting an approximate fee, never for sending - address: - "8347huhmj6Ggzr1BpZPJAD5oa96ob5Fe8GtQdGZDYVVYVsCgtUNH3pEEzExDuaAVZdC16D4FkAb24J6wUfsKkcZtC8EPXB7", - satoshiAmount: satoshiAmount, - args: {"feeRate": feeRateType}))['fee']; - await Future.delayed(const Duration(milliseconds: 1000)); - } catch (e, s) { - Logging.instance.log("$feeRateType $e $s", level: LogLevel.Error); - aprox = -9999999999999999; - } - } - }); + // corrupted size vs. prev_size occurs but not sure if related to fees or just generating monero transactions in general + + // await estimateFeeMutex.protect(() async { + // { + // try { + // aprox = (await prepareSend( + // // This address is only used for getting an approximate fee, never for sending + // address: + // "8347huhmj6Ggzr1BpZPJAD5oa96ob5Fe8GtQdGZDYVVYVsCgtUNH3pEEzExDuaAVZdC16D4FkAb24J6wUfsKkcZtC8EPXB7", + // satoshiAmount: satoshiAmount, + // args: {"feeRate": feeRateType}))['fee'] as int?; + // await Future.delayed(const Duration(milliseconds: 1000)); + // } catch (e, s) { + // Logging.instance.log("$feeRateType $e $s", level: LogLevel.Error); + final aprox = walletBase!.calculateEstimatedFee(priority, satoshiAmount); + // } + // } + // }); print("this is the aprox fee $aprox for $satoshiAmount"); - final fee = (aprox as int); + final fee = aprox; return fee; } diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index c8c84fb27..142bfb379 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -196,19 +196,21 @@ class NamecoinWallet extends CoinServiceAPI { Future get availableBalance async { final data = await utxoData; return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed); + data.satoshiBalance - data.satoshiBalanceUnconfirmed, + coin: coin); } @override Future get pendingBalance async { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); } @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + (Decimal.fromInt((await maxFee)) / + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(); @override @@ -218,13 +220,13 @@ class NamecoinWallet extends CoinServiceAPI { .get(boxName: walletId, key: 'totalBalance') as int?; if (totalBalance == null) { final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } else { - return Format.satoshisToAmount(totalBalance); + return Format.satoshisToAmount(totalBalance, coin: coin); } } final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance); + return Format.satoshisToAmount(data.satoshiBalance, coin: coin); } @override @@ -262,7 +264,8 @@ class NamecoinWallet extends CoinServiceAPI { @override Future get maxFee async { final fee = (await fees).fast as String; - final satsFee = Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin); + final satsFee = + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); return satsFee.floor().toBigInt().toInt(); } @@ -1086,7 +1089,8 @@ class NamecoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis(await availableBalance); + final balance = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (satoshiAmount == balance) { isSendAll = true; } @@ -1290,7 +1294,7 @@ class NamecoinWallet extends CoinServiceAPI { final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2), decimalPlaces: 2, locale: locale!); @@ -1490,9 +1494,9 @@ class NamecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast), - medium: Format.decimalAmountToSatoshis(medium), - slow: Format.decimalAmountToSatoshis(slow), + fast: Format.decimalAmountToSatoshis(fast, coin), + medium: Format.decimalAmountToSatoshis(medium, coin), + slow: Format.decimalAmountToSatoshis(slow, coin), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1965,7 +1969,7 @@ class NamecoinWallet extends CoinServiceAPI { utxo["status"]["block_time"] = txn["blocktime"]; final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); utxo["rawWorth"] = fiatValue; utxo["fiatWorth"] = fiatValue.toString(); @@ -1975,15 +1979,16 @@ class NamecoinWallet extends CoinServiceAPI { Decimal currencyBalanceRaw = ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2); final Map result = { "total_user_currency": currencyBalanceRaw.toString(), "total_sats": satoshiBalance, "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) .toString(), "outputArray": outputArray, "unconfirmed": satoshiBalancePending, @@ -2540,7 +2545,7 @@ class NamecoinWallet extends CoinServiceAPI { if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2554,7 +2559,7 @@ class NamecoinWallet extends CoinServiceAPI { final address = output["scriptPubKey"]["address"]; final value = output["value"]; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOutput += _value; @@ -2582,7 +2587,7 @@ class NamecoinWallet extends CoinServiceAPI { } if (address != null) { final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); totalOut += value; @@ -2605,7 +2610,7 @@ class NamecoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); } @@ -2627,7 +2632,7 @@ class NamecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = inputAmtSentFromWallet; final String worthNow = ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -2637,7 +2642,7 @@ class NamecoinWallet extends CoinServiceAPI { midSortedTx["amount"] = outputAmtAddressedToWallet; final worthNow = ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin)) + Decimal.fromInt(Constants.satsPerCoin(coin))) .toDecimal(scaleOnInfinitePrecision: 2) .toStringAsFixed(2); midSortedTx["worthNow"] = worthNow; @@ -3772,7 +3777,8 @@ class NamecoinWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = Format.decimalAmountToSatoshis(await availableBalance); + final available = + Format.decimalAmountToSatoshis(await availableBalance, coin); if (available == satoshiAmount) { return satoshiAmount - sweepAllEstimate(feeRate); diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index e6a531b78..686dcd08c 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -45,6 +45,7 @@ import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; @@ -534,8 +535,7 @@ class WowneroWallet extends CoinServiceAPI { @override Future get balanceMinusMaxFee async => (await availableBalance) - - (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) - .toDecimal(); + Format.satoshisToAmount(await maxFee, coin: Coin.wownero); @override Future get currentReceivingAddress => @@ -563,13 +563,15 @@ class WowneroWallet extends CoinServiceAPI { Future? _currentReceivingAddress; Future _getFees() async { + // TODO: not use hard coded values here return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 10, - numberOfBlocksSlow: 10, - fast: 4, - medium: 2, - slow: 0); + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: MoneroTransactionPriority.fast.raw!, + medium: MoneroTransactionPriority.regular.raw!, + slow: MoneroTransactionPriority.slow.raw!, + ); } @override @@ -873,8 +875,9 @@ class WowneroWallet extends CoinServiceAPI { Future get maxFee async { var bal = await availableBalance; var fee = walletBase!.calculateEstimatedFee( - wownero.getDefaultTransactionPriority(), bal.toBigInt().toInt()) ~/ - 10000; + wownero.getDefaultTransactionPriority(), + Format.decimalAmountToSatoshis(bal, coin), + ); return fee; } @@ -1446,13 +1449,13 @@ class WowneroWallet extends CoinServiceAPI { try { final feeRate = args?["feeRate"]; if (feeRate is FeeRateType) { - MoneroTransactionPriority feePriority = MoneroTransactionPriority.slow; + MoneroTransactionPriority feePriority; switch (feeRate) { case FeeRateType.fast: - feePriority = MoneroTransactionPriority.fastest; + feePriority = MoneroTransactionPriority.fast; break; case FeeRateType.average: - feePriority = MoneroTransactionPriority.medium; + feePriority = MoneroTransactionPriority.regular; break; case FeeRateType.slow: feePriority = MoneroTransactionPriority.slow; @@ -1465,15 +1468,14 @@ class WowneroWallet extends CoinServiceAPI { bool isSendAll = false; final balance = await availableBalance; final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoinWownero)) - .toDecimal() * - Decimal.fromInt(1000)); + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal()); if (satInDecimal == balance) { isSendAll = true; } Logging.instance .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = wowneroAmountToString(amount: amount * 1000); + String amountToSend = wowneroAmountToString(amount: amount); Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); wownero_output.Output output = wownero_output.Output(walletBase!); @@ -1495,10 +1497,8 @@ class WowneroWallet extends CoinServiceAPI { PendingWowneroTransaction pendingWowneroTransaction = await (awaitPendingTransaction!) as PendingWowneroTransaction; - int realfee = (Decimal.parse(pendingWowneroTransaction.feeFormatted) * - 100000000.toDecimal()) - .toBigInt() - .toInt(); + int realfee = Format.decimalAmountToSatoshis( + Decimal.parse(pendingWowneroTransaction.feeFormatted), coin); debugPrint("fee? $realfee"); Map txData = { "pendingWowneroTransaction": pendingWowneroTransaction, @@ -1531,12 +1531,12 @@ class WowneroWallet extends CoinServiceAPI { @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { - MoneroTransactionPriority? priority; + MoneroTransactionPriority priority; FeeRateType feeRateType = FeeRateType.slow; switch (feeRate) { case 1: priority = MoneroTransactionPriority.regular; - feeRateType = FeeRateType.slow; + feeRateType = FeeRateType.average; break; case 2: priority = MoneroTransactionPriority.medium; @@ -1544,7 +1544,7 @@ class WowneroWallet extends CoinServiceAPI { break; case 3: priority = MoneroTransactionPriority.fast; - feeRateType = FeeRateType.average; + feeRateType = FeeRateType.fast; break; case 4: priority = MoneroTransactionPriority.fastest; @@ -1568,7 +1568,7 @@ class WowneroWallet extends CoinServiceAPI { args: {"feeRate": feeRateType}))['fee']; await Future.delayed(const Duration(milliseconds: 500)); } catch (e, s) { - aprox = -9999999999999999; + aprox = walletBase!.calculateEstimatedFee(priority, satoshiAmount); } } }); diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 0a062de67..3263d526e 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -23,12 +23,12 @@ abstract class Constants { static bool enableExchange = Util.isDesktop || !Platform.isIOS; //TODO: correct for monero? - static const int satsPerCoinMonero = 1000000000000; - static const int satsPerCoinWownero = 100000000000; - static const int satsPerCoin = 100000000; - static const int decimalPlaces = 8; - static const int decimalPlacesWownero = 11; - static const int decimalPlacesMonero = 12; + static const int _satsPerCoinMonero = 1000000000000; + static const int _satsPerCoinWownero = 100000000000; + static const int _satsPerCoin = 100000000; + static const int _decimalPlaces = 8; + static const int _decimalPlacesWownero = 11; + static const int _decimalPlacesMonero = 12; static const int notificationsMax = 0xFFFFFFFF; static const Duration networkAliveTimerDuration = Duration(seconds: 10); @@ -40,6 +40,30 @@ abstract class Constants { static const int currentHiveDbVersion = 3; + static int satsPerCoin(Coin coin) { + switch (coin) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.litecoinTestNet: + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + case Coin.dogecoin: + case Coin.firo: + case Coin.bitcoinTestNet: + case Coin.dogecoinTestNet: + case Coin.firoTestNet: + case Coin.epicCash: + case Coin.namecoin: + return _satsPerCoin; + + case Coin.wownero: + return _satsPerCoinWownero; + + case Coin.monero: + return _satsPerCoinMonero; + } + } + static int decimalPlacesForCoin(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -54,13 +78,13 @@ abstract class Constants { case Coin.firoTestNet: case Coin.epicCash: case Coin.namecoin: - return decimalPlaces; + return _decimalPlaces; case Coin.wownero: - return decimalPlacesWownero; + return _decimalPlacesWownero; case Coin.monero: - return decimalPlacesMonero; + return _decimalPlacesMonero; } } diff --git a/lib/utilities/format.dart b/lib/utilities/format.dart index 775780833..136ec5b95 100644 --- a/lib/utilities/format.dart +++ b/lib/utilities/format.dart @@ -8,46 +8,28 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class Format { - static Decimal satoshisToAmount(int sats, {Coin? coin}) { - late final int satsPerCoin; - - switch (coin) { - case Coin.wownero: - satsPerCoin = Constants.satsPerCoinWownero; - break; - case Coin.monero: - satsPerCoin = Constants.satsPerCoinMonero; - break; - case Coin.bitcoin: - case Coin.bitcoincash: - case Coin.dogecoin: - case Coin.epicCash: - case Coin.firo: - case Coin.litecoin: - case Coin.namecoin: - case Coin.bitcoinTestNet: - case Coin.litecoinTestNet: - case Coin.bitcoincashTestnet: - case Coin.dogecoinTestNet: - case Coin.firoTestNet: - default: - satsPerCoin = Constants.satsPerCoin; - } - - return (Decimal.fromInt(sats) / Decimal.fromInt(satsPerCoin)) - .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces); + static Decimal satoshisToAmount(int sats, {required Coin coin}) { + return (Decimal.fromInt(sats) / + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toDecimal( + scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)); } /// - static String satoshiAmountToPrettyString(int sats, String locale) { - final amount = satoshisToAmount(sats); + static String satoshiAmountToPrettyString( + int sats, String locale, Coin coin) { + final amount = satoshisToAmount(sats, coin: coin); return localizedStringAsFixed( - value: amount, locale: locale, decimalPlaces: Constants.decimalPlaces); + value: amount, + locale: locale, + decimalPlaces: Constants.decimalPlacesForCoin(coin), + ); } - static int decimalAmountToSatoshis(Decimal amount) { - final value = - (Decimal.fromInt(Constants.satsPerCoin) * amount).floor().toBigInt(); + static int decimalAmountToSatoshis(Decimal amount, Coin coin) { + final value = (Decimal.fromInt(Constants.satsPerCoin(coin)) * amount) + .floor() + .toBigInt(); return value.toInt(); } diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index 15dcf2b4d..4389573c3 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -9,7 +9,6 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_deta import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -198,13 +197,9 @@ class _TransactionCardState extends ConsumerState { fit: BoxFit.scaleDown, child: Builder( builder: (_) { - final amount = coin == Coin.monero - ? (_transaction.amount ~/ 10000) - : coin == Coin.wownero - ? (_transaction.amount ~/ 1000) - : _transaction.amount; + final amount = _transaction.amount; return Text( - "$prefix${Format.satoshiAmountToPrettyString(amount, locale)} ${coin.ticker}", + "$prefix${Format.satoshiAmountToPrettyString(amount, locale, coin)} ${coin.ticker}", style: STextStyles.itemSubtitle12_600(context), ); @@ -242,17 +237,12 @@ class _TransactionCardState extends ConsumerState { fit: BoxFit.scaleDown, child: Builder( builder: (_) { - // TODO: modify Format. to take optional Coin parameter so this type oif check isn't done in ui int value = _transaction.amount; - if (coin == Coin.monero) { - value = (value ~/ 10000); - } else if (coin == Coin.wownero) { - value = (value ~/ 1000); - } return Text( "$prefix${Format.localizedStringAsFixed( - value: Format.satoshisToAmount(value) * + value: Format.satoshisToAmount(value, + coin: coin) * price, locale: locale, decimalPlaces: 2, From ffe9a83abf170b7082a24f030b5219daefe9d203 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 12:38:36 -0600 Subject: [PATCH 195/225] Format tests updated --- test/formet_test.dart | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/formet_test.dart b/test/formet_test.dart index 4f7136cd4..e27293114 100644 --- a/test/formet_test.dart +++ b/test/formet_test.dart @@ -1,54 +1,64 @@ import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; void main() { group("satoshisToAmount", () { test("12345", () { - expect(Format.satoshisToAmount(12345), Decimal.parse("0.00012345")); + expect(Format.satoshisToAmount(12345, coin: Coin.bitcoin), + Decimal.parse("0.00012345")); }); test("100012345", () { - expect(Format.satoshisToAmount(100012345), Decimal.parse("1.00012345")); + expect(Format.satoshisToAmount(100012345, coin: Coin.bitcoin), + Decimal.parse("1.00012345")); }); test("0", () { - expect(Format.satoshisToAmount(0), Decimal.zero); + expect(Format.satoshisToAmount(0, coin: Coin.bitcoin), Decimal.zero); }); test("1000000000", () { - expect(Format.satoshisToAmount(1000000000), Decimal.parse("10")); + expect(Format.satoshisToAmount(1000000000, coin: Coin.bitcoin), + Decimal.parse("10")); }); }); group("satoshiAmountToPrettyString", () { const locale = "en_US"; test("12345", () { - expect(Format.satoshiAmountToPrettyString(12345, locale), "0.00012345"); + expect(Format.satoshiAmountToPrettyString(12345, locale, Coin.bitcoin), + "0.00012345"); }); test("100012345", () { expect( - Format.satoshiAmountToPrettyString(100012345, locale), "1.00012345"); + Format.satoshiAmountToPrettyString(100012345, locale, Coin.bitcoin), + "1.00012345"); }); test("123450000", () { expect( - Format.satoshiAmountToPrettyString(123450000, locale), "1.23450000"); + Format.satoshiAmountToPrettyString(123450000, locale, Coin.bitcoin), + "1.23450000"); }); test("1230045000", () { - expect(Format.satoshiAmountToPrettyString(1230045000, locale), + expect( + Format.satoshiAmountToPrettyString(1230045000, locale, Coin.bitcoin), "12.30045000"); }); test("1000000000", () { - expect(Format.satoshiAmountToPrettyString(1000000000, locale), + expect( + Format.satoshiAmountToPrettyString(1000000000, locale, Coin.bitcoin), "10.00000000"); }); test("0", () { - expect(Format.satoshiAmountToPrettyString(0, locale), "0.00000000"); + expect(Format.satoshiAmountToPrettyString(0, locale, Coin.bitcoin), + "0.00000000"); }); }); From 85b9fdc2f3c07f353166deb2a087bbf52ca50ef4 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 12:42:08 -0600 Subject: [PATCH 196/225] random hardcoded values :/ --- lib/services/coins/monero/monero_wallet.dart | 6 +++--- lib/services/coins/wownero/wownero_wallet.dart | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 58bd36c72..6f1e49ee5 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -564,11 +564,11 @@ class MoneroWallet extends CoinServiceAPI { Future? _currentReceivingAddress; Future _getFees() async { - // TODO: not use hard coded values here + // TODO: not use random hard coded values here return FeeObject( numberOfBlocksFast: 10, - numberOfBlocksAverage: 10, - numberOfBlocksSlow: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, fast: MoneroTransactionPriority.fast.raw!, medium: MoneroTransactionPriority.regular.raw!, slow: MoneroTransactionPriority.slow.raw!, diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 686dcd08c..72580ea4a 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -563,11 +563,11 @@ class WowneroWallet extends CoinServiceAPI { Future? _currentReceivingAddress; Future _getFees() async { - // TODO: not use hard coded values here + // TODO: not use random hard coded values here return FeeObject( numberOfBlocksFast: 10, - numberOfBlocksAverage: 10, - numberOfBlocksSlow: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, fast: MoneroTransactionPriority.fast.raw!, medium: MoneroTransactionPriority.regular.raw!, slow: MoneroTransactionPriority.slow.raw!, From d14593407df61ebf656028bc88985fa0e27172ad Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Wed, 23 Nov 2022 17:48:58 -0700 Subject: [PATCH 197/225] v1.5.19 build 91 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f79879f61..7ca368d29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.18+90 +version: 1.5.19+91 environment: sdk: ">=2.17.0 <3.0.0" From 08c6fb72ac8fe2cd4eed91c403d83be65459e874 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 23 Nov 2022 14:55:04 -0600 Subject: [PATCH 198/225] mobile confirm send button height fix --- lib/pages/send_view/confirm_transaction_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 1ddeb3c9f..fd5341dd9 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -780,7 +780,7 @@ class _ConfirmTransactionViewState : const EdgeInsets.all(0), child: PrimaryButton( label: "Send", - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { final dynamic unlocked; From 3bda6620efdb24210667d207cd151c7b601e6142 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 24 Nov 2022 18:07:17 -0600 Subject: [PATCH 199/225] reduce minimum height and set starting height lower on linux --- lib/main.dart | 2 +- linux/my_application.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 728152951..11a851b79 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,7 +77,7 @@ void main() async { if (Util.isDesktop) { setWindowTitle('Stack Wallet'); - setWindowMinSize(const Size(1220, 900)); + setWindowMinSize(const Size(1220, 100)); setWindowMaxSize(Size.infinite); } diff --git a/linux/my_application.cc b/linux/my_application.cc index 9cb3acebd..d342c1506 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -47,7 +47,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "Stack Wallet"); } - gtk_window_set_default_size(window, 1220, 900); + gtk_window_set_default_size(window, 1220, 500); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); From 286f6a552b07294ad9f4d6795f74e7d834162376 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 24 Nov 2022 18:16:26 -0600 Subject: [PATCH 200/225] desktop send to coin ticker fixed --- .../exchange_view/confirm_change_now_send.dart | 11 ++++++++--- lib/pages/send_view/confirm_transaction_view.dart | 13 ++++++++++--- .../wallet_view/sub_widgets/desktop_auth_send.dart | 14 +++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 0e1eef755..810ea2541 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -157,6 +157,9 @@ class _ConfirmChangeNowSendViewState Future _confirmSend() async { final dynamic unlocked; + final coin = + ref.read(walletsChangeNotifierProvider).getManager(walletId).coin; + if (Util.isDesktop) { unlocked = await showDialog( context: context, @@ -172,13 +175,15 @@ class _ConfirmChangeNowSendViewState DesktopDialogCloseButton(), ], ), - const Padding( - padding: EdgeInsets.only( + Padding( + padding: const EdgeInsets.only( left: 32, right: 32, bottom: 32, ), - child: DesktopAuthSend(), + child: DesktopAuthSend( + coin: coin, + ), ), ], ), diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index fd5341dd9..16de6cf55 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -784,6 +784,11 @@ class _ConfirmTransactionViewState onPressed: () async { final dynamic unlocked; + final coin = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .coin; + if (isDesktop) { unlocked = await showDialog( context: context, @@ -799,13 +804,15 @@ class _ConfirmTransactionViewState DesktopDialogCloseButton(), ], ), - const Padding( - padding: EdgeInsets.only( + Padding( + padding: const EdgeInsets.only( left: 32, right: 32, bottom: 32, ), - child: DesktopAuthSend(), + child: DesktopAuthSend( + coin: coin, + ), ), ], ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index a8d1ea497..20bb3f95a 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -6,17 +6,21 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; -import '../../../../../notifications/show_flush_bar.dart'; -import '../../../../../widgets/loading_indicator.dart'; - class DesktopAuthSend extends ConsumerStatefulWidget { - const DesktopAuthSend({Key? key}) : super(key: key); + const DesktopAuthSend({ + Key? key, + required this.coin, + }) : super(key: key); + + final Coin coin; @override ConsumerState createState() => _DesktopAuthSendState(); @@ -72,7 +76,7 @@ class _DesktopAuthSendState extends ConsumerState { height: 16, ), Text( - "Enter your wallet password to send BTC", + "Enter your wallet password to send ${widget.coin.ticker.toUpperCase()}", style: STextStyles.desktopTextMedium(context).copyWith( color: Theme.of(context).extension()!.textDark3, ), From d71899d1dff48ded1ec45d02abb3c88a0b1c37c8 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 24 Nov 2022 18:22:35 -0600 Subject: [PATCH 201/225] mobile exchange form top padding added --- lib/pages/exchange_view/exchange_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 84e054eac..ed99047b2 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -43,7 +43,11 @@ class _ExchangeViewState extends ConsumerState { handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: const SliverToBoxAdapter( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), child: ExchangeForm(), ), ), From 7db3abab473af403c156d26be580713e3d1e4a4f Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 09:01:09 -0600 Subject: [PATCH 202/225] desktop starting to height be 3/4 screen height or 900, whichever is smaller --- lib/main.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 11a851b79..d5980409f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:cw_core/node.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -79,6 +80,15 @@ void main() async { setWindowTitle('Stack Wallet'); setWindowMinSize(const Size(1220, 100)); setWindowMaxSize(Size.infinite); + final screen = await getCurrentScreen(); + final screenHeight = screen?.frame.height; + if (screenHeight != null) { + // starting to height be 3/4 screen height or 900, whichever is smaller + final height = min(screenHeight * 0.75, 900); + setWindowFrame( + Rect.fromLTWH(0, 0, 1220, height), + ); + } } // FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); From 42aad5dcd53c9367ffd9f6672299b81b29af8f8a Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 13:24:01 -0600 Subject: [PATCH 203/225] themed background gradient option and background image, as well as various button height fixes for mobile --- assets/svg/oceanBreeze/bg.svg | 11 + .../add_wallet_view/add_wallet_view.dart | 67 +- .../create_or_restore_wallet_view.dart | 83 +- .../name_your_wallet_view.dart | 65 +- .../new_wallet_recovery_phrase_view.dart | 1 - .../address_book_views/address_book_view.dart | 184 +- .../subviews/add_address_book_entry_view.dart | 115 +- .../add_new_contact_address_view.dart | 80 +- .../subviews/address_book_filter_view.dart | 97 +- .../subviews/contact_details_view.dart | 764 ++--- .../subviews/edit_contact_address_view.dart | 80 +- .../edit_contact_name_emoji_view.dart | 80 +- .../exchange_view/choose_from_stack_view.dart | 154 +- .../confirm_change_now_send.dart | 77 +- .../exchange_view/edit_trade_note_view.dart | 190 +- .../fixed_rate_pair_coin_selection_view.dart | 50 +- ...floating_rate_currency_selection_view.dart | 50 +- .../exchange_step_views/step_1_view.dart | 304 +- .../exchange_step_views/step_2_view.dart | 1077 +++---- .../exchange_step_views/step_3_view.dart | 522 ++-- .../exchange_step_views/step_4_view.dart | 1034 +++---- lib/pages/exchange_view/send_from_view.dart | 33 +- .../exchange_view/trade_details_view.dart | 40 +- .../wallet_initiated_exchange_view.dart | 131 +- lib/pages/home_view/home_view.dart | 232 +- lib/pages/intro_view.dart | 193 +- lib/pages/loading_view.dart | 37 +- .../notifications_view.dart | 113 +- lib/pages/pinpad_views/create_pin_view.dart | 403 +-- lib/pages/pinpad_views/lock_screen_view.dart | 299 +- .../generate_receiving_uri_qr_code_view.dart | 87 +- lib/pages/receive_view/receive_view.dart | 266 +- .../send_view/confirm_transaction_view.dart | 76 +- lib/pages/send_view/send_view.dart | 2567 +++++++++-------- .../global_settings_view/about_view.dart | 802 ++--- .../advanced_settings_view.dart | 289 +- .../advanced_views/debug_view.dart | 869 +++--- .../appearance_settings_view.dart | 223 +- .../global_settings_view/currency_view.dart | 54 +- .../global_settings_view.dart | 478 +-- .../global_settings_view/hidden_settings.dart | 270 +- .../global_settings_view/language_view.dart | 368 +-- .../add_edit_node_view.dart | 160 +- .../manage_nodes_views/coin_nodes_view.dart | 117 +- .../manage_nodes_views/manage_nodes_view.dart | 156 +- .../manage_nodes_views/node_details_view.dart | 147 +- .../change_pin_view/change_pin_view.dart | 335 +-- .../security_views/security_view.dart | 226 +- .../stack_backup_views/auto_backup_view.dart | 450 +-- .../create_auto_backup_view.dart | 1051 +++---- .../create_backup_information_view.dart | 129 +- .../create_backup_view.dart | 70 +- .../edit_auto_backup_view.dart | 54 +- .../restore_from_encrypted_string_view.dart | 365 +-- .../restore_from_file_view.dart | 71 +- .../stack_backup_views/stack_backup_view.dart | 259 +- .../startup_preferences_view.dart | 463 +-- .../startup_wallet_selection_view.dart | 308 +- .../global_settings_view/support_view.dart | 33 +- .../syncing_options_view.dart | 57 +- .../syncing_preferences_view.dart | 240 +- .../wallet_syncing_options_view.dart | 45 +- .../wallet_backup_view.dart | 333 +-- .../wallet_network_settings_view.dart | 209 +- .../wallet_settings_view.dart | 370 +-- .../delete_wallet_recovery_phrase_view.dart | 308 +- .../delete_wallet_warning_view.dart | 162 +- .../rename_wallet_view.dart | 186 +- .../wallet_settings_wallet_settings_view.dart | 275 +- .../transaction_views/edit_note_view.dart | 271 +- .../transaction_details_view.dart | 1962 ++++++------- .../transaction_search_filter_view.dart | 73 +- lib/pages/wallet_view/wallet_view.dart | 737 ++--- .../desktop_login_view.dart | 1 - .../home/desktop_home_view.dart | 33 +- .../home/my_stack_view/my_stack_view.dart | 59 +- lib/utilities/assets.dart | 11 + lib/utilities/theme/color_theme.dart | 7 +- lib/utilities/theme/dark_colors.dart | 5 + lib/utilities/theme/light_colors.dart | 5 + lib/utilities/theme/ocean_breeze_colors.dart | 14 +- lib/utilities/theme/stack_colors.dart | 17 + lib/widgets/background.dart | 67 + lib/widgets/desktop/desktop_scaffold.dart | 36 +- pubspec.yaml | 1 + 85 files changed, 11664 insertions(+), 11129 deletions(-) create mode 100644 assets/svg/oceanBreeze/bg.svg create mode 100644 lib/widgets/background.dart diff --git a/assets/svg/oceanBreeze/bg.svg b/assets/svg/oceanBreeze/bg.svg new file mode 100644 index 000000000..35fbda281 --- /dev/null +++ b/assets/svg/oceanBreeze/bg.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index e964610ae..29bae26c1 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.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'; @@ -182,39 +183,43 @@ class _AddWalletViewState extends State { ), ); } else { - return Scaffold( - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), ), - ), - body: Container( - color: Theme.of(context).extension()!.background, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const AddWalletText( - isDesktop: false, - ), - const SizedBox( - height: 16, - ), - Expanded( - child: MobileCoinList( - coins: coins, + body: Container( + color: Theme.of(context).extension()!.background, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const AddWalletText( + isDesktop: false, ), - ), - const SizedBox( - height: 16, - ), - const AddWalletNextButton( - isDesktop: false, - ), - ], + const SizedBox( + height: 16, + ), + Expanded( + child: MobileCoinList( + coins: coins, + ), + ), + const SizedBox( + height: 16, + ), + const AddWalletNextButton( + isDesktop: false, + ), + ], + ), ), ), ), diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart index b3be99e25..1dfdc2a53 100644 --- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/exit_to_my import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.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'; @@ -77,49 +78,53 @@ class CreateOrRestoreWalletView extends StatelessWidget { ), ); } else { - return Scaffold( - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), ), - ), - body: Container( - color: Theme.of(context).extension()!.background, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(31), - child: CoinImage( + body: Container( + color: Theme.of(context).extension()!.background, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(31), + child: CoinImage( + coin: coin, + isDesktop: isDesktop, + ), + ), + const Spacer( + flex: 2, + ), + CreateRestoreWalletTitle( coin: coin, isDesktop: isDesktop, ), - ), - const Spacer( - flex: 2, - ), - CreateRestoreWalletTitle( - coin: coin, - isDesktop: isDesktop, - ), - const SizedBox( - height: 8, - ), - CreateRestoreWalletSubTitle( - isDesktop: isDesktop, - ), - const Spacer( - flex: 5, - ), - CreateWalletButtonGroup( - coin: coin, - isDesktop: isDesktop, - ), - ], + const SizedBox( + height: 8, + ), + CreateRestoreWalletSubTitle( + isDesktop: isDesktop, + ), + const Spacer( + flex: 5, + ), + CreateWalletButtonGroup( + coin: coin, + isDesktop: isDesktop, + ), + ], + ), ), ), ), diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart index 8bc01b124..e435e285d 100644 --- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart +++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart @@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/name_generator.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.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'; @@ -108,40 +109,44 @@ class _NameYourWalletViewState extends ConsumerState { ), ); } else { - return Scaffold( - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - if (textFieldFocusNode.hasFocus) { - textFieldFocusNode.unfocus(); - Future.delayed(const Duration(milliseconds: 100)) - .then((value) => Navigator.of(context).pop()); - } else { - if (mounted) { - Navigator.of(context).pop(); + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + if (textFieldFocusNode.hasFocus) { + textFieldFocusNode.unfocus(); + Future.delayed(const Duration(milliseconds: 100)) + .then((value) => Navigator.of(context).pop()); + } else { + if (mounted) { + Navigator.of(context).pop(); + } } - } - }, - ), - ), - body: Container( - color: Theme.of(context).extension()!.background, - child: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (ctx, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight( - child: _content(), - ), - ), - ); }, ), ), + body: Container( + color: Theme.of(context).extension()!.background, + child: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (ctx, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: _content(), + ), + ), + ); + }, + ), + ), + ), ), ); } diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart index faab6d08c..b3ceb0968 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart @@ -16,7 +16,6 @@ import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index c87906870..0fbf334a7 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; @@ -108,110 +109,115 @@ class _AddressBookViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Address book", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addressBookFilterViewButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.filter, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Address book", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addressBookFilterViewButton"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .accentColorDark, - width: 20, - height: 20, + .background, + icon: SvgPicture.asset( + Assets.svg.filter, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddressBookFilterView.routeName, + ); + }, ), - onPressed: () { - Navigator.of(context).pushNamed( - AddressBookFilterView.routeName, - ); - }, ), ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addressBookAddNewContactViewButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.plus, + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addressBookAddNewContactViewButton"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .accentColorDark, - width: 20, - height: 20, + .background, + icon: SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddAddressBookEntryView.routeName, + ); + }, ), - onPressed: () { - Navigator.of(context).pushNamed( - AddAddressBookEntryView.routeName, - ); - }, ), ), - ), - ], - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height - 271, + ], + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + MediaQuery.of(context).size.height - 271, + ), + child: child, ), - child: child, ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); }, diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 2759e9cb1..36191e0b7 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -16,6 +16,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -116,65 +117,67 @@ class _AddAddressBookEntryViewState return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "New contact", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("addAddressBookEntryFavoriteButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context) - .extension()! - .background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _isFavorite - ? Theme.of(context) - .extension()! - .favoriteStarActive - : Theme.of(context) - .extension()! - .favoriteStarInactive, - width: 20, - height: 20, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "New contact", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addAddressBookEntryFavoriteButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _isFavorite + ? Theme.of(context) + .extension()! + .favoriteStarActive + : Theme.of(context) + .extension()! + .favoriteStarInactive, + width: 20, + height: 20, + ), + onPressed: () { + setState(() { + _isFavorite = !_isFavorite; + }); + }, ), - onPressed: () { - setState(() { - _isFavorite = !_isFavorite; - }); - }, ), ), - ), - ], - ), - body: child); + ], + ), + body: child), + ); }, child: ConditionalParent( condition: isDesktop, diff --git a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart index dc25c3dc1..de2dfe90c 100644 --- a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -63,48 +64,51 @@ class _AddNewContactAddressViewState return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Add new address", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Add new address", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), child: Column( diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index 55c3d47ac..9f410aae7 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -50,60 +51,62 @@ class _AddressBookFilterViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( + return Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Filter addresses", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Filter addresses", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: LayoutBuilder(builder: (builderContext, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - child: Text( - "Only selected cryptocurrency addresses will be displayed.", - style: STextStyles.itemSubtitle(context), + body: Padding( + padding: const EdgeInsets.all(12), + child: LayoutBuilder(builder: (builderContext, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Text( + "Only selected cryptocurrency addresses will be displayed.", + style: STextStyles.itemSubtitle(context), + ), ), - ), - const SizedBox( - height: 12, - ), - Text( - "Select cryptocurrency", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - child, - ], + const SizedBox( + height: 12, + ), + Text( + "Select cryptocurrency", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + child, + ], + ), ), ), ), - ), - ); - }), + ); + }), + ), ), ); }, diff --git a/lib/pages/address_book_views/subviews/contact_details_view.dart b/lib/pages/address_book_views/subviews/contact_details_view.dart index a48a535c6..0ab6ebba9 100644 --- a/lib/pages/address_book_views/subviews/contact_details_view.dart +++ b/lib/pages/address_book_views/subviews/contact_details_view.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.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/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -104,335 +105,203 @@ class _ContactDetailsViewState extends ConsumerState { final _contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(_contactId))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Contact details", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("contactDetails"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _contact.isFavorite - ? Theme.of(context) - .extension()! - .favoriteStarActive - : Theme.of(context) - .extension()! - .favoriteStarInactive, - width: 20, - height: 20, - ), - onPressed: () { - bool isFavorite = _contact.isFavorite; + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Contact details", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("contactDetails"), + size: 36, + shadows: const [], + color: Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _contact.isFavorite + ? Theme.of(context) + .extension()! + .favoriteStarActive + : Theme.of(context) + .extension()! + .favoriteStarInactive, + width: 20, + height: 20, + ), + onPressed: () { + bool isFavorite = _contact.isFavorite; - ref - .read(addressBookServiceProvider) - .editContact(_contact.copyWith(isFavorite: !isFavorite)); - }, + ref.read(addressBookServiceProvider).editContact( + _contact.copyWith(isFavorite: !isFavorite)); + }, + ), ), ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("contactDetailsViewDeleteContactButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.trash, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Delete ${_contact.name}?", - message: "Contact will be deleted permanently!", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("contactDetailsViewDeleteContactButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.trash, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Delete ${_contact.name}?", + message: "Contact will be deleted permanently!", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Delete", + style: STextStyles.button(context), + ), + onPressed: () { + ref + .read(addressBookServiceProvider) + .removeContact(_contact.id); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.success, + message: "${_contact.name} deleted", + context: context, + ); + }, ), - onPressed: () { - Navigator.of(context).pop(); - }, ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Delete", - style: STextStyles.button(context), + ); + }, + ), + ), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 12, + ), + Row( + children: [ + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: Theme.of(context) + .extension()! + .textFieldActiveBG, ), + child: Center( + child: _contact.emojiChar == null + ? SvgPicture.asset( + Assets.svg.user, + height: 24, + width: 24, + ) + : Text( + _contact.emojiChar!, + style: STextStyles.pageTitleH1(context), + ), + ), + ), + const SizedBox( + width: 16, + ), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + _contact.name, + textAlign: TextAlign.left, + style: STextStyles.pageTitleH2(context), + ), + ), + const Spacer(), + TextButton( onPressed: () { - ref - .read(addressBookServiceProvider) - .removeContact(_contact.id); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.success, - message: "${_contact.name} deleted", - context: context, + Navigator.of(context).pushNamed( + EditContactNameEmojiView.routeName, + arguments: _contact.id, ); }, - ), - ), - ); - }, - ), - ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - ), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 12, - ), - Row( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: Theme.of(context) + style: Theme.of(context) .extension()! - .textFieldActiveBG, - ), - child: Center( - child: _contact.emojiChar == null - ? SvgPicture.asset( - Assets.svg.user, - height: 24, - width: 24, - ) - : Text( - _contact.emojiChar!, - style: STextStyles.pageTitleH1(context), - ), - ), - ), - const SizedBox( - width: 16, - ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - _contact.name, - textAlign: TextAlign.left, - style: STextStyles.pageTitleH2(context), - ), - ), - const Spacer(), - TextButton( - onPressed: () { - Navigator.of(context).pushNamed( - EditContactNameEmojiView.routeName, - arguments: _contact.id, - ); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context)! - .copyWith( - minimumSize: MaterialStateProperty.all( - const Size(46, 32)), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - SvgPicture.asset(Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension()! - .accentColorDark), - const SizedBox( - width: 4, + .getSecondaryEnabledButtonColor(context)! + .copyWith( + minimumSize: MaterialStateProperty.all( + const Size(46, 32)), ), - Text( - "Edit", - style: STextStyles.buttonSmall(context), - ), - ], - ), - ), - ), - ], - ), - const SizedBox( - height: 24, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Addresses", - style: STextStyles.itemSubtitle(context), - ), - BlueTextButton( - text: "Add new", - onTap: () { - Navigator.of(context).pushNamed( - AddNewContactAddressView.routeName, - arguments: _contact.id, - ); - }, - ), - ], - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ..._contact.addresses.map( - (e) => Padding( - padding: const EdgeInsets.all(12), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: e.coin), - height: 24, - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${e.label} (${e.coin.ticker})", - style: - STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 2, - ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - e.address, - style: STextStyles.itemSubtitle(context) - .copyWith( - fontSize: 8, - ), - ), - ), - ], - ), - ), - GestureDetector( - onTap: () { - ref - .read(addressEntryDataProvider(0)) - .address = e.address; - ref - .read(addressEntryDataProvider(0)) - .addressLabel = e.label; - ref.read(addressEntryDataProvider(0)).coin = - e.coin; - - Navigator.of(context).pushNamed( - EditContactAddressView.routeName, - arguments: Tuple2(_contact.id, e), - ); - }, - child: RoundedContainer( + SvgPicture.asset(Assets.svg.pencil, + width: 10, + height: 10, color: Theme.of(context) .extension()! - .textFieldDefaultBG, - padding: const EdgeInsets.all(6), - child: SvgPicture.asset( - Assets.svg.pencil, - width: 14, - height: 14, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), + .accentColorDark), const SizedBox( width: 4, ), - GestureDetector( - onTap: () { - clipboard.setData( - ClipboardData(text: e.address), - ); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - child: RoundedContainer( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - padding: const EdgeInsets.all(6), - child: SvgPicture.asset( - Assets.svg.copy, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), + Text( + "Edit", + style: STextStyles.buttonSmall(context), ), ], ), @@ -440,81 +309,216 @@ class _ContactDetailsViewState extends ConsumerState { ), ], ), - ), - const SizedBox( - height: 24, - ), - Text( - "Transaction history", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: _filteredTransactionsByContact( - ref.watch(walletsChangeNotifierProvider).managers), - builder: (_, - AsyncSnapshot>> - snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - _cachedTransactions = snapshot.data!; - - if (_cachedTransactions.isNotEmpty) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ..._cachedTransactions.map( - (e) => TransactionCard( - key: Key( - "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), - transaction: e.item2, - walletId: e.item1, + const SizedBox( + height: 24, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Addresses", + style: STextStyles.itemSubtitle(context), + ), + BlueTextButton( + text: "Add new", + onTap: () { + Navigator.of(context).pushNamed( + AddNewContactAddressView.routeName, + arguments: _contact.id, + ); + }, + ), + ], + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + ..._contact.addresses.map( + (e) => Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: e.coin), + height: 24, ), - ), - ], - ), - ); - } else { - return RoundedWhiteContainer( - child: Center( - child: Text( - "No transactions found", - style: STextStyles.itemSubtitle(context), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "${e.label} (${e.coin.ticker})", + style: + STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 2, + ), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + e.address, + style: + STextStyles.itemSubtitle(context) + .copyWith( + fontSize: 8, + ), + ), + ), + ], + ), + ), + GestureDetector( + onTap: () { + ref + .read(addressEntryDataProvider(0)) + .address = e.address; + ref + .read(addressEntryDataProvider(0)) + .addressLabel = e.label; + ref.read(addressEntryDataProvider(0)).coin = + e.coin; + + Navigator.of(context).pushNamed( + EditContactAddressView.routeName, + arguments: Tuple2(_contact.id, e), + ); + }, + child: RoundedContainer( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + padding: const EdgeInsets.all(6), + child: SvgPicture.asset( + Assets.svg.pencil, + width: 14, + height: 14, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + const SizedBox( + width: 4, + ), + GestureDetector( + onTap: () { + clipboard.setData( + ClipboardData(text: e.address), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: RoundedContainer( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + padding: const EdgeInsets.all(6), + child: SvgPicture.asset( + Assets.svg.copy, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ], ), ), - ); - } - } else { - // TODO: proper loading animation - if (_cachedTransactions.isEmpty) { - return const LoadingIndicator(); - } else { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ..._cachedTransactions.map( - (e) => TransactionCard( - key: Key( - "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), - transaction: e.item2, - walletId: e.item1, + ), + ], + ), + ), + const SizedBox( + height: 24, + ), + Text( + "Transaction history", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: _filteredTransactionsByContact( + ref.watch(walletsChangeNotifierProvider).managers), + builder: (_, + AsyncSnapshot>> + snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cachedTransactions = snapshot.data!; + + if (_cachedTransactions.isNotEmpty) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + ..._cachedTransactions.map( + (e) => TransactionCard( + key: Key( + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), + transaction: e.item2, + walletId: e.item1, + ), ), + ], + ), + ); + } else { + return RoundedWhiteContainer( + child: Center( + child: Text( + "No transactions found", + style: STextStyles.itemSubtitle(context), ), - ], - ), - ); + ), + ); + } + } else { + // TODO: proper loading animation + if (_cachedTransactions.isEmpty) { + return const LoadingIndicator(); + } else { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + ..._cachedTransactions.map( + (e) => TransactionCard( + key: Key( + "contactDetailsTransaction_${e.item1}_${e.item2.txid}_cardKey"), + transaction: e.item2, + walletId: e.item1, + ), + ), + ], + ), + ); + } } - } - }, - ), - const SizedBox( - height: 16, - ), - ], + }, + ), + const SizedBox( + height: 16, + ), + ], + ), ), ), ), diff --git a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart index f0143d39d..0454903c3 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -103,48 +104,51 @@ class _EditContactAddressViewState return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit address", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit address", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), child: Column( diff --git a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart index a9b264b3c..99638ad2c 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -79,48 +80,51 @@ class _EditContactNameEmojiViewState return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit contact", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit contact", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), child: Column( diff --git a/lib/pages/exchange_view/choose_from_stack_view.dart b/lib/pages/exchange_view/choose_from_stack_view.dart index f54a7552c..7c7669430 100644 --- a/lib/pages/exchange_view/choose_from_stack_view.dart +++ b/lib/pages/exchange_view/choose_from_stack_view.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.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/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart'; @@ -39,89 +40,92 @@ class _ChooseFromStackViewState extends ConsumerState { final walletIds = ref.watch(walletsChangeNotifierProvider .select((value) => value.getWalletIdsFor(coin: coin))); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: const AppBarBackButton(), - title: Text( - "Choose your ${coin.ticker.toUpperCase()} wallet", - style: STextStyles.navBarTitle(context), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: const AppBarBackButton(), + title: Text( + "Choose your ${coin.ticker.toUpperCase()} wallet", + style: STextStyles.navBarTitle(context), + ), ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: walletIds.isEmpty - ? Column( - children: [ - RoundedWhiteContainer( - child: Center( - child: Text( - "No ${coin.ticker.toUpperCase()} wallets", - style: STextStyles.itemSubtitle(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: walletIds.isEmpty + ? Column( + children: [ + RoundedWhiteContainer( + child: Center( + child: Text( + "No ${coin.ticker.toUpperCase()} wallets", + style: STextStyles.itemSubtitle(context), + ), ), ), - ), - ], - ) - : ListView.builder( - itemCount: walletIds.length, - itemBuilder: (context, index) { - final manager = ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletIds[index]))); + ], + ) + : ListView.builder( + itemCount: walletIds.length, + itemBuilder: (context, index) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletIds[index]))); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: RawMaterialButton( - splashColor: - Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - padding: const EdgeInsets.all(0), - // color: Theme.of(context).extension()!.popupBG, - elevation: 0, - onPressed: () async { - if (mounted) { - Navigator.of(context).pop(manager.walletId); - } - }, - child: RoundedWhiteContainer( - // color: Colors.transparent, - child: Row( - children: [ - WalletInfoCoinIcon(coin: coin), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - manager.walletName, - style: STextStyles.titleBold12(context), - overflow: TextOverflow.ellipsis, - ), - const SizedBox( - height: 2, - ), - WalletInfoRowBalanceFuture( - walletId: walletIds[index], - ), - ], + padding: const EdgeInsets.all(0), + // color: Theme.of(context).extension()!.popupBG, + elevation: 0, + onPressed: () async { + if (mounted) { + Navigator.of(context).pop(manager.walletId); + } + }, + child: RoundedWhiteContainer( + // color: Colors.transparent, + child: Row( + children: [ + WalletInfoCoinIcon(coin: coin), + const SizedBox( + width: 12, ), - ) - ], + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + manager.walletName, + style: STextStyles.titleBold12(context), + overflow: TextOverflow.ellipsis, + ), + const SizedBox( + height: 2, + ), + WalletInfoRowBalanceFuture( + walletId: walletIds[index], + ), + ], + ), + ) + ], + ), ), ), - ), - ); - }, - ), + ); + }, + ), + ), ), ); } diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 810ea2541..25ae51ecf 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -232,49 +233,51 @@ class _ConfirmChangeNowSendViewState return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( + return Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Confirm transaction", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); }, diff --git a/lib/pages/exchange_view/edit_trade_note_view.dart b/lib/pages/exchange_view/edit_trade_note_view.dart index e2a72d1b4..19804b7c5 100644 --- a/lib/pages/exchange_view/edit_trade_note_view.dart +++ b/lib/pages/exchange_view/edit_trade_note_view.dart @@ -4,13 +4,13 @@ import 'package:stackwallet/providers/exchange/trade_note_service_provider.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/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class EditTradeNoteView extends ConsumerStatefulWidget { const EditTradeNoteView({ Key? key, @@ -47,105 +47,109 @@ class _EditNoteViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( + return Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit trade note", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit trade note", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _noteController, - style: STextStyles.field(context), - focusNode: noteFieldFocusNode, - onChanged: (_) => setState(() {}), - decoration: standardInputDecoration( - "Note", - noteFieldFocusNode, - context, - ).copyWith( - suffixIcon: _noteController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _noteController.text = ""; - }); - }, - ), - ], + body: Padding( + padding: const EdgeInsets.all(12), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _noteController, + style: STextStyles.field(context), + focusNode: noteFieldFocusNode, + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Note", + noteFieldFocusNode, + context, + ).copyWith( + suffixIcon: _noteController.text.isNotEmpty + ? Padding( + padding: + const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _noteController.text = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), - ), - const Spacer(), - TextButton( - onPressed: () async { - await ref.read(tradeNoteServiceProvider).set( - tradeId: widget.tradeId, - note: _noteController.text, - ); - if (mounted) { - Navigator.of(context).pop(); - } - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Save", - style: STextStyles.button(context), - ), - ) - ], + const Spacer(), + TextButton( + onPressed: () async { + await ref.read(tradeNoteServiceProvider).set( + tradeId: widget.tradeId, + note: _noteController.text, + ); + if (mounted) { + Navigator.of(context).pop(); + } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Save", + style: STextStyles.button(context), + ), + ) + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart index 779d99306..fdc1e027a 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; @@ -124,32 +125,35 @@ class _FixedRateMarketPairCoinSelectionViewState return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a coin to exchange", + style: STextStyles.pageTitleH2(context), + ), ), - title: Text( - "Choose a coin to exchange", - style: STextStyles.pageTitleH2(context), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, ), ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: child, - ), ); }, child: Column( diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart index eb7a99299..1a82e5ec5 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; @@ -80,32 +81,35 @@ class _FloatingRateCurrencySelectionViewState return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a coin to exchange", + style: STextStyles.pageTitleH2(context), + ), ), - title: Text( - "Choose a coin to exchange", - style: STextStyles.pageTitleH2(context), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, ), ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: child, - ), ); }, child: Column( diff --git a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart index 95f086003..b51bccc55 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/utilities/clipboard_interface.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'; @@ -39,166 +40,169 @@ class _Step1ViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Exchange", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 0, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Confirm amount", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Network fees and other exchange charges are included in the rate.", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 24, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "You send", - style: STextStyles.itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension()! - .infoItemText), - ), - Text( - "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .infoItemText), - ), - ], + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 0, + width: width, ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "You receive", - style: STextStyles.itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension()! - .infoItemText), - ), - Text( - "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .infoItemText), - ), - ], + const SizedBox( + height: 14, ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - model.rateType == ExchangeRateType.estimated - ? "Estimated rate" - : "Fixed rate", - style: - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .infoItemLabel, + Text( + "Confirm amount", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 24, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "You send", + style: STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemText), ), - ), - Text( - model.rateInfo, - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .infoItemText), - ), - ], + Text( + "${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemText), + ), + ], + ), ), - ), - const SizedBox( - height: 12, - ), - const Spacer(), - TextButton( - onPressed: () { - Navigator.of(context).pushNamed(Step2View.routeName, - arguments: model); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Next", - style: STextStyles.button(context), + const SizedBox( + height: 12, ), - ), - ], + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "You receive", + style: STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemText), + ), + Text( + "~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemText), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + model.rateType == ExchangeRateType.estimated + ? "Estimated rate" + : "Fixed rate", + style: STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ), + Text( + model.rateInfo, + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .infoItemText), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + const Spacer(), + TextButton( + onPressed: () { + Navigator.of(context).pushNamed( + Step2View.routeName, + arguments: model); + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Next", + style: STextStyles.button(context), + ), + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart index 030f91cb7..d943adcfa 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart @@ -16,6 +16,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.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/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -122,188 +123,285 @@ class _Step2ViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Exchange", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 1, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Exchange details", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Enter your recipient and refund addresses", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 24, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Recipient Wallet", - style: STextStyles.smallMed12(context), - ), - if (isStackCoin(model.receiveTicker)) - BlueTextButton( - text: "Choose from stack", - onTap: () { - try { - final coin = coinFromTickerCaseInsensitive( - model.receiveTicker, - ); - Navigator.of(context) - .pushNamed( - ChooseFromStackView.routeName, - arguments: coin, - ) - .then((value) async { - if (value is String) { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(value); - - _toController.text = manager.walletName; - model.recipientAddress = await manager - .currentReceivingAddress; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController.text.isNotEmpty; - }); - } - }); - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Info); - } - }, - ), - ], - ), - const SizedBox( - height: 4, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 1, + width: width, ), - child: TextField( - onTap: () {}, - key: const Key( - "recipientExchangeStep2ViewAddressFieldKey"), - controller: _toController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - focusNode: _toFocusNode, - style: STextStyles.field(context), - onChanged: (value) { - setState(() {}); - }, - decoration: standardInputDecoration( - "Enter the ${model.receiveTicker.toUpperCase()} payout address", - _toFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, + const SizedBox( + height: 14, + ), + Text( + "Exchange details", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Enter your recipient and refund addresses", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 24, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recipient Wallet", + style: STextStyles.smallMed12(context), ), - suffixIcon: Padding( - padding: _toController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _toController.text.isNotEmpty - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - _toController.text = ""; - model.recipientAddress = - _toController.text; + if (isStackCoin(model.receiveTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: () { + try { + final coin = + coinFromTickerCaseInsensitive( + model.receiveTicker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = ref + .read( + walletsChangeNotifierProvider) + .getManager(value); + _toController.text = + manager.walletName; + model.recipientAddress = await manager + .currentReceivingAddress; + + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } + }); + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Info); + } + }, + ), + ], + ), + const SizedBox( + height: 4, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + onTap: () {}, + key: const Key( + "recipientExchangeStep2ViewAddressFieldKey"), + controller: _toController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + focusNode: _toFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() {}); + }, + decoration: standardInputDecoration( + "Enter the ${model.receiveTicker.toUpperCase()} payout address", + _toFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _toController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _toController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _toController.text = ""; + model.recipientAddress = + _toController.text; + + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = + data.text!.trim(); + + _toController.text = + content; + model.recipientAddress = + _toController.text; + + setState(() { + enableNext = _toController + .text + .isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } + }, + child: + _toController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_toController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = true; + Navigator.of(context) + .pushNamed( + AddressBookView.routeName, + ) + .then((_) { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = false; + + final address = ref + .read( + exchangeFromAddressBookAddressStateProvider + .state) + .state; + if (address.isNotEmpty) { + _toController.text = address; + model.recipientAddress = + _toController.text; + ref + .read( + exchangeFromAddressBookAddressStateProvider + .state) + .state = ""; + } setState(() { enableNext = _toController .text.isNotEmpty && _refundController .text.isNotEmpty; }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - final content = - data.text!.trim(); + }); + }, + child: const AddressBookIcon(), + ), + if (_toController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewScanQrButtonKey"), + onTap: () async { + try { + final qrResult = + await scanner.scan(); - _toController.text = content; + final results = + AddressUtils.parseUri( + qrResult.rawContent); + if (results.isNotEmpty) { + // auto fill address + _toController.text = + results["address"] ?? ""; + model.recipientAddress = + _toController.text; + + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } else { + _toController.text = + qrResult.rawContent; model.recipientAddress = _toController.text; @@ -314,248 +412,247 @@ class _Step2ViewState extends ConsumerState { .text.isNotEmpty; }); } - }, - child: _toController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (_toController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewAddressBookButtonKey"), - onTap: () { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = true; - Navigator.of(context) - .pushNamed( - AddressBookView.routeName, - ) - .then((_) { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = false; - - final address = ref - .read( - exchangeFromAddressBookAddressStateProvider - .state) - .state; - if (address.isNotEmpty) { - _toController.text = address; - model.recipientAddress = - _toController.text; - ref - .read( - exchangeFromAddressBookAddressStateProvider - .state) - .state = ""; + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - }); - }, - child: const AddressBookIcon(), - ), - if (_toController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewScanQrButtonKey"), - onTap: () async { - try { - final qrResult = - await scanner.scan(); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - if (results.isNotEmpty) { - // auto fill address - _toController.text = - results["address"] ?? ""; - model.recipientAddress = - _toController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } else { - _toController.text = - qrResult.rawContent; - model.recipientAddress = - _toController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], + }, + child: const QrCodeIcon(), + ), + ], + ), ), ), ), ), ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Text( - "This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.", - style: STextStyles.label(context), + const SizedBox( + height: 6, ), - ), - const SizedBox( - height: 24, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Refund Wallet (required)", - style: STextStyles.smallMed12(context), + RoundedWhiteContainer( + child: Text( + "This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.", + style: STextStyles.label(context), ), - if (isStackCoin(model.sendTicker)) - BlueTextButton( - text: "Choose from stack", - onTap: () { - try { - final coin = coinFromTickerCaseInsensitive( - model.sendTicker, - ); - Navigator.of(context) - .pushNamed( - ChooseFromStackView.routeName, - arguments: coin, - ) - .then((value) async { - if (value is String) { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(value); + ), + const SizedBox( + height: 24, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Refund Wallet (required)", + style: STextStyles.smallMed12(context), + ), + if (isStackCoin(model.sendTicker)) + BlueTextButton( + text: "Choose from stack", + onTap: () { + try { + final coin = + coinFromTickerCaseInsensitive( + model.sendTicker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = ref + .read( + walletsChangeNotifierProvider) + .getManager(value); - _refundController.text = - manager.walletName; - model.refundAddress = await manager - .currentReceivingAddress; - } - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController.text.isNotEmpty; + _refundController.text = + manager.walletName; + model.refundAddress = await manager + .currentReceivingAddress; + } + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController.text.isNotEmpty; + }); }); - }); - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Info); - } - }, - ), - ], - ), - const SizedBox( - height: 4, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Info); + } + }, + ), + ], ), - child: TextField( - key: const Key( - "refundExchangeStep2ViewAddressFieldKey"), - controller: _refundController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, + const SizedBox( + height: 4, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - focusNode: _refundFocusNode, - style: STextStyles.field(context), - onChanged: (value) { - setState(() {}); - }, - decoration: standardInputDecoration( - "Enter ${model.sendTicker.toUpperCase()} refund address", - _refundFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, + child: TextField( + key: const Key( + "refundExchangeStep2ViewAddressFieldKey"), + controller: _refundController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - suffixIcon: Padding( - padding: _refundController.text.isEmpty - ? const EdgeInsets.only(right: 16) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _refundController.text.isNotEmpty - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - _refundController.text = ""; - model.refundAddress = - _refundController.text; + focusNode: _refundFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + setState(() {}); + }, + decoration: standardInputDecoration( + "Enter ${model.sendTicker.toUpperCase()} refund address", + _refundFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _refundController.text.isEmpty + ? const EdgeInsets.only(right: 16) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _refundController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _refundController.text = ""; + model.refundAddress = + _refundController.text; + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + final content = + data.text!.trim(); + + _refundController.text = + content; + model.refundAddress = + _refundController.text; + + setState(() { + enableNext = _toController + .text + .isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } + }, + child: _refundController + .text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_refundController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = true; + Navigator.of(context) + .pushNamed( + AddressBookView.routeName, + ) + .then((_) { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = false; + final address = ref + .read( + exchangeFromAddressBookAddressStateProvider + .state) + .state; + if (address.isNotEmpty) { + _refundController.text = + address; + model.refundAddress = + _refundController.text; + } setState(() { enableNext = _toController .text.isNotEmpty && _refundController .text.isNotEmpty; }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - final content = - data.text!.trim(); + }); + }, + child: const AddressBookIcon(), + ), + if (_refundController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewScanQrButtonKey"), + onTap: () async { + try { + final qrResult = + await scanner.scan(); + final results = + AddressUtils.parseUri( + qrResult.rawContent); + if (results.isNotEmpty) { + // auto fill address _refundController.text = - content; + results["address"] ?? ""; + model.refundAddress = + _refundController.text; + + setState(() { + enableNext = _toController + .text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } else { + _refundController.text = + qrResult.rawContent; model.refundAddress = _refundController.text; @@ -566,162 +663,78 @@ class _Step2ViewState extends ConsumerState { .text.isNotEmpty; }); } - }, - child: - _refundController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (_refundController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewAddressBookButtonKey"), - onTap: () { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = true; - Navigator.of(context) - .pushNamed( - AddressBookView.routeName, - ) - .then((_) { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = false; - final address = ref - .read( - exchangeFromAddressBookAddressStateProvider - .state) - .state; - if (address.isNotEmpty) { - _refundController.text = - address; - model.refundAddress = - _refundController.text; + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - }); - }, - child: const AddressBookIcon(), - ), - if (_refundController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewScanQrButtonKey"), - onTap: () async { - try { - final qrResult = - await scanner.scan(); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - if (results.isNotEmpty) { - // auto fill address - _refundController.text = - results["address"] ?? ""; - model.refundAddress = - _refundController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } else { - _refundController.text = - qrResult.rawContent; - model.refundAddress = - _refundController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], + }, + child: const QrCodeIcon(), + ), + ], + ), ), ), ), ), ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Text( - "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", - style: STextStyles.label(context), + const SizedBox( + height: 6, ), - ), - const Spacer(), - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + RoundedWhiteContainer( + child: Text( + "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", + style: STextStyles.label(context), + ), + ), + const Spacer(), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), ), ), ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( - label: "Next", - enabled: enableNext, - onPressed: () { - Navigator.of(context).pushNamed( - Step3View.routeName, - arguments: model, - ); - }, + const SizedBox( + width: 16, ), - ), - ], - ), - ], + Expanded( + child: PrimaryButton( + label: "Next", + enabled: enableNext, + onPressed: () { + Navigator.of(context).pushNamed( + Step3View.routeName, + arguments: model, + ); + }, + ), + ), + ], + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index 0f03d4216..467a3b9e7 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.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/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -50,290 +51,295 @@ class _Step3ViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Exchange", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 2, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Confirm exchange details", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 24, - ), - RoundedWhiteContainer( - child: Row( - children: [ - Text( - "You send", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Text( - "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ) - ], + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 2, + width: width, ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Row( - children: [ - Text( - "You receive", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Text( - "${model.receiveAmount.toString()} ${model.receiveTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ) - ], + const SizedBox( + height: 14, ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Row( - children: [ - Text( - "Estimated rate", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Text( - model.rateInfo, - style: STextStyles.itemSubtitle12(context), - ) - ], + Text( + "Confirm exchange details", + style: STextStyles.pageTitleH1(context), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Recipient ${model.receiveTicker.toUpperCase()} address", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - Text( - model.recipientAddress!, - style: STextStyles.itemSubtitle12(context), - ) - ], + const SizedBox( + height: 24, ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Refund ${model.sendTicker.toUpperCase()} address", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - Text( - model.refundAddress!, - style: STextStyles.itemSubtitle12(context), - ) - ], + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "You send", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Text( + "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ) + ], + ), ), - ), - const SizedBox( - height: 8, - ), - const Spacer(), - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "You receive", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Text( + "${model.receiveAmount.toString()} ${model.receiveTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ) + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Estimated rate", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Text( + model.rateInfo, + style: STextStyles.itemSubtitle12(context), + ) + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Recipient ${model.receiveTicker.toUpperCase()} address", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + Text( + model.recipientAddress!, + style: STextStyles.itemSubtitle12(context), + ) + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Refund ${model.sendTicker.toUpperCase()} address", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + Text( + model.refundAddress!, + style: STextStyles.itemSubtitle12(context), + ) + ], + ), + ), + const SizedBox( + height: 8, + ), + const Spacer(), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), ), ), ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: TextButton( - onPressed: () async { - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Creating a trade", - eventBus: null, + const SizedBox( + width: 16, + ), + Expanded( + child: TextButton( + onPressed: () async { + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Creating a trade", + eventBus: null, + ), ), ), ), - ), - ); + ); - final ExchangeResponse response = - await ref - .read(exchangeProvider) - .createTrade( - from: model.sendTicker, - to: model.receiveTicker, - fixedRate: model.rateType != - ExchangeRateType.estimated, - amount: model.reversed - ? model.receiveAmount - : model.sendAmount, - addressTo: model.recipientAddress!, - extraId: null, - addressRefund: model.refundAddress!, - refundExtraId: "", - rateId: model.rateId, - reversed: model.reversed, - ); + final ExchangeResponse response = + await ref + .read(exchangeProvider) + .createTrade( + from: model.sendTicker, + to: model.receiveTicker, + fixedRate: model.rateType != + ExchangeRateType.estimated, + amount: model.reversed + ? model.receiveAmount + : model.sendAmount, + addressTo: + model.recipientAddress!, + extraId: null, + addressRefund: + model.refundAddress!, + refundExtraId: "", + rateId: model.rateId, + reversed: model.reversed, + ); + + if (response.value == null) { + if (mounted) { + Navigator.of(context).pop(); + } + + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to create trade", + message: + response.exception?.toString(), + ), + )); + return; + } + + // save trade to hive + await ref.read(tradesServiceProvider).add( + trade: response.value!, + shouldNotifyListeners: true, + ); + + String status = response.value!.status; + + model.trade = response.value!; + + // extra info if status is waiting + if (status == "Waiting") { + status += " for deposit"; + } - if (response.value == null) { if (mounted) { Navigator.of(context).pop(); } - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to create trade", - message: response.exception?.toString(), - ), + unawaited(NotificationApi.showNotification( + changeNowId: model.trade!.tradeId, + title: status, + body: "Trade ID ${model.trade!.tradeId}", + walletId: "", + iconAssetName: Assets.svg.arrowRotate, + date: model.trade!.timestamp, + shouldWatchForUpdates: true, + coinName: "coinName", )); - return; - } - // save trade to hive - await ref.read(tradesServiceProvider).add( - trade: response.value!, - shouldNotifyListeners: true, - ); - - String status = response.value!.status; - - model.trade = response.value!; - - // extra info if status is waiting - if (status == "Waiting") { - status += " for deposit"; - } - - if (mounted) { - Navigator.of(context).pop(); - } - - unawaited(NotificationApi.showNotification( - changeNowId: model.trade!.tradeId, - title: status, - body: "Trade ID ${model.trade!.tradeId}", - walletId: "", - iconAssetName: Assets.svg.arrowRotate, - date: model.trade!.timestamp, - shouldWatchForUpdates: true, - coinName: "coinName", - )); - - if (mounted) { - unawaited(Navigator.of(context).pushNamed( - Step4View.routeName, - arguments: model, - )); - } - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Next", - style: STextStyles.button(context), + if (mounted) { + unawaited(Navigator.of(context).pushNamed( + Step4View.routeName, + arguments: model, + )); + } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Next", + style: STextStyles.button(context), + ), ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index f5975a277..101ac637f 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -21,6 +21,7 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_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/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -106,539 +107,548 @@ class _Step4ViewState extends ConsumerState { Widget build(BuildContext context) { final bool isWalletCoin = _isWalletCoinAndHasWallet(model.trade!.payInCurrency, ref); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Exchange", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 3, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Send ${model.sendTicker.toUpperCase()} to the address below", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 12, - ), - RoundedContainer( - color: Theme.of(context) - .extension()! - .warningBackground, - child: RichText( - text: TextSpan( - text: - "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", - style: STextStyles.label700(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 3, + width: width, + ), + const SizedBox( + height: 14, + ), + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 12, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .warningBackground, + child: RichText( + text: TextSpan( + text: + "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + children: [ + TextSpan( + text: + "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + ), + ], ), + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextSpan( - text: - "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", - style: STextStyles.label(context).copyWith( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.itemSubtitle(context), + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: model.sendAmount.toString()); + await clipboard.setData(data); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + )); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 10, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Send ${model.sendTicker.toUpperCase()} to this address", + style: STextStyles.itemSubtitle(context), + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: model.trade!.payInAddress); + await clipboard.setData(data); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + )); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 10, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + model.trade!.payInAddress, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Trade ID", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Row( + children: [ + Text( + model.trade!.tradeId, + style: + STextStyles.itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: model.trade!.tradeId); + await clipboard.setData(data); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + )); + }, + child: SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 12, + ), + ) + ], + ) + ], + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.itemSubtitle(context), + ), + Text( + _statusString, + style: STextStyles.itemSubtitle(context) + .copyWith( color: Theme.of(context) .extension()! - .warningForeground, + .colorForStatus(_statusString), ), ), ], ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Amount", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.sendAmount.toString()); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 10, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - Text( - "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Send ${model.sendTicker.toUpperCase()} to this address", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.trade!.payInAddress); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 10, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - Text( - model.trade!.payInAddress, - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Row( - children: [ - Text( - "Trade ID", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Row( - children: [ - Text( - model.trade!.tradeId, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - width: 10, - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.trade!.tradeId); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 12, - ), - ) - ], - ) - ], - ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Status", - style: STextStyles.itemSubtitle(context), - ), - Text( - _statusString, - style: - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .colorForStatus(_statusString), - ), - ), - ], - ), - ), - const Spacer(), - const SizedBox( - height: 12, - ), - TextButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: true, - builder: (_) { - return StackDialogBase( - child: Column( - children: [ - const SizedBox( - height: 8, - ), - Center( - child: Text( - "Send ${model.sendTicker} to this address", - style: - STextStyles.pageTitleH2(context), - ), - ), - const SizedBox( - height: 24, - ), - Center( - child: QrImage( - // TODO: grab coin uri scheme from somewhere - // data: "${coin.uriScheme}:$receivingAddress", - data: model.trade!.payInAddress, - size: MediaQuery.of(context) - .size - .width / - 2, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - const SizedBox( - height: 24, - ), - Row( - children: [ - const Spacer(), - Expanded( - child: TextButton( - onPressed: () => - Navigator.of(context).pop(), - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: - STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), - ), - ), - ], - ) - ], - ), - ); - }, - ); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Show QR Code", - style: STextStyles.button(context), - ), - ), - if (isWalletCoin) + const Spacer(), const SizedBox( height: 12, ), - if (isWalletCoin) - Builder( - builder: (context) { - String buttonTitle = "Send from Stack Wallet"; - - final tuple = ref - .read(exchangeSendFromWalletIdStateProvider - .state) - .state; - if (tuple != null && - model.sendTicker.toLowerCase() == - tuple.item2.ticker.toLowerCase()) { - final walletName = ref - .read(walletsChangeNotifierProvider) - .getManager(tuple.item1) - .walletName; - buttonTitle = "Send from $walletName"; - } - - return TextButton( - onPressed: tuple != null && - model.sendTicker.toLowerCase() == - tuple.item2.ticker.toLowerCase() - ? () async { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(tuple.item1); - - final amount = - Format.decimalAmountToSatoshis( - model.sendAmount, manager.coin); - final address = - model.trade!.payInAddress; - - try { - bool wasCancelled = false; - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; - - Navigator.of(context).pop(); - }, - ); - }, - )); - - final txData = - await manager.prepareSend( - address: address, - satoshiAmount: amount, - args: { - "feeRate": FeeRateType.average, - // ref.read(feeRateTypeStateProvider) - }, - ); - - if (!wasCancelled) { - // pop building dialog - - if (mounted) { - Navigator.of(context).pop(); - } - - txData["note"] = - "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange"; - txData["address"] = address; - - if (mounted) { - unawaited( - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (_) => - ConfirmChangeNowSendView( - transactionInfo: txData, - walletId: tuple.item1, - routeOnSuccessName: - HomeView.routeName, - trade: model.trade!, - ), - settings: const RouteSettings( - name: - ConfirmChangeNowSendView - .routeName, - ), - ), - )); - } - } - } catch (e) { - // if (mounted) { - // pop building dialog - Navigator.of(context).pop(); - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Ok", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .buttonTextSecondary, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - }, - )); - // } - } - } - : () { - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (BuildContext context) { - return SendFromView( - coin: - coinFromTickerCaseInsensitive( - model.trade! - .payInCurrency), - amount: model.sendAmount, - address: - model.trade!.payInAddress, - trade: model.trade!, - ); - }, - settings: const RouteSettings( - name: SendFromView.routeName, - ), + TextButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (_) { + return StackDialogBase( + child: Column( + children: [ + const SizedBox( + height: 8, + ), + Center( + child: Text( + "Send ${model.sendTicker} to this address", + style: STextStyles.pageTitleH2( + context), ), - ); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - buttonTitle, - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), + ), + const SizedBox( + height: 24, + ), + Center( + child: QrImage( + // TODO: grab coin uri scheme from somewhere + // data: "${coin.uriScheme}:$receivingAddress", + data: model.trade!.payInAddress, + size: MediaQuery.of(context) + .size + .width / + 2, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: TextButton( + onPressed: () => + Navigator.of(context).pop(), + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.button( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .buttonTextSecondary, + ), + ), + ), + ), + ], + ) + ], + ), + ); + }, ); }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Show QR Code", + style: STextStyles.button(context), + ), ), - ], + if (isWalletCoin) + const SizedBox( + height: 12, + ), + if (isWalletCoin) + Builder( + builder: (context) { + String buttonTitle = "Send from Stack Wallet"; + + final tuple = ref + .read(exchangeSendFromWalletIdStateProvider + .state) + .state; + if (tuple != null && + model.sendTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase()) { + final walletName = ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .walletName; + buttonTitle = "Send from $walletName"; + } + + return TextButton( + onPressed: tuple != null && + model.sendTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase() + ? () async { + final manager = ref + .read( + walletsChangeNotifierProvider) + .getManager(tuple.item1); + + final amount = + Format.decimalAmountToSatoshis( + model.sendAmount, + manager.coin); + final address = + model.trade!.payInAddress; + + try { + bool wasCancelled = false; + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ); + }, + )); + + final txData = + await manager.prepareSend( + address: address, + satoshiAmount: amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + + if (!wasCancelled) { + // pop building dialog + + if (mounted) { + Navigator.of(context).pop(); + } + + txData["note"] = + "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange"; + txData["address"] = address; + + if (mounted) { + unawaited( + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator + .useMaterialPageRoute, + builder: (_) => + ConfirmChangeNowSendView( + transactionInfo: txData, + walletId: tuple.item1, + routeOnSuccessName: + HomeView.routeName, + trade: model.trade!, + ), + settings: + const RouteSettings( + name: + ConfirmChangeNowSendView + .routeName, + ), + ), + )); + } + } + } catch (e) { + // if (mounted) { + // pop building dialog + Navigator.of(context).pop(); + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension< + StackColors>()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Ok", + style: STextStyles.button( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context) + .pop(); + }, + ), + ); + }, + )); + // } + } + } + : () { + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator + .useMaterialPageRoute, + builder: (BuildContext context) { + return SendFromView( + coin: + coinFromTickerCaseInsensitive( + model.trade! + .payInCurrency), + amount: model.sendAmount, + address: + model.trade!.payInAddress, + trade: model.trade!, + ); + }, + settings: const RouteSettings( + name: SendFromView.routeName, + ), + ), + ); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + buttonTitle, + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ); + }, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index f13971a67..6a7fe5285 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -22,6 +22,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -85,24 +86,26 @@ class _SendFromViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Send from", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Send from", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, ), ), - body: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), ); }, child: ConditionalParent( diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 0b7f4b502..5679d5be5 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -28,6 +28,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -167,27 +168,30 @@ class _TradeDetailsViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( + builder: (child) => Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Trade details", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Trade details", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index c816d4fe8..915fb33ba 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/utilities/enums/coin_enum.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'; class WalletInitiatedExchangeView extends ConsumerStatefulWidget { @@ -47,75 +48,77 @@ class _WalletInitiatedExchangeViewState Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Exchange", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 0, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Exchange amount", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Network fees and other exchange charges are included in the rate.", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 24, - ), - ExchangeForm( - walletId: walletId, - coin: coin, - ), - ], + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 0, + width: width, + ), + const SizedBox( + height: 14, + ), + Text( + "Exchange amount", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 24, + ), + ExchangeForm( + walletId: walletId, + coin: coin, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 33744570b..5f41bfb16 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -20,6 +20,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/logger.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/stack_dialog.dart'; @@ -141,129 +142,138 @@ class _HomeViewState extends ConsumerState { debugPrint("BUILD: $runtimeType"); return WillPopScope( onWillPop: _onWillPop, - child: Scaffold( - key: _key, - appBar: AppBar( - automaticallyImplyLeading: false, - title: Row( - children: [ - GestureDetector( - onTap: _hiddenOptions, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 24, - height: 24, - ), - ), - const SizedBox( - width: 16, - ), - Text( - "My Stack", - style: STextStyles.navBarTitle(context), - ) - ], - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletsViewAlertsButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? Assets.svg.bellNew(context) - : Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? null - : Theme.of(context) - .extension()! - .topNavIconPrimary, + child: Background( + child: Scaffold( + backgroundColor: Colors.transparent, + key: _key, + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + title: Row( + children: [ + GestureDetector( + onTap: _hiddenOptions, + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 24, + height: 24, ), - onPressed: () { - // reset unread state - ref.refresh(unreadNotificationsStateProvider); - - Navigator.of(context) - .pushNamed(NotificationsView.routeName) - .then((_) { - final Set unreadNotificationIds = ref - .read(unreadNotificationsStateProvider.state) - .state; - if (unreadNotificationIds.isEmpty) return; - - List> futures = []; - for (int i = 0; - i < unreadNotificationIds.length - 1; - i++) { - futures.add(ref.read(notificationsProvider).markAsRead( - unreadNotificationIds.elementAt(i), false)); - } - - // wait for multiple to update if any - Future.wait(futures).then((_) { - // only notify listeners once - ref - .read(notificationsProvider) - .markAsRead(unreadNotificationIds.last, true); - }); - }); - }, ), - ), + const SizedBox( + width: 16, + ), + Text( + "My Stack", + style: STextStyles.navBarTitle(context), + ) + ], ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletsViewSettingsButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.gear, + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("walletsViewAlertsButton"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .topNavIconPrimary, - width: 20, - height: 20, + .backgroundAppBar, + icon: SvgPicture.asset( + ref.watch(notificationsProvider + .select((value) => value.hasUnreadNotifications)) + ? Assets.svg.bellNew(context) + : Assets.svg.bell, + width: 20, + height: 20, + color: ref.watch(notificationsProvider + .select((value) => value.hasUnreadNotifications)) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // reset unread state + ref.refresh(unreadNotificationsStateProvider); + + Navigator.of(context) + .pushNamed(NotificationsView.routeName) + .then((_) { + final Set unreadNotificationIds = ref + .read(unreadNotificationsStateProvider.state) + .state; + if (unreadNotificationIds.isEmpty) return; + + List> futures = []; + for (int i = 0; + i < unreadNotificationIds.length - 1; + i++) { + futures.add(ref + .read(notificationsProvider) + .markAsRead( + unreadNotificationIds.elementAt(i), false)); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.last, true); + }); + }); + }, ), - onPressed: () { - debugPrint("main view settings tapped"); - Navigator.of(context) - .pushNamed(GlobalSettingsView.routeName); - }, ), ), - ), - ], - ), - body: Container( - color: Theme.of(context).extension()!.background, - child: Column( + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("walletsViewSettingsButton"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .backgroundAppBar, + icon: SvgPicture.asset( + Assets.svg.gear, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + width: 20, + height: 20, + ), + onPressed: () { + debugPrint("main view settings tapped"); + Navigator.of(context) + .pushNamed(GlobalSettingsView.routeName); + }, + ), + ), + ), + ], + ), + body: Column( children: [ if (Constants.enableExchange) Container( decoration: BoxDecoration( - color: - Theme.of(context).extension()!.background, + color: Theme.of(context) + .extension()! + .backgroundAppBar, boxShadow: [ Theme.of(context) .extension()! diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index 494f23973..be0a9b82a 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -3,14 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:tuple/tuple.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stackwallet/utilities/prefs.dart'; - class IntroView extends StatefulWidget { const IntroView({Key? key}) : super(key: key); @@ -32,118 +31,120 @@ class _IntroViewState extends State { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType "); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - body: Center( - child: !isDesktop - ? Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer( - flex: 2, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 300, - ), - child: Image( - image: AssetImage( - Assets.png.stack, - ), - ), - ), - ), - const Spacer( - flex: 1, - ), - AppNameText( - isDesktop: isDesktop, - ), - const SizedBox( - height: 8, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 48, - ), - child: IntroAboutText( - isDesktop: isDesktop, - ), - ), - const Spacer( - flex: 4, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - ), - child: PrivacyAndTOSText( - isDesktop: isDesktop, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - child: Row( - children: [ - Expanded( - child: GetStartedButton( - isDesktop: isDesktop, - ), - ), - ], - ), - ), - ], - ) - : SizedBox( - width: 350, - height: 540, - child: Column( + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + body: Center( + child: !isDesktop + ? Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Spacer( flex: 2, ), - SizedBox( - width: 130, - height: 130, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: Image( + image: AssetImage( + Assets.png.stack, + ), + ), ), ), const Spacer( - flex: 42, + flex: 1, ), AppNameText( isDesktop: isDesktop, ), - const Spacer( - flex: 24, + const SizedBox( + height: 8, ), - IntroAboutText( - isDesktop: isDesktop, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 48, + ), + child: IntroAboutText( + isDesktop: isDesktop, + ), ), const Spacer( - flex: 42, + flex: 4, ), - GetStartedButton( - isDesktop: isDesktop, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: PrivacyAndTOSText( + isDesktop: isDesktop, + ), ), - const Spacer( - flex: 65, - ), - PrivacyAndTOSText( - isDesktop: isDesktop, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Row( + children: [ + Expanded( + child: GetStartedButton( + isDesktop: isDesktop, + ), + ), + ], + ), ), ], + ) + : SizedBox( + width: 350, + height: 540, + child: Column( + children: [ + const Spacer( + flex: 2, + ), + SizedBox( + width: 130, + height: 130, + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + ), + ), + const Spacer( + flex: 42, + ), + AppNameText( + isDesktop: isDesktop, + ), + const Spacer( + flex: 24, + ), + IntroAboutText( + isDesktop: isDesktop, + ), + const Spacer( + flex: 42, + ), + GetStartedButton( + isDesktop: isDesktop, + ), + const Spacer( + flex: 65, + ), + PrivacyAndTOSText( + isDesktop: isDesktop, + ), + ], + ), ), - ), + ), ), ); } diff --git a/lib/pages/loading_view.dart b/lib/pages/loading_view.dart index c252913df..b7db1aa58 100644 --- a/lib/pages/loading_view.dart +++ b/lib/pages/loading_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; class LoadingView extends StatelessWidget { const LoadingView({Key? key}) : super(key: key); @@ -11,25 +12,27 @@ class LoadingView extends StatelessWidget { @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - body: Container( - color: Theme.of(context).extension()!.background, - child: Center( - child: SizedBox( - width: min(size.width, size.height) * 0.5, - child: Lottie.asset( - Assets.lottie.test2, - animate: true, - repeat: true, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + body: Container( + color: Theme.of(context).extension()!.background, + child: Center( + child: SizedBox( + width: min(size.width, size.height) * 0.5, + child: Lottie.asset( + Assets.lottie.test2, + animate: true, + repeat: true, + ), ), + // child: Image( + // image: AssetImage( + // Assets.png.splash, + // ), + // width: MediaQuery.of(context).size.width * 0.5, + // ), ), - // child: Image( - // image: AssetImage( - // Assets.png.splash, - // ), - // width: MediaQuery.of(context).size.width * 0.5, - // ), ), ), ); diff --git a/lib/pages/notification_views/notifications_view.dart b/lib/pages/notification_views/notifications_view.dart index a034a34e4..a574e083d 100644 --- a/lib/pages/notification_views/notifications_view.dart +++ b/lib/pages/notification_views/notifications_view.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.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'; @@ -43,66 +44,68 @@ class _NotificationsViewState extends ConsumerState { .where((element) => element.walletId == widget.walletId) .toList(growable: false); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - title: Text( - "Notifications", - style: STextStyles.navBarTitle(context), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + title: Text( + "Notifications", + style: STextStyles.navBarTitle(context), + ), + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), ), - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: notifications.isNotEmpty - ? Column( - children: [ - Expanded( - child: ListView.builder( - shrinkWrap: true, - itemCount: notifications.length, - itemBuilder: (builderContext, index) { - final notification = notifications[index]; - if (notification.read == false) { - ref - .read(unreadNotificationsStateProvider.state) - .state - .add(notification.id); - } - return Padding( - padding: const EdgeInsets.all(4), - child: NotificationCard( - notification: notifications[index], - ), - ); - }, + body: Padding( + padding: const EdgeInsets.all(12), + child: notifications.isNotEmpty + ? Column( + children: [ + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: notifications.length, + itemBuilder: (builderContext, index) { + final notification = notifications[index]; + if (notification.read == false) { + ref + .read(unreadNotificationsStateProvider.state) + .state + .add(notification.id); + } + return Padding( + padding: const EdgeInsets.all(4), + child: NotificationCard( + notification: notifications[index], + ), + ); + }, + ), ), - ), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Center( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "Notifications will appear here", - style: STextStyles.itemSubtitle(context), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Center( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "Notifications will appear here", + style: STextStyles.itemSubtitle(context), + ), ), ), ), - ), - ) - ], - ), + ) + ], + ), + ), ), ); } diff --git a/lib/pages/pinpad_views/create_pin_view.dart b/lib/pages/pinpad_views/create_pin_view.dart index 766f10fa5..3180689c2 100644 --- a/lib/pages/pinpad_views/create_pin_view.dart +++ b/lib/pages/pinpad_views/create_pin_view.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.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/custom_pin_put/custom_pin_put.dart'; @@ -76,215 +77,219 @@ class _CreatePinViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(widget.popOnSuccess); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(widget.popOnSuccess); + } + }, + ), ), - ), - body: SafeArea( - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - // page 1 - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Create a PIN", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "This PIN protects access to your wallet.", - style: STextStyles.subtitle(context), - ), - const SizedBox( - height: 36, - ), - CustomPinPut( - fieldsCount: Constants.pinLength, - eachFieldHeight: 12, - eachFieldWidth: 12, - textStyle: STextStyles.label(context).copyWith( - fontSize: 1, + body: SafeArea( + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + // page 1 + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Create a PIN", + style: STextStyles.pageTitleH1(context), ), - focusNode: _pinPutFocusNode1, - controller: _pinPutController1, - useNativeKeyboard: false, - obscureText: "", - inputDecoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - fillColor: - Theme.of(context).extension()!.background, - counterText: "", + const SizedBox( + height: 8, ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, + Text( + "This PIN protects access to your wallet.", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + CustomPinPut( + fieldsCount: Constants.pinLength, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.label(context).copyWith( + fontSize: 1, + ), + focusNode: _pinPutFocusNode1, + controller: _pinPutController1, + useNativeKeyboard: false, + obscureText: "", + inputDecoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Theme.of(context) + .extension()! + .background, + counterText: "", + ), + submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! .infoItemIcons, + border: Border.all( + width: 1, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, - onSubmit: (String pin) { - if (pin.length == Constants.pinLength) { - _pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - ); - } - }, - ), - ], - ), - - // page 2 - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Confirm PIN", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "This PIN protects access to your wallet.", - style: STextStyles.subtitle(context), - ), - const SizedBox( - height: 36, - ), - CustomPinPut( - fieldsCount: Constants.pinLength, - eachFieldHeight: 12, - eachFieldWidth: 12, - textStyle: STextStyles.infoSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle3, - fontSize: 1, - ), - focusNode: _pinPutFocusNode2, - controller: _pinPutController2, - useNativeKeyboard: false, - obscureText: "", - inputDecoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - fillColor: - Theme.of(context).extension()!.background, - counterText: "", - ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, - onSubmit: (String pin) async { - // _onSubmitCount++; - // if (_onSubmitCount - _onSubmitFailCount > 1) return; - - if (_pinPutController1.text == _pinPutController2.text) { - // ask if want to use biometrics - final bool useBiometrics = (Platform.isLinux) - ? false - : await biometrics.authenticate( - cancelButtonText: "SKIP", - localizedReason: - "You can use your fingerprint to unlock the wallet and confirm transactions.", - title: "Enable fingerprint authentication", - ); - - //TODO investigate why this crashes IOS, maybe ios persists securestorage even after an uninstall? - // This should never fail as we are writing a new pin - // assert( - // (await _secureStore.read(key: "stack_pin")) == null); - // possible alternative to the above but it does not guarantee we aren't overwriting a pin - // if (!Platform.isLinux) - // assert((await _secureStore.read(key: "stack_pin")) == - // null); - assert(ref.read(prefsChangeNotifierProvider).hasPin == - false); - - await _secureStore.write(key: "stack_pin", value: pin); - - ref.read(prefsChangeNotifierProvider).useBiometrics = - useBiometrics; - ref.read(prefsChangeNotifierProvider).hasPin = true; - - await Future.delayed( - const Duration(milliseconds: 200)); - - if (mounted) { - if (!widget.popOnSuccess) { - Navigator.of(context).pushNamedAndRemoveUntil( - HomeView.routeName, - (route) => false, - ); - } else { - Navigator.of(context).pop(); - } + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration, + onSubmit: (String pin) { + if (pin.length == Constants.pinLength) { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); } - } else { - // _onSubmitFailCount++; - _pageController.animateTo( - 0, - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - ); + }, + ), + ], + ), - showFloatingFlushBar( - type: FlushBarType.warning, - message: "PIN codes do not match. Try again.", - context: context, - iconAsset: Assets.svg.alertCircle, - ); + // page 2 + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Confirm PIN", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "This PIN protects access to your wallet.", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + CustomPinPut( + fieldsCount: Constants.pinLength, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + fontSize: 1, + ), + focusNode: _pinPutFocusNode2, + controller: _pinPutController2, + useNativeKeyboard: false, + obscureText: "", + inputDecoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Theme.of(context) + .extension()! + .background, + counterText: "", + ), + submittedFieldDecoration: _pinPutDecoration.copyWith( + color: Theme.of(context) + .extension()! + .infoItemIcons, + border: Border.all( + width: 1, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration, + onSubmit: (String pin) async { + // _onSubmitCount++; + // if (_onSubmitCount - _onSubmitFailCount > 1) return; - _pinPutController1.text = ''; - _pinPutController2.text = ''; - } - }, - ), - ], - ), - ], + if (_pinPutController1.text == _pinPutController2.text) { + // ask if want to use biometrics + final bool useBiometrics = (Platform.isLinux) + ? false + : await biometrics.authenticate( + cancelButtonText: "SKIP", + localizedReason: + "You can use your fingerprint to unlock the wallet and confirm transactions.", + title: "Enable fingerprint authentication", + ); + + //TODO investigate why this crashes IOS, maybe ios persists securestorage even after an uninstall? + // This should never fail as we are writing a new pin + // assert( + // (await _secureStore.read(key: "stack_pin")) == null); + // possible alternative to the above but it does not guarantee we aren't overwriting a pin + // if (!Platform.isLinux) + // assert((await _secureStore.read(key: "stack_pin")) == + // null); + assert(ref.read(prefsChangeNotifierProvider).hasPin == + false); + + await _secureStore.write(key: "stack_pin", value: pin); + + ref.read(prefsChangeNotifierProvider).useBiometrics = + useBiometrics; + ref.read(prefsChangeNotifierProvider).hasPin = true; + + await Future.delayed( + const Duration(milliseconds: 200)); + + if (mounted) { + if (!widget.popOnSuccess) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeView.routeName, + (route) => false, + ); + } else { + Navigator.of(context).pop(); + } + } + } else { + // _onSubmitFailCount++; + _pageController.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); + + showFloatingFlushBar( + type: FlushBarType.warning, + message: "PIN codes do not match. Try again.", + context: context, + iconAsset: Assets.svg.alertCircle, + ); + + _pinPutController1.text = ''; + _pinPutController2.text = ''; + } + }, + ), + ], + ), + ], + ), ), ), ); diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 60d317e21..455d5ee3b 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.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/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/shake/shake.dart'; @@ -161,173 +162,177 @@ class _LockscreenViewState extends ConsumerState { late SecureStorageInterface _secureStore; late Biometrics biometrics; - Scaffold get _body => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: widget.showBackButton - ? AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ) - : Container(), - ), - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Shake( - animationDuration: const Duration(milliseconds: 700), - animationRange: 12, - controller: _shakeController, - child: Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Text( - "Enter PIN", - style: STextStyles.pageTitleH1(context), + Widget get _body => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: widget.showBackButton + ? AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ) + : Container(), + ), + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Shake( + animationDuration: const Duration(milliseconds: 700), + animationRange: 12, + controller: _shakeController, + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + "Enter PIN", + style: STextStyles.pageTitleH1(context), + ), ), - ), - const SizedBox( - height: 52, - ), - CustomPinPut( - fieldsCount: Constants.pinLength, - eachFieldHeight: 12, - eachFieldWidth: 12, - textStyle: STextStyles.label(context).copyWith( - fontSize: 1, + const SizedBox( + height: 52, ), - focusNode: _pinFocusNode, - controller: _pinTextController, - useNativeKeyboard: false, - obscureText: "", - inputDecoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - fillColor: Theme.of(context) - .extension()! - .background, - counterText: "", - ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, + CustomPinPut( + fieldsCount: Constants.pinLength, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.label(context).copyWith( + fontSize: 1, + ), + focusNode: _pinFocusNode, + controller: _pinTextController, + useNativeKeyboard: false, + obscureText: "", + inputDecoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Theme.of(context) + .extension()! + .background, + counterText: "", + ), + submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! .infoItemIcons, + border: Border.all( + width: 1, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, - onSubmit: (String pin) async { - _attempts++; + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration, + onSubmit: (String pin) async { + _attempts++; - if (_attempts > maxAttemptsBeforeThrottling) { - _attemptLock = true; - switch (_attempts) { - case 4: - _timeout = const Duration(seconds: 30); - break; + if (_attempts > maxAttemptsBeforeThrottling) { + _attemptLock = true; + switch (_attempts) { + case 4: + _timeout = const Duration(seconds: 30); + break; - case 5: - _timeout = const Duration(seconds: 60); - break; + case 5: + _timeout = const Duration(seconds: 60); + break; - case 6: - _timeout = const Duration(minutes: 5); - break; + case 6: + _timeout = const Duration(minutes: 5); + break; - case 7: - _timeout = const Duration(minutes: 10); - break; + case 7: + _timeout = const Duration(minutes: 10); + break; - case 8: - _timeout = const Duration(minutes: 20); - break; + case 8: + _timeout = const Duration(minutes: 20); + break; - case 9: - _timeout = const Duration(minutes: 30); - break; + case 9: + _timeout = const Duration(minutes: 30); + break; - default: - _timeout = const Duration(minutes: 60); + default: + _timeout = const Duration(minutes: 60); + } + + unawaited( + Future.delayed(_timeout).then((_) { + _attemptLock = false; + _attempts = 0; + })); } - unawaited(Future.delayed(_timeout).then((_) { - _attemptLock = false; - _attempts = 0; - })); - } + if (_attemptLock) { + String prettyTime = ""; + if (_timeout.inSeconds >= 60) { + prettyTime += "${_timeout.inMinutes} minutes"; + } else { + prettyTime += "${_timeout.inSeconds} seconds"; + } - if (_attemptLock) { - String prettyTime = ""; - if (_timeout.inSeconds >= 60) { - prettyTime += "${_timeout.inMinutes} minutes"; + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Incorrect PIN entered too many times. Please wait $prettyTime", + context: context, + iconAsset: Assets.svg.alertCircle, + )); + + await Future.delayed( + const Duration(milliseconds: 100)); + + _pinTextController.text = ''; + + return; + } + + final storedPin = + await _secureStore.read(key: 'stack_pin'); + + if (storedPin == pin) { + await Future.delayed( + const Duration(milliseconds: 200)); + unawaited(_onUnlock()); } else { - prettyTime += "${_timeout.inSeconds} seconds"; + unawaited(_shakeController.shake()); + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Incorrect PIN. Please try again", + context: context, + iconAsset: Assets.svg.alertCircle, + )); + + await Future.delayed( + const Duration(milliseconds: 100)); + + _pinTextController.text = ''; } - - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Incorrect PIN entered too many times. Please wait $prettyTime", - context: context, - iconAsset: Assets.svg.alertCircle, - )); - - await Future.delayed( - const Duration(milliseconds: 100)); - - _pinTextController.text = ''; - - return; - } - - final storedPin = - await _secureStore.read(key: 'stack_pin'); - - if (storedPin == pin) { - await Future.delayed( - const Duration(milliseconds: 200)); - unawaited(_onUnlock()); - } else { - unawaited(_shakeController.shake()); - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Incorrect PIN. Please try again", - context: context, - iconAsset: Assets.svg.alertCircle, - )); - - await Future.delayed( - const Duration(milliseconds: 100)); - - _pinTextController.text = ''; - } - }, - ), - ], + }, + ), + ], + ), ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 05cedb148..46b48bb42 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -23,6 +23,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -305,48 +306,51 @@ class _GenerateUriQrCodeViewState extends State { return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Generate QR code", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Generate QR code", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (buildContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (buildContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), child: Padding( @@ -530,7 +534,7 @@ class _GenerateUriQrCodeViewState extends State { }); } : onGeneratePressed, - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, ), if (isDesktop && didGenerate) Row( @@ -586,6 +590,8 @@ class _GenerateUriQrCodeViewState extends State { if (!isDesktop) SecondaryButton( width: 170, + buttonHeight: + isDesktop ? ButtonHeight.l : null, onPressed: () async { await _capturePng(false); }, @@ -605,7 +611,8 @@ class _GenerateUriQrCodeViewState extends State { ), PrimaryButton( width: 170, - buttonHeight: ButtonHeight.l, + buttonHeight: + isDesktop ? ButtonHeight.l : null, onPressed: () async { // TODO: add save functionality instead of share // save works on linux at the moment diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 50e0ffd5e..1ba00f8ee 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -12,9 +12,9 @@ import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.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/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -115,147 +115,149 @@ class _ReceiveViewState extends ConsumerState { } }); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Receive ${coin.ticker}", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Receive ${coin.ticker}", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - GestureDetector( - onTap: () { - clipboard.setData( - ClipboardData(text: receivingAddress), - ); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - child: RoundedWhiteContainer( - child: Column( - children: [ - Row( - children: [ - Text( - "Your ${coin.ticker} address", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 10, - height: 10, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ], - ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Expanded( - child: Text( - receivingAddress, - style: STextStyles.itemSubtitle12(context), - ), - ), - ], - ), - ], - ), - ), - ), - if (coin != Coin.epicCash) - const SizedBox( - height: 12, - ), - if (coin != Coin.epicCash) - TextButton( - onPressed: generateNewAddress, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Generate new address", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - const SizedBox( - height: 30, - ), - RoundedWhiteContainer( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( + body: Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GestureDetector( + onTap: () { + clipboard.setData( + ClipboardData(text: receivingAddress), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: RoundedWhiteContainer( child: Column( children: [ - QrImage( - data: "${coin.uriScheme}:$receivingAddress", - size: MediaQuery.of(context).size.width / 2, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark), - const SizedBox( - height: 20, + Row( + children: [ + Text( + "Your ${coin.ticker} address", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ], ), - BlueTextButton( - text: "Create new QR code", - onTap: () async { - unawaited(Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => GenerateUriQrCodeView( - coin: coin, - receivingAddress: receivingAddress, - ), - settings: const RouteSettings( - name: GenerateUriQrCodeView.routeName, - ), + const SizedBox( + height: 4, + ), + Row( + children: [ + Expanded( + child: Text( + receivingAddress, + style: STextStyles.itemSubtitle12(context), ), - )); - }, + ), + ], ), ], ), ), ), - ), - ], + if (coin != Coin.epicCash) + const SizedBox( + height: 12, + ), + if (coin != Coin.epicCash) + TextButton( + onPressed: generateNewAddress, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Generate new address", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + const SizedBox( + height: 30, + ), + RoundedWhiteContainer( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + children: [ + QrImage( + data: "${coin.uriScheme}:$receivingAddress", + size: MediaQuery.of(context).size.width / 2, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark), + const SizedBox( + height: 20, + ), + BlueTextButton( + text: "Create new QR code", + onTap: () async { + unawaited(Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => GenerateUriQrCodeView( + coin: coin, + receivingAddress: receivingAddress, + ), + settings: const RouteSettings( + name: GenerateUriQrCodeView.routeName, + ), + ), + )); + }, + ), + ], + ), + ), + ), + ), + ], + ), ), ), ), diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 16de6cf55..f1075b6e6 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -22,6 +22,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -203,48 +204,51 @@ class _ConfirmTransactionViewState return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( + builder: (child) => Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Confirm transaction", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), child: ConditionalParent( diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 2539b89ab..a89a20d85 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -33,6 +33,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; @@ -379,325 +380,464 @@ class _SendViewState extends ConsumerState { }); } - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Send ${coin.ticker}", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Send ${coin.ticker}", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - // subtract top and bottom padding set in parent - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + // subtract top and bottom padding set in parent + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 22, + height: 22, + ), + const SizedBox( + width: 6, + ), + if (coin != Coin.firo && + coin != Coin.firoTestNet) + Expanded( + child: Text( + ref.watch(provider.select( + (value) => value.walletName)), + style: STextStyles.titleBold12(context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + if (coin == Coin.firo || + coin == Coin.firoTestNet) + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + ref.watch(provider.select( + (value) => value.walletName)), + style: + STextStyles.titleBold12(context) + .copyWith(fontSize: 14), + ), + // const SizedBox( + // height: 2, + // ), + Text( + "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", + style: STextStyles.label(context) + .copyWith(fontSize: 10), + ), + ], + ), + if (coin != Coin.firo && + coin != Coin.firoTestNet) + const SizedBox( + width: 10, + ), + if (coin == Coin.firo || + coin == Coin.firoTestNet) + const Spacer(), + FutureBuilder( + future: (coin != Coin.firo && + coin != Coin.firoTestNet) + ? ref.watch(provider.select( + (value) => value.availableBalance)) + : ref + .watch( + publicPrivateBalanceStateProvider + .state) + .state == + "Private" + ? (ref.watch(provider).wallet + as FiroWallet) + .availablePrivateBalance() + : (ref.watch(provider).wallet + as FiroWallet) + .availablePublicBalance(), + builder: + (_, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + _cachedBalance = snapshot.data!; + } + + if (_cachedBalance != null) { + return GestureDetector( + onTap: () { + cryptoAmountController.text = + _cachedBalance!.toStringAsFixed( + Constants + .decimalPlacesForCoin( + coin)); + }, + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Text( + "${Format.localizedStringAsFixed( + value: _cachedBalance!, + locale: locale, + decimalPlaces: 8, + )} ${coin.ticker}", + style: + STextStyles.titleBold12( + context) + .copyWith( + fontSize: 10, + ), + textAlign: TextAlign.right, + ), + Text( + "${Format.localizedStringAsFixed( + value: _cachedBalance! * + ref.watch(priceAnd24hChangeNotifierProvider + .select((value) => + value + .getPrice( + coin) + .item1)), + locale: locale, + decimalPlaces: 2, + )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles + .titleBold12_400( + context) + .copyWith( + fontSize: 8, + ), + textAlign: TextAlign.right, + ) + ], + ), + ), + ); + } else { + return Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance...", + ], + style: STextStyles.itemSubtitle( + context) + .copyWith( + fontSize: 10, + ), + ), + const SizedBox( + height: 2, + ), + AnimatedText( + stringsToLoopThrough: const [ + "Loading balance ", + "Loading balance. ", + "Loading balance.. ", + "Loading balance...", + ], + style: STextStyles.itemSubtitle( + context) + .copyWith( + fontSize: 8, + ), + ) + ], + ); + } + }, + ), + ], + ), + ), + ), + const SizedBox( + height: 16, + ), + Text( + "Send to", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 8, + ), + ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 22, - height: 22, - ), - const SizedBox( - width: 6, - ), - if (coin != Coin.firo && - coin != Coin.firoTestNet) - Expanded( - child: Text( - ref.watch(provider - .select((value) => value.walletName)), - style: STextStyles.titleBold12(context), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - if (coin == Coin.firo || - coin == Coin.firoTestNet) - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - ref.watch(provider.select( - (value) => value.walletName)), - style: STextStyles.titleBold12(context) - .copyWith(fontSize: 14), - ), - // const SizedBox( - // height: 2, - // ), - Text( - "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", - style: STextStyles.label(context) - .copyWith(fontSize: 10), - ), - ], - ), - if (coin != Coin.firo && - coin != Coin.firoTestNet) - const SizedBox( - width: 10, - ), - if (coin == Coin.firo || - coin == Coin.firoTestNet) - const Spacer(), - FutureBuilder( - future: (coin != Coin.firo && - coin != Coin.firoTestNet) - ? ref.watch(provider.select( - (value) => value.availableBalance)) - : ref - .watch( - publicPrivateBalanceStateProvider - .state) - .state == - "Private" - ? (ref.watch(provider).wallet - as FiroWallet) - .availablePrivateBalance() - : (ref.watch(provider).wallet - as FiroWallet) - .availablePublicBalance(), - builder: - (_, AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - _cachedBalance = snapshot.data!; - } - - if (_cachedBalance != null) { - return GestureDetector( - onTap: () { - cryptoAmountController.text = - _cachedBalance!.toStringAsFixed( - Constants - .decimalPlacesForCoin( - coin)); - }, - child: Container( - color: Colors.transparent, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - children: [ - Text( - "${Format.localizedStringAsFixed( - value: _cachedBalance!, - locale: locale, - decimalPlaces: 8, - )} ${coin.ticker}", - style: STextStyles.titleBold12( - context) - .copyWith( - fontSize: 10, - ), - textAlign: TextAlign.right, - ), - Text( - "${Format.localizedStringAsFixed( - value: _cachedBalance! * - ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => - value - .getPrice( - coin) - .item1)), - locale: locale, - decimalPlaces: 2, - )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: - STextStyles.titleBold12_400( - context) - .copyWith( - fontSize: 8, - ), - textAlign: TextAlign.right, - ) - ], - ), - ), - ); - } else { - return Column( - crossAxisAlignment: - CrossAxisAlignment.end, - children: [ - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance...", - ], - style: STextStyles.itemSubtitle( - context) - .copyWith( - fontSize: 10, - ), - ), - const SizedBox( - height: 2, - ), - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance...", - ], - style: STextStyles.itemSubtitle( - context) - .copyWith( - fontSize: 8, - ), - ) - ], - ); - } - }, - ), - ], - ), - ), - ), - const SizedBox( - height: 16, - ), - Text( - "Send to", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("sendViewAddressFieldKey"), - controller: sendToController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - onChanged: (newValue) { - _address = newValue; - _updatePreviewButtonState( - _address, _amountToSend); - - setState(() { - _addressToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter ${coin.ticker} address", - _addressFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, + child: TextField( + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - suffixIcon: Padding( - padding: sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - sendToController.text = ""; - _address = ""; - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - String content = - data.text!.trim(); - if (content.contains("\n")) { - content = content.substring( - 0, - content.indexOf("\n")); + onChanged: (newValue) { + _address = newValue; + _updatePreviewButtonState( + _address, _amountToSend); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${coin.ticker} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, _amountToSend); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + String content = + data.text!.trim(); + if (content + .contains("\n")) { + content = + content.substring( + 0, + content.indexOf( + "\n")); + } + + sendToController.text = + content; + _address = content; + + _updatePreviewButtonState( + _address, + _amountToSend); + setState(() { + _addressToggleFlag = + sendToController + .text.isNotEmpty; + }); + } + }, + child: sendToController + .text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + Navigator.of(context).pushNamed( + AddressBookView.routeName, + arguments: widget.coin, + ); + }, + child: const AddressBookIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewScanQrButtonKey"), + onTap: () async { + try { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context) + .unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await scanner.scan(); + + // Future.delayed( + // const Duration(seconds: 2), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + + Logging.instance.log( + "qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = + AddressUtils.parseUri( + qrResult.rawContent); + + Logging.instance.log( + "qrResult parsed: $results", + level: LogLevel.Info); + + if (results.isNotEmpty && + results["scheme"] == + coin.uriScheme) { + // auto fill address + _address = + results["address"] ?? ""; + sendToController.text = + _address!; + + // autofill notes field + if (results["message"] != + null) { + noteController.text = + results["message"]!; + } else if (results["label"] != + null) { + noteController.text = + results["label"]!; } + // autofill amount field + if (results["amount"] != + null) { + final amount = + Decimal.parse( + results["amount"]!); + cryptoAmountController + .text = + Format + .localizedStringAsFixed( + value: amount, + locale: ref + .read( + localeServiceChangeNotifierProvider) + .locale, + decimalPlaces: Constants + .decimalPlacesForCoin( + coin), + ); + amount.toString(); + _amountToSend = amount; + } + + _updatePreviewButtonState( + _address, _amountToSend); + setState(() { + _addressToggleFlag = + sendToController + .text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else if (ref + .read( + walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress( + qrResult.rawContent)) { + _address = + qrResult.rawContent; sendToController.text = - content; - _address = content; + _address ?? ""; _updatePreviewButtonState( _address, _amountToSend); @@ -707,211 +847,498 @@ class _SendViewState extends ConsumerState { .text.isNotEmpty; }); } - }, - child: - sendToController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewAddressBookButtonKey"), - onTap: () { - Navigator.of(context).pushNamed( - AddressBookView.routeName, - arguments: widget.coin, - ); - }, - child: const AddressBookIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewScanQrButtonKey"), - onTap: () async { - try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); + } on PlatformException catch (e, s) { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true; + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning); } - - final qrResult = - await scanner.scan(); - - // Future.delayed( - // const Duration(seconds: 2), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - - Logging.instance.log( - "qrResult parsed: $results", - level: LogLevel.Info); - - if (results.isNotEmpty && - results["scheme"] == - coin.uriScheme) { - // auto fill address - _address = - results["address"] ?? ""; - sendToController.text = - _address!; - - // autofill notes field - if (results["message"] != - null) { - noteController.text = - results["message"]!; - } else if (results["label"] != - null) { - noteController.text = - results["label"]!; - } - - // autofill amount field - if (results["amount"] != null) { - final amount = Decimal.parse( - results["amount"]!); - cryptoAmountController.text = - Format - .localizedStringAsFixed( - value: amount, - locale: ref - .read( - localeServiceChangeNotifierProvider) - .locale, - decimalPlaces: Constants - .decimalPlacesForCoin( - coin), - ); - amount.toString(); - _amountToSend = amount; - } - - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - - // now check for non standard encoded basic address - } else if (ref - .read( - walletsChangeNotifierProvider) - .getManager(walletId) - .validateAddress( - qrResult.rawContent)) { - _address = qrResult.rawContent; - sendToController.text = - _address ?? ""; - - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; - // here we ignore the exception caused by not giving permission - // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning); - } - }, - child: const QrCodeIcon(), - ) - ], + }, + child: const QrCodeIcon(), + ) + ], + ), ), ), ), ), ), - ), - Builder( - builder: (_) { - final error = _updateInvalidAddressText( - _address ?? "", - ref - .read(walletsChangeNotifierProvider) - .getManager(walletId), - ); + Builder( + builder: (_) { + final error = _updateInvalidAddressText( + _address ?? "", + ref + .read(walletsChangeNotifierProvider) + .getManager(walletId), + ); - if (error == null || error.isEmpty) { - return Container(); - } else { - return Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - ), - child: Text( - error, - textAlign: TextAlign.left, - style: STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .textError, + if (error == null || error.isEmpty) { + return Container(); + } else { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + top: 4.0, + ), + child: Text( + error, + textAlign: TextAlign.left, + style: + STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .textError, + ), ), ), + ); + } + }, + ), + if (coin == Coin.firo) + const SizedBox( + height: 12, + ), + if (coin == Coin.firo) + Text( + "Send from", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + if (coin == Coin.firo) + const SizedBox( + height: 8, + ), + if (coin == Coin.firo) + Stack( + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: + Util.isDesktop ? false : true, + readOnly: true, + textInputAction: TextInputAction.none, ), - ); - } - }, - ), - if (coin == Coin.firo) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + FiroBalanceSelectionSheet( + walletId: walletId, + ), + ); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", + style: STextStyles.itemSubtitle12( + context), + ), + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _firoBalanceFuture( + provider, locale), + builder: (context, + AsyncSnapshot + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Private") { + _privateBalanceString = + snapshot.data!; + } else { + _publicBalanceString = + snapshot.data!; + } + } + if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Private" && + _privateBalanceString != + null) { + return Text( + "$_privateBalanceString ${coin.ticker}", + style: STextStyles + .itemSubtitle(context), + ); + } else if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Public" && + _publicBalanceString != + null) { + return Text( + "$_publicBalanceString ${coin.ticker}", + style: STextStyles + .itemSubtitle(context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading balance", + "Loading balance.", + "Loading balance..", + "Loading balance...", + ], + style: STextStyles + .itemSubtitle(context), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ], + ), + ), + ) + ], + ), const SizedBox( height: 12, ), - if (coin == Coin.firo) - Text( - "Send from", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + BlueTextButton( + text: "Send all ${coin.ticker}", + onTap: () async { + if (coin == Coin.firo || + coin == Coin.firoTestNet) { + final firoWallet = + ref.read(provider).wallet as FiroWallet; + if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Private") { + cryptoAmountController.text = + (await firoWallet + .availablePrivateBalance()) + .toStringAsFixed(Constants + .decimalPlacesForCoin(coin)); + } else { + cryptoAmountController.text = + (await firoWallet + .availablePublicBalance()) + .toStringAsFixed(Constants + .decimalPlacesForCoin(coin)); + } + } else { + cryptoAmountController.text = (await ref + .read(provider) + .availableBalance) + .toStringAsFixed( + Constants.decimalPlacesForCoin( + coin)); + } + }, + ), + ], + ), + const SizedBox( + height: 8, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + key: + const Key("amountInputFieldCryptoTextFieldKey"), + controller: cryptoAmountController, + focusNode: _cryptoFocus, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, + newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: + STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + coin.ticker, + style: STextStyles.smallMed14(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + if (Prefs.instance.externalCalls) + const SizedBox( + height: 8, + ), + if (Prefs.instance.externalCalls) + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + key: + const Key("amountInputFieldFiatTextFieldKey"), + controller: baseAmountController, + focusNode: _baseFocus, + keyboardType: + const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a fiat amount with 2 decimal places + TextInputFormatter.withFunction((oldValue, + newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + onChanged: (baseAmountString) { + if (baseAmountString.isNotEmpty && + baseAmountString != "." && + baseAmountString != ",") { + final baseAmount = + baseAmountString.contains(",") + ? Decimal.parse(baseAmountString + .replaceFirst(",", ".")) + : Decimal.parse(baseAmountString); + + var _price = ref + .read(priceAnd24hChangeNotifierProvider) + .getPrice(coin) + .item1; + + if (_price == Decimal.zero) { + _amountToSend = Decimal.zero; + } else { + _amountToSend = baseAmount <= Decimal.zero + ? Decimal.zero + : (baseAmount / _price).toDecimal( + scaleOnInfinitePrecision: + Constants.decimalPlacesForCoin( + coin)); + } + if (_cachedAmountToSend != null && + _cachedAmountToSend == _amountToSend) { + return; + } + _cachedAmountToSend = _amountToSend; + Logging.instance.log( + "it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + + final amountString = + Format.localizedStringAsFixed( + value: _amountToSend!, + locale: ref + .read( + localeServiceChangeNotifierProvider) + .locale, + decimalPlaces: + Constants.decimalPlacesForCoin(coin), + ); + + _cryptoAmountChangeLock = true; + cryptoAmountController.text = amountString; + _cryptoAmountChangeLock = false; + } else { + _amountToSend = Decimal.zero; + _cryptoAmountChangeLock = true; + cryptoAmountController.text = ""; + _cryptoAmountChangeLock = false; + } + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis( + // _amountToSend!)); + // }); + _updatePreviewButtonState( + _address, _amountToSend); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: + STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)), + style: STextStyles.smallMed14(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + const SizedBox( + height: 12, + ), + Text( + "Note (optional)", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: noteController, + focusNode: _noteFocusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Type something...", + _noteFocusNode, + context, + ).copyWith( + suffixIcon: noteController.text.isNotEmpty + ? Padding( + padding: + const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + noteController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 12, + ), + Text( + "Transaction fee (estimated)", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, ), - if (coin == Coin.firo) const SizedBox( height: 8, ), - if (coin == Coin.firo) Stack( children: [ TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, + controller: feeController, readOnly: true, textInputAction: TextInputAction.none, ), @@ -928,780 +1355,172 @@ class _SendViewState extends ConsumerState { Constants.size.circularBorderRadius, ), ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => FiroBalanceSelectionSheet( - walletId: walletId, - ), - ); - }, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", - style: STextStyles.itemSubtitle12( - context), - ), - const SizedBox( - width: 10, - ), - FutureBuilder( - future: _firoBalanceFuture( - provider, locale), - builder: (context, - AsyncSnapshot - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") { - _privateBalanceString = - snapshot.data!; + onPressed: (coin == Coin.firo || + coin == Coin.firoTestNet) && + ref + .watch( + publicPrivateBalanceStateProvider + .state) + .state == + "Private" + ? null + : () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + TransactionFeeSelectionSheet( + walletId: walletId, + amount: Decimal.tryParse( + cryptoAmountController + .text) ?? + Decimal.zero, + updateChosen: (String fee) { + setState(() { + _calculateFeesFuture = + Future(() => fee); + }); + }, + ), + ); + }, + child: ((coin == Coin.firo || + coin == Coin.firoTestNet) && + ref + .watch( + publicPrivateBalanceStateProvider + .state) + .state == + "Private") + ? Row( + children: [ + FutureBuilder( + future: _calculateFeesFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Text( + "~${snapshot.data! as String} ${coin.ticker}", + style: STextStyles + .itemSubtitle(context), + ); } else { - _publicBalanceString = - snapshot.data!; + return AnimatedText( + stringsToLoopThrough: const [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ], + style: STextStyles + .itemSubtitle(context), + ); } - } - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private" && - _privateBalanceString != - null) { - return Text( - "$_privateBalanceString ${coin.ticker}", - style: - STextStyles.itemSubtitle( - context), - ); - } else if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Public" && - _publicBalanceString != - null) { - return Text( - "$_publicBalanceString ${coin.ticker}", - style: - STextStyles.itemSubtitle( - context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance...", - ], - style: - STextStyles.itemSubtitle( - context), - ); - } - }, - ), - ], - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: Theme.of(context) - .extension()! - .textSubtitle2, - ), - ], - ), + }, + ), + ], + ) + : Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + ref + .watch( + feeRateTypeStateProvider + .state) + .state + .prettyName, + style: STextStyles + .itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _calculateFeesFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + return Text( + "~${snapshot.data! as String} ${coin.ticker}", + style: STextStyles + .itemSubtitle( + context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ], + style: STextStyles + .itemSubtitle( + context), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ], + ), ), ) ], ), - const SizedBox( - height: 12, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Amount", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - BlueTextButton( - text: "Send all ${coin.ticker}", - onTap: () async { - if (coin == Coin.firo || - coin == Coin.firoTestNet) { - final firoWallet = - ref.read(provider).wallet as FiroWallet; - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") { - cryptoAmountController.text = - (await firoWallet - .availablePrivateBalance()) - .toStringAsFixed( - Constants.decimalPlacesForCoin( - coin)); - } else { - cryptoAmountController.text = - (await firoWallet - .availablePublicBalance()) - .toStringAsFixed( - Constants.decimalPlacesForCoin( - coin)); - } - } else { - cryptoAmountController.text = (await ref - .read(provider) - .availableBalance) - .toStringAsFixed( - Constants.decimalPlacesForCoin(coin)); - } - }, - ), - ], - ), - const SizedBox( - height: 8, - ), - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - key: const Key("amountInputFieldCryptoTextFieldKey"), - controller: cryptoAmountController, - focusNode: _cryptoFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - textAlign: TextAlign.right, - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - coin.ticker, - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - ), - ), - ), - if (Prefs.instance.externalCalls) + const Spacer(), const SizedBox( - height: 8, + height: 12, ), - if (Prefs.instance.externalCalls) - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - key: const Key("amountInputFieldFiatTextFieldKey"), - controller: baseAmountController, - focusNode: _baseFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - textAlign: TextAlign.right, - inputFormatters: [ - // regex to validate a fiat amount with 2 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - onChanged: (baseAmountString) { - if (baseAmountString.isNotEmpty && - baseAmountString != "." && - baseAmountString != ",") { - final baseAmount = baseAmountString - .contains(",") - ? Decimal.parse( - baseAmountString.replaceFirst(",", ".")) - : Decimal.parse(baseAmountString); - - var _price = ref - .read(priceAnd24hChangeNotifierProvider) - .getPrice(coin) - .item1; - - if (_price == Decimal.zero) { - _amountToSend = Decimal.zero; - } else { - _amountToSend = baseAmount <= Decimal.zero - ? Decimal.zero - : (baseAmount / _price).toDecimal( - scaleOnInfinitePrecision: - Constants.decimalPlacesForCoin( - coin)); - } - if (_cachedAmountToSend != null && - _cachedAmountToSend == _amountToSend) { - return; - } - _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info); - - final amountString = - Format.localizedStringAsFixed( - value: _amountToSend!, - locale: ref - .read(localeServiceChangeNotifierProvider) - .locale, - decimalPlaces: - Constants.decimalPlacesForCoin(coin), - ); - - _cryptoAmountChangeLock = true; - cryptoAmountController.text = amountString; - _cryptoAmountChangeLock = false; - } else { - _amountToSend = Decimal.zero; - _cryptoAmountChangeLock = true; - cryptoAmountController.text = ""; - _cryptoAmountChangeLock = false; - } - // setState(() { - // _calculateFeesFuture = calculateFees( - // Format.decimalAmountToSatoshis( - // _amountToSend!)); - // }); - _updatePreviewButtonState( - _address, _amountToSend); - }, - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: - STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)), - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - ), - ), - ), - const SizedBox( - height: 12, - ), - Text( - "Note (optional)", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: noteController, - focusNode: _noteFocusNode, - style: STextStyles.field(context), - onChanged: (_) => setState(() {}), - decoration: standardInputDecoration( - "Type something...", - _noteFocusNode, - context, - ).copyWith( - suffixIcon: noteController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - noteController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 12, - ), - Text( - "Transaction fee (estimated)", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, - ), - Stack( - children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: feeController, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - ), - child: RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: (coin == Coin.firo || - coin == Coin.firoTestNet) && - ref - .watch( - publicPrivateBalanceStateProvider - .state) - .state == - "Private" - ? null - : () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => - TransactionFeeSelectionSheet( - walletId: walletId, - amount: Decimal.tryParse( - cryptoAmountController - .text) ?? - Decimal.zero, - updateChosen: (String fee) { - setState(() { - _calculateFeesFuture = - Future(() => fee); - }); - }, - ), - ); - }, - child: ((coin == Coin.firo || - coin == Coin.firoTestNet) && - ref - .watch( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") - ? Row( - children: [ - FutureBuilder( - future: _calculateFeesFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "~${snapshot.data! as String} ${coin.ticker}", - style: - STextStyles.itemSubtitle( - context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ], - style: - STextStyles.itemSubtitle( - context), - ); - } - }, - ), - ], - ) - : Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - ref - .watch( - feeRateTypeStateProvider - .state) - .state - .prettyName, - style: - STextStyles.itemSubtitle12( - context), - ), - const SizedBox( - width: 10, - ), - FutureBuilder( - future: _calculateFeesFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState - .done && - snapshot.hasData) { - return Text( - "~${snapshot.data! as String} ${coin.ticker}", - style: STextStyles - .itemSubtitle( - context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ], - style: STextStyles - .itemSubtitle( - context), - ); - } - }, - ), - ], - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: Theme.of(context) - .extension()! - .textSubtitle2, - ), - ], - ), - ), - ) - ], - ), - const Spacer(), - const SizedBox( - height: 12, - ), - TextButton( - onPressed: ref - .watch(previewTxButtonStateProvider.state) - .state - ? () async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId); - - // TODO: remove the need for this!! - final bool isOwnAddress = - await manager.isOwnAddress(_address!); - if (isOwnAddress) { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: - "Sending to self is currently disabled", - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Ok", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - }, + TextButton( + onPressed: ref + .watch(previewTxButtonStateProvider.state) + .state + ? () async { + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), ); - return; - } + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId); - final amount = Format.decimalAmountToSatoshis( - _amountToSend!, coin); - int availableBalance; - if ((coin == Coin.firo || - coin == Coin.firoTestNet)) { - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") { - availableBalance = - Format.decimalAmountToSatoshis( - await (manager.wallet - as FiroWallet) - .availablePrivateBalance(), - coin); - } else { - availableBalance = - Format.decimalAmountToSatoshis( - await (manager.wallet - as FiroWallet) - .availablePublicBalance(), - coin); - } - } else { - availableBalance = - Format.decimalAmountToSatoshis( - await manager.availableBalance, - coin); - } - - // confirm send all - if (amount == availableBalance) { - final bool? shouldSendAll = - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Confirm send all", - message: - "You are about to send your entire balance. Would you like to continue?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Yes", - style: - STextStyles.button(context), - ), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - ); - }, - ); - - if (shouldSendAll == null || - shouldSendAll == false) { - // cancel preview - return; - } - } - - try { - bool wasCancelled = false; - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; - - Navigator.of(context).pop(); - }, - ); - }, - )); - - Map txData; - - if ((coin == Coin.firo || - coin == Coin.firoTestNet) && - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state != - "Private") { - txData = - await (manager.wallet as FiroWallet) - .prepareSendPublic( - address: _address!, - satoshiAmount: amount, - args: { - "feeRate": - ref.read(feeRateTypeStateProvider) - }, - ); - } else { - txData = await manager.prepareSend( - address: _address!, - satoshiAmount: amount, - args: { - "feeRate": - ref.read(feeRateTypeStateProvider) - }, - ); - } - - if (!wasCancelled && mounted) { - // pop building dialog - Navigator.of(context).pop(); - txData["note"] = noteController.text; - txData["address"] = _address; - - unawaited(Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: RouteGenerator - .useMaterialPageRoute, - builder: (_) => - ConfirmTransactionView( - transactionInfo: txData, - walletId: walletId, - ), - settings: const RouteSettings( - name: ConfirmTransactionView - .routeName, - ), - ), - )); - } - } catch (e) { - if (mounted) { - // pop building dialog - Navigator.of(context).pop(); - - unawaited(showDialog( + // TODO: remove the need for this!! + final bool isOwnAddress = + await manager.isOwnAddress(_address!); + if (isOwnAddress) { + await showDialog( context: context, useSafeArea: false, barrierDismissible: true, builder: (context) { return StackDialog( title: "Transaction failed", - message: e.toString(), + message: + "Sending to self is currently disabled", rightButton: TextButton( style: Theme.of(context) .extension()! @@ -1723,36 +1542,238 @@ class _SendViewState extends ConsumerState { ), ); }, + ); + return; + } + + final amount = + Format.decimalAmountToSatoshis( + _amountToSend!, coin); + int availableBalance; + if ((coin == Coin.firo || + coin == Coin.firoTestNet)) { + if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Private") { + availableBalance = + Format.decimalAmountToSatoshis( + await (manager.wallet + as FiroWallet) + .availablePrivateBalance(), + coin); + } else { + availableBalance = + Format.decimalAmountToSatoshis( + await (manager.wallet + as FiroWallet) + .availablePublicBalance(), + coin); + } + } else { + availableBalance = + Format.decimalAmountToSatoshis( + await manager.availableBalance, + coin); + } + + // confirm send all + if (amount == availableBalance) { + final bool? shouldSendAll = + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Confirm send all", + message: + "You are about to send your entire balance. Would you like to continue?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.button( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context) + .pop(false); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor( + context), + child: Text( + "Yes", + style: + STextStyles.button(context), + ), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ); + }, + ); + + if (shouldSendAll == null || + shouldSendAll == false) { + // cancel preview + return; + } + } + + try { + bool wasCancelled = false; + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ); + }, )); + + Map txData; + + if ((coin == Coin.firo || + coin == Coin.firoTestNet) && + ref + .read( + publicPrivateBalanceStateProvider + .state) + .state != + "Private") { + txData = + await (manager.wallet as FiroWallet) + .prepareSendPublic( + address: _address!, + satoshiAmount: amount, + args: { + "feeRate": ref + .read(feeRateTypeStateProvider) + }, + ); + } else { + txData = await manager.prepareSend( + address: _address!, + satoshiAmount: amount, + args: { + "feeRate": ref + .read(feeRateTypeStateProvider) + }, + ); + } + + if (!wasCancelled && mounted) { + // pop building dialog + Navigator.of(context).pop(); + txData["note"] = noteController.text; + txData["address"] = _address; + + unawaited(Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator + .useMaterialPageRoute, + builder: (_) => + ConfirmTransactionView( + transactionInfo: txData, + walletId: walletId, + ), + settings: const RouteSettings( + name: ConfirmTransactionView + .routeName, + ), + ), + )); + } + } catch (e) { + if (mounted) { + // pop building dialog + Navigator.of(context).pop(); + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Ok", + style: STextStyles.button( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + )); + } } } - } - : null, - style: ref - .watch(previewTxButtonStateProvider.state) - .state - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - child: Text( - "Preview", - style: STextStyles.button(context), + : null, + style: ref + .watch(previewTxButtonStateProvider.state) + .state + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + child: Text( + "Preview", + style: STextStyles.button(context), + ), ), - ), - const SizedBox( - height: 4, - ), - ], + const SizedBox( + height: 4, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index dc2da2488..a1e78ba7d 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -11,6 +11,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/utilities/logger.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/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -117,408 +118,431 @@ class AboutView extends ConsumerWidget { ]; Future commitMoneroFuture = Future.wait(futureMoneroList); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "About", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "About", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: - (context, AsyncSnapshot snapshot) { - String version = ""; - String signature = ""; - String appName = ""; - String build = ""; + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: + (context, AsyncSnapshot snapshot) { + String version = ""; + String signature = ""; + String appName = ""; + String build = ""; - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - version = snapshot.data!.version; - build = snapshot.data!.buildNumber; - signature = snapshot.data!.buildSignature; - appName = snapshot.data!.appName; - } + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + version = snapshot.data!.version; + build = snapshot.data!.buildNumber; + signature = snapshot.data!.buildSignature; + appName = snapshot.data!.appName; + } - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + appName, + style: STextStyles.pageTitleH2(context), + ), + ), + const SizedBox( + height: 24, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Version", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + version, + style: + STextStyles.itemSubtitle(context), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Build number", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + build, + style: + STextStyles.itemSubtitle(context), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Build signature", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + signature, + style: + STextStyles.itemSubtitle(context), + ), + ], + ), + ), + ], + ); + }, + ), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: commitFiroFuture, + builder: + (context, AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = snapshot.data![0] as bool; + isHead = snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = CommitStatus.isOldCommit; + } else { + stateOfCommit = CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle(context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed); + break; + default: + break; + } + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Firo Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + firoCommit, + style: indicationStyle, + ), + ], + ), + ); + }), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: commitEpicFuture, + builder: + (context, AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = snapshot.data![0] as bool; + isHead = snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = CommitStatus.isOldCommit; + } else { + stateOfCommit = CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle(context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed); + break; + default: + break; + } + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Epic Cash Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + epicCashCommit, + style: indicationStyle, + ), + ], + ), + ); + }), + const SizedBox( + height: 12, + ), + FutureBuilder( + future: commitMoneroFuture, + builder: + (context, AsyncSnapshot snapshot) { + bool commitExists = false; + bool isHead = false; + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + commitExists = snapshot.data![0] as bool; + isHead = snapshot.data![1] as bool; + if (commitExists && isHead) { + stateOfCommit = CommitStatus.isHead; + } else if (commitExists) { + stateOfCommit = CommitStatus.isOldCommit; + } else { + stateOfCommit = CommitStatus.notACommit; + } + } + TextStyle indicationStyle = + STextStyles.itemSubtitle(context); + switch (stateOfCommit) { + case CommitStatus.isHead: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen); + break; + case CommitStatus.isOldCommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorYellow); + break; + case CommitStatus.notACommit: + indicationStyle = + STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed); + break; + default: + break; + } + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Monero Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + moneroCommit, + style: indicationStyle, + ), + ], + ), + ); + }), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center( - child: Text( - appName, - style: STextStyles.pageTitleH2(context), - ), + Text( + "Website", + style: STextStyles.titleBold12(context), ), const SizedBox( - height: 24, + height: 4, ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Text( - "Version", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - version, - style: STextStyles.itemSubtitle(context), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Text( - "Build number", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - build, - style: STextStyles.itemSubtitle(context), - ), - ], - ), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Text( - "Build signature", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - signature, - style: STextStyles.itemSubtitle(context), - ), - ], - ), + BlueTextButton( + text: "https://stackwallet.com", + onTap: () { + launchUrl( + Uri.parse("https://stackwallet.com"), + mode: LaunchMode.externalApplication, + ); + }, ), ], - ); - }, - ), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: commitFiroFuture, - builder: (context, AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = snapshot.data![0] as bool; - isHead = snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = CommitStatus.isOldCommit; - } else { - stateOfCommit = CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle(context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed); - break; - default: - break; - } - return RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Firo Build Commit", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - firoCommit, - style: indicationStyle, - ), - ], - ), - ); - }), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: commitEpicFuture, - builder: (context, AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = snapshot.data![0] as bool; - isHead = snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = CommitStatus.isOldCommit; - } else { - stateOfCommit = CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle(context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed); - break; - default: - break; - } - return RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Epic Cash Build Commit", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - epicCashCommit, - style: indicationStyle, - ), - ], - ), - ); - }), - const SizedBox( - height: 12, - ), - FutureBuilder( - future: commitMoneroFuture, - builder: (context, AsyncSnapshot snapshot) { - bool commitExists = false; - bool isHead = false; - CommitStatus stateOfCommit = CommitStatus.notLoaded; - - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - commitExists = snapshot.data![0] as bool; - isHead = snapshot.data![1] as bool; - if (commitExists && isHead) { - stateOfCommit = CommitStatus.isHead; - } else if (commitExists) { - stateOfCommit = CommitStatus.isOldCommit; - } else { - stateOfCommit = CommitStatus.notACommit; - } - } - TextStyle indicationStyle = - STextStyles.itemSubtitle(context); - switch (stateOfCommit) { - case CommitStatus.isHead: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorGreen); - break; - case CommitStatus.isOldCommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorYellow); - break; - case CommitStatus.notACommit: - indicationStyle = - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed); - break; - default: - break; - } - return RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Monero Build Commit", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - moneroCommit, - style: indicationStyle, - ), - ], - ), - ); - }), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Website", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - BlueTextButton( - text: "https://stackwallet.com", - onTap: () { - launchUrl( - Uri.parse("https://stackwallet.com"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], + ), ), - ), - const SizedBox( - height: 12, - ), - const Spacer(), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: STextStyles.label(context), - children: [ - const TextSpan( - text: - "By using Stack Wallet, you agree to the "), - TextSpan( - text: "Terms of service", - style: STextStyles.richLink(context), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse( - "https://stackwallet.com/terms-of-service.html"), - mode: LaunchMode.externalApplication, - ); - }, - ), - const TextSpan(text: " and "), - TextSpan( - text: "Privacy policy", - style: STextStyles.richLink(context), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse( - "https://stackwallet.com/privacy-policy.html"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], + const SizedBox( + height: 12, ), - ), - ], + const Spacer(), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: STextStyles.label(context), + children: [ + const TextSpan( + text: + "By using Stack Wallet, you agree to the "), + TextSpan( + text: "Terms of service", + style: STextStyles.richLink(context), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/terms-of-service.html"), + mode: LaunchMode.externalApplication, + ); + }, + ), + const TextSpan(text: " and "), + TextSpan( + text: "Privacy policy", + style: STextStyles.richLink(context), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse( + "https://stackwallet.com/privacy-policy.html"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index 86212e0c7..06b798c36 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -1,16 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/providers/global/prefs_provider.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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:tuple/tuple.dart'; - -import 'package:stackwallet/pages/stack_privacy_calls.dart'; class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ @@ -23,158 +22,160 @@ class AdvancedSettingsView extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Advanced", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Advanced", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: () { - Navigator.of(context).pushNamed(DebugView.routeName); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - Text( - "Debug info", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], + onPressed: () { + Navigator.of(context).pushNamed(DebugView.routeName); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Debug info", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: null, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Toggle testnet coins", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider - .select((value) => value.showTestNetCoins), - ), - onValueChanged: (newValue) { - ref - .read(prefsChangeNotifierProvider) - .showTestNetCoins = newValue; - }, + onPressed: null, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Toggle testnet coins", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, ), - ), - ], - ), - ), - ); - }, - ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Consumer( - builder: (_, ref, __) { - final externalCalls = ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - ); - return RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - Navigator.of(context).pushNamed( - StackPrivacyCalls.routeName, - arguments: true, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - RichText( - textAlign: TextAlign.left, - text: TextSpan( - children: [ - TextSpan( - text: "Stack Experience", - style: STextStyles.titleBold12(context), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.showTestNetCoins), ), - TextSpan( - text: externalCalls - ? "\nEasy crypto" - : "\nIncognito", - style: STextStyles.label(context) - .copyWith(fontSize: 15.0), - ) - ], + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .showTestNetCoins = newValue; + }, + ), ), - ), - ], + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - ], + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Consumer( + builder: (_, ref, __) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider + .select((value) => value.externalCalls), + ); + return RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context).pushNamed( + StackPrivacyCalls.routeName, + arguments: true, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + RichText( + textAlign: TextAlign.left, + text: TextSpan( + children: [ + TextSpan( + text: "Stack Experience", + style: STextStyles.titleBold12(context), + ), + TextSpan( + text: externalCalls + ? "\nEasy crypto" + : "\nIncognito", + style: STextStyles.label(context) + .copyWith(fontSize: 15.0), + ) + ], + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index 055773ef6..33f21759e 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -26,6 +26,7 @@ import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -99,474 +100,484 @@ class _DebugViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Debug", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("deleteLogsAppBarButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.trash, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () async { - await showDialog( - context: context, - builder: (_) => StackDialog( - title: "Delete logs?", - message: - "You are about to delete all logs permanently. Are you sure?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Debug", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("deleteLogsAppBarButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.trash, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () async { + await showDialog( + context: context, + builder: (_) => StackDialog( + title: "Delete logs?", + message: + "You are about to delete all logs permanently. Are you sure?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(); + }, ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Delete logs", - style: STextStyles.button(context), - ), - onPressed: () async { - Navigator.of(context).pop(); + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Delete logs", + style: STextStyles.button(context), + ), + onPressed: () async { + Navigator.of(context).pop(); - bool shouldPop = false; - unawaited(showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: const CustomLoadingOverlay( - message: "Deleting logs...", - eventBus: null, + bool shouldPop = false; + unawaited(showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: const CustomLoadingOverlay( + message: "Deleting logs...", + eventBus: null, + ), ), - ), - )); + )); - await ref - .read(debugServiceProvider) - .deleteAllMessages(); - await ref - .read(debugServiceProvider) - .updateRecentLogs(); + await ref + .read(debugServiceProvider) + .deleteAllMessages(); + await ref + .read(debugServiceProvider) + .updateRecentLogs(); - shouldPop = true; + shouldPop = true; - if (mounted) { - Navigator.pop(context); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - context: context, - message: 'Logs cleared!')); - } - }, + if (mounted) { + Navigator.pop(context); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + context: context, + message: 'Logs cleared!')); + } + }, + ), ), - ), - ); - }, + ); + }, + ), ), ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, + ], ), - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: - NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => _searchTerm = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchTerm = ""; - }); - }, - ), - ], - ), - ), - ) - : null, + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: + NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ), - ), - const SizedBox( - height: 12, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - BlueTextButton( - text: "Save Debug Info to clipboard", - onTap: () async { - try { - final packageInfo = - await PackageInfo.fromPlatform(); - final version = packageInfo.version; - final build = packageInfo.buildNumber; - final signature = packageInfo.buildSignature; - final appName = packageInfo.appName; - String firoCommit = - FIRO_VERSIONS.getPluginVersion(); - String epicCashCommit = - EPIC_VERSIONS.getPluginVersion(); - String moneroCommit = - MONERO_VERSIONS.getPluginVersion(); - DeviceInfoPlugin deviceInfoPlugin = - DeviceInfoPlugin(); - final deviceInfo = - await deviceInfoPlugin.deviceInfo; - var deviceInfoMap = deviceInfo.toMap(); - deviceInfoMap.remove("systemFeatures"); - - final logs = filtered( - ref.watch(debugServiceProvider.select( - (value) => value.recentLogs)), - _searchTerm) - .reversed - .toList(growable: false); - List errorLogs = []; - for (var log in logs) { - if (log.logLevel == LogLevel.Error || - log.logLevel == LogLevel.Fatal) { - errorLogs.add( - "${log.logLevel}: ${log.message}"); - } - } - - final finalDebugMap = { - "version": version, - "build": build, - "signature": signature, - "appName": appName, - "firoCommit": firoCommit, - "epicCashCommit": epicCashCommit, - "moneroCommit": moneroCommit, - "deviceInfoMap": deviceInfoMap, - "errorLogs": errorLogs, - }; - Logging.instance.log( - json.encode(finalDebugMap), - level: LogLevel.Info, - printFullLength: true); - const ClipboardInterface clipboard = - ClipboardWrapper(); - await clipboard.setData( - ClipboardData( - text: json.encode(finalDebugMap)), - ); - } catch (e, s) { - Logging.instance - .log("$e $s", level: LogLevel.Error); - } + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => _searchTerm = newString); }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: + const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), ), - const Spacer(), - BlueTextButton( - text: "Save logs to file", - onTap: () async { - final systemfile = SWBFileSystem(); - await systemfile.prepareStorage(); - Directory rootPath = await StackFileSystem - .applicationRootDirectory(); - - if (Platform.isAndroid) { - rootPath = Directory("/storage/emulated/0/"); - } - - Directory dir = - Directory('${rootPath.path}/Documents'); - if (Platform.isIOS) { - dir = Directory(rootPath.path); - } - try { - if (!dir.existsSync()) { - dir.createSync(); - } - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - } - String? path; - if (Platform.isAndroid) { - path = dir.path; - } else { - path = await FilePicker.platform - .getDirectoryPath( - dialogTitle: "Choose Log Save Location", - initialDirectory: - systemfile.startPath!.path, - lockParentWindow: true, - ); - } - - if (path != null) { - final eventBus = EventBus(); - bool shouldPop = false; - unawaited(showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: CustomLoadingOverlay( - message: "Generating Stack logs file", - eventBus: eventBus, - ), - ), - )); - - bool logssaved = true; - var filename; + ), + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BlueTextButton( + text: "Save Debug Info to clipboard", + onTap: () async { try { - filename = await ref - .read(debugServiceProvider) - .exportToFile(path, eventBus); + final packageInfo = + await PackageInfo.fromPlatform(); + final version = packageInfo.version; + final build = packageInfo.buildNumber; + final signature = + packageInfo.buildSignature; + final appName = packageInfo.appName; + String firoCommit = + FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit = + EPIC_VERSIONS.getPluginVersion(); + String moneroCommit = + MONERO_VERSIONS.getPluginVersion(); + DeviceInfoPlugin deviceInfoPlugin = + DeviceInfoPlugin(); + final deviceInfo = + await deviceInfoPlugin.deviceInfo; + var deviceInfoMap = deviceInfo.toMap(); + deviceInfoMap.remove("systemFeatures"); + + final logs = filtered( + ref.watch(debugServiceProvider + .select((value) => + value.recentLogs)), + _searchTerm) + .reversed + .toList(growable: false); + List errorLogs = []; + for (var log in logs) { + if (log.logLevel == LogLevel.Error || + log.logLevel == LogLevel.Fatal) { + errorLogs.add( + "${log.logLevel}: ${log.message}"); + } + } + + final finalDebugMap = { + "version": version, + "build": build, + "signature": signature, + "appName": appName, + "firoCommit": firoCommit, + "epicCashCommit": epicCashCommit, + "moneroCommit": moneroCommit, + "deviceInfoMap": deviceInfoMap, + "errorLogs": errorLogs, + }; + Logging.instance.log( + json.encode(finalDebugMap), + level: LogLevel.Info, + printFullLength: true); + const ClipboardInterface clipboard = + ClipboardWrapper(); + await clipboard.setData( + ClipboardData( + text: json.encode(finalDebugMap)), + ); } catch (e, s) { - logssaved = false; Logging.instance .log("$e $s", level: LogLevel.Error); } + }, + ), + const Spacer(), + BlueTextButton( + text: "Save logs to file", + onTap: () async { + final systemfile = SWBFileSystem(); + await systemfile.prepareStorage(); + Directory rootPath = await StackFileSystem + .applicationRootDirectory(); - shouldPop = true; + if (Platform.isAndroid) { + rootPath = + Directory("/storage/emulated/0/"); + } - if (mounted) { - Navigator.pop(context); + Directory dir = + Directory('${rootPath.path}/Documents'); + if (Platform.isIOS) { + dir = Directory(rootPath.path); + } + try { + if (!dir.existsSync()) { + dir.createSync(); + } + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + } + String? path; + if (Platform.isAndroid) { + path = dir.path; + } else { + path = await FilePicker.platform + .getDirectoryPath( + dialogTitle: "Choose Log Save Location", + initialDirectory: + systemfile.startPath!.path, + lockParentWindow: true, + ); + } - if (Platform.isAndroid) { - unawaited( - showDialog( - context: context, - builder: (context) => StackOkDialog( - title: logssaved - ? "Logs saved to" - : "Error Saving Logs", - message: "${path!}/$filename", + if (path != null) { + final eventBus = EventBus(); + bool shouldPop = false; + unawaited(showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: CustomLoadingOverlay( + message: "Generating Stack logs file", + eventBus: eventBus, + ), + ), + )); + + bool logssaved = true; + var filename; + try { + filename = await ref + .read(debugServiceProvider) + .exportToFile(path, eventBus); + } catch (e, s) { + logssaved = false; + Logging.instance + .log("$e $s", level: LogLevel.Error); + } + + shouldPop = true; + + if (mounted) { + Navigator.pop(context); + + if (Platform.isAndroid) { + unawaited( + showDialog( + context: context, + builder: (context) => StackOkDialog( + title: logssaved + ? "Logs saved to" + : "Error Saving Logs", + message: "${path!}/$filename", + ), ), - ), - ); - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - context: context, - message: logssaved - ? 'Logs file saved' - : "Error Saving Logs", - ), - ); + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + context: context, + message: logssaved + ? 'Logs file saved' + : "Error Saving Logs", + ), + ); + } } } - } - }, - ), - ], - ) - ], + }, + ), + ], + ) + ], + ), ), ), ), - ), - ]; - }, - body: Builder( - builder: (context) { - final logs = filtered( - ref.watch(debugServiceProvider - .select((value) => value.recentLogs)), - _searchTerm) - .reversed - .toList(growable: false); + ]; + }, + body: Builder( + builder: (context) { + final logs = filtered( + ref.watch(debugServiceProvider + .select((value) => value.recentLogs)), + _searchTerm) + .reversed + .toList(growable: false); - return CustomScrollView( - reverse: true, - // shrinkWrap: true, - controller: scrollController, - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - context, + return CustomScrollView( + reverse: true, + // shrinkWrap: true, + controller: scrollController, + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor( + context, + ), ), - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final log = logs[index]; + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final log = logs[index]; - return Container( - key: Key("log_${log.id}_${log.timestampInMillisUTC}"), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: _borderRadius(index, logs.length), - ), - child: Padding( - padding: const EdgeInsets.all(4), - child: RoundedContainer( - padding: const EdgeInsets.all(0), + return Container( + key: Key( + "log_${log.id}_${log.timestampInMillisUTC}"), + decoration: BoxDecoration( color: Theme.of(context) .extension()! .popupBG, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - " [${log.logLevel.name}]", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 8, - color: (log.logLevel == LogLevel.Info - ? Theme.of(context) - .extension()! - .topNavIconGreen - : (log.logLevel == - LogLevel.Warning - ? Theme.of(context) - .extension()! - .topNavIconYellow - : (log.logLevel == - LogLevel.Error - ? Colors.orange - : Theme.of(context) - .extension< - StackColors>()! - .topNavIconRed))), + borderRadius: _borderRadius(index, logs.length), + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .popupBG, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + " [${log.logLevel.name}]", + style: STextStyles.baseXS(context) + .copyWith( + fontSize: 8, + color: (log.logLevel == + LogLevel.Info + ? Theme.of(context) + .extension()! + .topNavIconGreen + : (log.logLevel == + LogLevel.Warning + ? Theme.of(context) + .extension< + StackColors>()! + .topNavIconYellow + : (log.logLevel == + LogLevel.Error + ? Colors.orange + : Theme.of(context) + .extension< + StackColors>()! + .topNavIconRed))), + ), ), - ), - Text( - "[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 8, - color: Theme.of(context) - .extension()! - .textDark3, + Text( + "[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ", + style: STextStyles.baseXS(context) + .copyWith( + fontSize: 8, + color: Theme.of(context) + .extension()! + .textDark3, + ), ), - ), - ], - ), - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox( - width: 20, - ), - Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SelectableText( - log.message, - style: STextStyles.baseXS(context) - .copyWith(fontSize: 8), - ), - ], + ], + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 20, ), - ), - ], - ), - ], + Flexible( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SelectableText( + log.message, + style: + STextStyles.baseXS(context) + .copyWith(fontSize: 8), + ), + ], + ), + ), + ], + ), + ], + ), ), ), - ), - ); - }, - childCount: logs.length, + ); + }, + childCount: logs.length, + ), ), - ), - ], - ); - }, + ], + ); + }, + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart index d1e893802..693f39f02 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/utilities/theme/dark_colors.dart'; import 'package:stackwallet/utilities/theme/light_colors.dart'; import 'package:stackwallet/utilities/theme/ocean_breeze_colors.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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -32,127 +33,131 @@ class AppearanceSettingsView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Appearance", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Appearance", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: null, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Display favorite wallets", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.showFavoriteWallets), - ), - onValueChanged: (newValue) { - ref - .read(prefsChangeNotifierProvider) - .showFavoriteWallets = newValue; - }, + onPressed: null, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Display favorite wallets", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, ), - ) - ], + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => + value.showFavoriteWallets), + ), + onValueChanged: (newValue) { + ref + .read( + prefsChangeNotifierProvider) + .showFavoriteWallets = newValue; + }, + ), + ) + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - const SizedBox( - height: 10, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( padding: const EdgeInsets.all(0), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: null, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Choose Theme", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - const Padding( - padding: EdgeInsets.all(10), - child: ThemeOptionsView(), - ), - ], - ), - ], + onPressed: null, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Choose Theme", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + const Padding( + padding: EdgeInsets.all(10), + child: ThemeOptionsView(), + ), + ], + ), + ], + ), ), ), ), - ), - ], + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/currency_view.dart b/lib/pages/settings_views/global_settings_view/currency_view.dart index dccf2d61b..5bcf7fb7f 100644 --- a/lib/pages/settings_views/global_settings_view/currency_view.dart +++ b/lib/pages/settings_views/global_settings_view/currency_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -130,34 +131,37 @@ class _CurrencyViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Currency", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Currency", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: child, ), ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, - ), - child: child, - ), ); }, child: ConditionalParent( diff --git a/lib/pages/settings_views/global_settings_view/global_settings_view.dart b/lib/pages/settings_views/global_settings_view/global_settings_view.dart index fe6529c20..49e7ea36c 100644 --- a/lib/pages/settings_views/global_settings_view/global_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/global_settings_view.dart @@ -20,11 +20,10 @@ import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.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/utilities/delete_everything.dart'; - class GlobalSettingsView extends StatelessWidget { const GlobalSettingsView({ Key? key, @@ -35,254 +34,257 @@ class GlobalSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Settings", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Settings", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - SettingsListButton( - iconAssetName: Assets.svg.addressBook, - iconSize: 16, - title: "Address book", - onPressed: () { - Navigator.of(context) - .pushNamed(AddressBookView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.downloadFolder, - iconSize: 14, - title: "Stack backup & restore", - onPressed: () { - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - routeOnSuccess: - StackBackupView.routeName, - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to access Stack backup & restore settings", - biometricsAuthenticationTitle: - "Stack backup", - ), - settings: const RouteSettings( - name: "/swblockscreen"), - ), - ); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.lock, - iconSize: 16, - title: "Security", - onPressed: () { - Navigator.of(context) - .pushNamed(SecurityView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.dollarSign, - iconSize: 18, - title: "Currency", - onPressed: () { - Navigator.of(context).pushNamed( - BaseCurrencySettingsView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.language, - iconSize: 18, - title: "Language", - onPressed: () { - Navigator.of(context).pushNamed( - LanguageSettingsView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.node, - iconSize: 16, - title: "Manage nodes", - onPressed: () { - Navigator.of(context) - .pushNamed(ManageNodesView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.arrowRotate3, - iconSize: 18, - title: "Syncing preferences", - onPressed: () { - Navigator.of(context).pushNamed( - SyncingPreferencesView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.arrowUpRight, - iconSize: 16, - title: "Startup", - onPressed: () { - Navigator.of(context).pushNamed( - StartupPreferencesView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.sun, - iconSize: 18, - title: "Appearance", - onPressed: () { - Navigator.of(context).pushNamed( - AppearanceSettingsView.routeName); - }, - ), - if (Platform.isIOS) + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: Column( + children: [ + SettingsListButton( + iconAssetName: Assets.svg.addressBook, + iconSize: 16, + title: "Address book", + onPressed: () { + Navigator.of(context) + .pushNamed(AddressBookView.routeName); + }, + ), const SizedBox( height: 8, ), - if (Platform.isIOS) SettingsListButton( - iconAssetName: Assets.svg.circleAlert, - iconSize: 16, - title: "Delete account", - onPressed: () async { - await Navigator.of(context) - .pushNamed(DeleteAccountView.routeName); + iconAssetName: Assets.svg.downloadFolder, + iconSize: 14, + title: "Stack backup & restore", + onPressed: () { + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + routeOnSuccess: + StackBackupView.routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to access Stack backup & restore settings", + biometricsAuthenticationTitle: + "Stack backup", + ), + settings: const RouteSettings( + name: "/swblockscreen"), + ), + ); }, ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.ellipsis, - iconSize: 18, - title: "About", - onPressed: () { - Navigator.of(context) - .pushNamed(AboutView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.solidSliders, - iconSize: 16, - title: "Advanced", - onPressed: () { - Navigator.of(context).pushNamed( - AdvancedSettingsView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.questionMessage, - iconSize: 16, - title: "Support", - onPressed: () { - Navigator.of(context) - .pushNamed(SupportView.routeName); - }, - ), - // TextButton( - // style: Theme.of(context) - // .textButtonTheme - // .style - // ?.copyWith( - // backgroundColor: - // MaterialStateProperty.all( - // Theme.of(context).extension()!.accentColorDark - // ), - // ), - // child: Text( - // "fire test notification", - // style: STextStyles.button(context), - // ), - // onPressed: () async { - // NotificationApi.showNotification2( - // title: "Test notification", - // body: "My doggy wallet", - // walletId: - // "3c5e2d70-fcc3-11ec-86a3-31a106a81c3b", - // iconAssetName: - // Assets.svg.iconFor(coin: Coin.dogecoin), - // date: DateTime.now(), - // ); - // }, - // ), - ], + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.lock, + iconSize: 16, + title: "Security", + onPressed: () { + Navigator.of(context) + .pushNamed(SecurityView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.dollarSign, + iconSize: 18, + title: "Currency", + onPressed: () { + Navigator.of(context).pushNamed( + BaseCurrencySettingsView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.language, + iconSize: 18, + title: "Language", + onPressed: () { + Navigator.of(context).pushNamed( + LanguageSettingsView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.node, + iconSize: 16, + title: "Manage nodes", + onPressed: () { + Navigator.of(context) + .pushNamed(ManageNodesView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.arrowRotate3, + iconSize: 18, + title: "Syncing preferences", + onPressed: () { + Navigator.of(context).pushNamed( + SyncingPreferencesView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.arrowUpRight, + iconSize: 16, + title: "Startup", + onPressed: () { + Navigator.of(context).pushNamed( + StartupPreferencesView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.sun, + iconSize: 18, + title: "Appearance", + onPressed: () { + Navigator.of(context).pushNamed( + AppearanceSettingsView.routeName); + }, + ), + if (Platform.isIOS) + const SizedBox( + height: 8, + ), + if (Platform.isIOS) + SettingsListButton( + iconAssetName: Assets.svg.circleAlert, + iconSize: 16, + title: "Delete account", + onPressed: () async { + await Navigator.of(context).pushNamed( + DeleteAccountView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.ellipsis, + iconSize: 18, + title: "About", + onPressed: () { + Navigator.of(context) + .pushNamed(AboutView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.solidSliders, + iconSize: 16, + title: "Advanced", + onPressed: () { + Navigator.of(context).pushNamed( + AdvancedSettingsView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.questionMessage, + iconSize: 16, + title: "Support", + onPressed: () { + Navigator.of(context) + .pushNamed(SupportView.routeName); + }, + ), + // TextButton( + // style: Theme.of(context) + // .textButtonTheme + // .style + // ?.copyWith( + // backgroundColor: + // MaterialStateProperty.all( + // Theme.of(context).extension()!.accentColorDark + // ), + // ), + // child: Text( + // "fire test notification", + // style: STextStyles.button(context), + // ), + // onPressed: () async { + // NotificationApi.showNotification2( + // title: "Test notification", + // body: "My doggy wallet", + // walletId: + // "3c5e2d70-fcc3-11ec-86a3-31a106a81c3b", + // iconAssetName: + // Assets.svg.iconFor(coin: Coin.dogecoin), + // date: DateTime.now(), + // ); + // }, + // ), + ], + ), ), - ), - const SizedBox( - height: 12, - ), - ], + const SizedBox( + height: 12, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index 6b6377ab6..7f35fcc86 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -5,9 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.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/rounded_white_container.dart'; class HiddenSettings extends StatelessWidget { @@ -17,149 +17,151 @@ class HiddenSettings extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: Container(), - title: Text( - "Not so secret anymore", - style: STextStyles.navBarTitle(context), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: Container(), + title: Text( + "Not so secret anymore", + style: STextStyles.navBarTitle(context), + ), ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Consumer(builder: (_, ref, __) { - return GestureDetector( - onTap: () async { - final notifs = - ref.read(notificationsProvider).notifications; + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Consumer(builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + final notifs = + ref.read(notificationsProvider).notifications; - for (final n in notifs) { + for (final n in notifs) { + await ref + .read(notificationsProvider) + .delete(n, false); + } await ref .read(notificationsProvider) - .delete(n, false); - } - await ref - .read(notificationsProvider) - .delete(notifs[0], true); + .delete(notifs[0], true); - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Notification history deleted", - context: context, - )); - }, - child: RoundedWhiteContainer( - child: Text( - "Delete notifications", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + unawaited(showFloatingFlushBar( + type: FlushBarType.success, + message: "Notification history deleted", + context: context, + )); + }, + child: RoundedWhiteContainer( + child: Text( + "Delete notifications", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), ), - ), - ); - }), - // const SizedBox( - // height: 12, - // ), - // Consumer(builder: (_, ref, __) { - // return GestureDetector( - // onTap: () async { - // final trades = - // ref.read(tradesServiceProvider).trades; - // - // for (final trade in trades) { - // ref.read(tradesServiceProvider).delete( - // trade: trade, shouldNotifyListeners: false); - // } - // ref.read(tradesServiceProvider).delete( - // trade: trades[0], shouldNotifyListeners: true); - // - // // ref.read(notificationsProvider).DELETE_EVERYTHING(); - // }, - // child: RoundedWhiteContainer( - // child: Text( - // "Delete trade history", - // style: STextStyles.button(context).copyWith( - // color: Theme.of(context).extension()!.accentColorDark - // ), - // ), - // ), - // ); - // }), - const SizedBox( - height: 12, - ), - Consumer(builder: (_, ref, __) { - return GestureDetector( - onTap: () async { - await ref - .read(debugServiceProvider) - .deleteAllMessages(); + ); + }), + // const SizedBox( + // height: 12, + // ), + // Consumer(builder: (_, ref, __) { + // return GestureDetector( + // onTap: () async { + // final trades = + // ref.read(tradesServiceProvider).trades; + // + // for (final trade in trades) { + // ref.read(tradesServiceProvider).delete( + // trade: trade, shouldNotifyListeners: false); + // } + // ref.read(tradesServiceProvider).delete( + // trade: trades[0], shouldNotifyListeners: true); + // + // // ref.read(notificationsProvider).DELETE_EVERYTHING(); + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Delete trade history", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context).extension()!.accentColorDark + // ), + // ), + // ), + // ); + // }), + const SizedBox( + height: 12, + ), + Consumer(builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + await ref + .read(debugServiceProvider) + .deleteAllMessages(); - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Debug Logs deleted", - context: context, - )); - }, - child: RoundedWhiteContainer( - child: Text( - "Delete Debug Logs", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + unawaited(showFloatingFlushBar( + type: FlushBarType.success, + message: "Debug Logs deleted", + context: context, + )); + }, + child: RoundedWhiteContainer( + child: Text( + "Delete Debug Logs", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), ), - ), - ); - }), - // const SizedBox( - // height: 12, - // ), - // GestureDetector( - // onTap: () async { - // showDialog( - // context: context, - // builder: (_) { - // return StackDialogBase( - // child: SizedBox( - // width: 200, - // child: Lottie.asset( - // Assets.lottie.test2, - // ), - // ), - // ); - // }, - // ); - // }, - // child: RoundedWhiteContainer( - // child: Text( - // "Lottie test", - // style: STextStyles.button(context).copyWith( - // color: Theme.of(context).extension()!.accentColorDark - // ), - // ), - // ), - // ), - ], + ); + }), + // const SizedBox( + // height: 12, + // ), + // GestureDetector( + // onTap: () async { + // showDialog( + // context: context, + // builder: (_) { + // return StackDialogBase( + // child: SizedBox( + // width: 200, + // child: Lottie.asset( + // Assets.lottie.test2, + // ), + // ), + // ); + // }, + // ); + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Lottie test", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context).extension()!.accentColorDark + // ), + // ), + // ), + // ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/language_view.dart b/lib/pages/settings_views/global_settings_view/language_view.dart index b617546e4..9ba2d6dd2 100644 --- a/lib/pages/settings_views/global_settings_view/language_view.dart +++ b/lib/pages/settings_views/global_settings_view/language_view.dart @@ -7,14 +7,14 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/languages_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class LanguageSettingsView extends ConsumerStatefulWidget { const LanguageSettingsView({Key? key}) : super(key: key); @@ -100,203 +100,205 @@ class _LanguageViewState extends ConsumerState { listWithoutSelected.insert(0, current); } listWithoutSelected = _filtered(); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Language", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Language", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, - ), - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: - NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(bottom: 16), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => filter = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: + NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(bottom: 16), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => filter = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - filter = ""; - }); - }, - ), - ], + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + filter = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), ), ), ), - ), - ]; - }, - body: Builder( - builder: (context) { - return CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - context, + ]; + }, + body: Builder( + builder: (context) { + return CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor( + context, + ), ), - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: _borderRadius(index), - ), - child: Padding( - padding: const EdgeInsets.all(4), - key: Key( - "languageSelect_${listWithoutSelected[index]}"), - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: index == 0 - ? Theme.of(context) - .extension()! - .currencyListItemBG - : Theme.of(context) - .extension()! - .popupBG, - child: RawMaterialButton( - onPressed: () async { - onTap(index); - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: _borderRadius(index), + ), + child: Padding( + padding: const EdgeInsets.all(4), + key: Key( + "languageSelect_${listWithoutSelected[index]}"), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: index == 0 + ? Theme.of(context) + .extension()! + .currencyListItemBG + : Theme.of(context) + .extension()! + .popupBG, + child: RawMaterialButton( + onPressed: () async { + onTap(index); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: true, - groupValue: index == 0, - onChanged: (_) { - onTap(index); - }, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: true, + groupValue: index == 0, + onChanged: (_) { + onTap(index); + }, + ), ), - ), - const SizedBox( - width: 12, - ), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - listWithoutSelected[index], - key: (index == 0) - ? const Key( - "selectedLanguageSettingsLanguageText") - : null, - style: STextStyles.largeMedium14( - context), - ), - const SizedBox( - height: 2, - ), - Text( - listWithoutSelected[index], - key: (index == 0) - ? const Key( - "selectedLanguageSettingsLanguageTextDescription") - : null, - style: STextStyles.itemSubtitle( - context), - ), - ], - ), - ], + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + listWithoutSelected[index], + key: (index == 0) + ? const Key( + "selectedLanguageSettingsLanguageText") + : null, + style: STextStyles.largeMedium14( + context), + ), + const SizedBox( + height: 2, + ), + Text( + listWithoutSelected[index], + key: (index == 0) + ? const Key( + "selectedLanguageSettingsLanguageTextDescription") + : null, + style: STextStyles.itemSubtitle( + context), + ), + ], + ), + ], + ), ), ), ), ), - ), - ); - }, - childCount: listWithoutSelected.length, + ); + }, + childCount: listWithoutSelected.length, + ), ), - ), - ], - ); - }, + ], + ); + }, + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 9062314f0..bd6e5c6d8 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -12,7 +12,6 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; @@ -20,6 +19,7 @@ import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -238,7 +238,8 @@ class _AddEditNodeViewState extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - buttonHeight: ButtonHeight.l, + buttonHeight: + isDesktop ? ButtonHeight.l : null, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -251,7 +252,8 @@ class _AddEditNodeViewState extends ConsumerState { Expanded( child: PrimaryButton( label: "Save", - buttonHeight: ButtonHeight.l, + buttonHeight: + isDesktop ? ButtonHeight.l : null, onPressed: () => Navigator.of( context, rootNavigator: true, @@ -409,84 +411,90 @@ class _AddEditNodeViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node", - style: STextStyles.navBarTitle(context), - ), - actions: [ - if (viewType == AddEditNodeViewType.edit) - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("deleteNodeAppBarButtonKey"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.trash, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node", + style: STextStyles.navBarTitle(context), + ), + actions: [ + if (viewType == AddEditNodeViewType.edit) + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("deleteNodeAppBarButtonKey"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () async { - Navigator.popUntil(context, - ModalRoute.withName(widget.routeOnSuccessOrDelete)); + .background, + icon: SvgPicture.asset( + Assets.svg.trash, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () async { + Navigator.popUntil(context, + ModalRoute.withName(widget.routeOnSuccessOrDelete)); - await ref.read(nodeServiceChangeNotifierProvider).delete( - nodeId!, - true, - ); - }, - ), - ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 12, - right: 12, - bottom: 12, - ), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: constraints.maxHeight - 8), - child: IntrinsicHeight( - child: child, + await ref + .read(nodeServiceChangeNotifierProvider) + .delete( + nodeId!, + true, + ); + }, ), ), ), - ); - }, + ], + ), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + bottom: 12, + ), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight - 8), + child: IntrinsicHeight( + child: child, + ), + ), + ), + ); + }, + ), ), ), ), @@ -562,7 +570,7 @@ class _AddEditNodeViewState extends ConsumerState { child: SecondaryButton( label: "Test connection", enabled: testConnectionEnabled, - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: testConnectionEnabled ? () async { await _testConnection(); diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart index a93b64be3..91e6871f3 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -122,66 +123,70 @@ class _CoinNodesViewState extends ConsumerState { ), ); } else { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "${widget.coin.prettyName} nodes", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("manageNodesAddNewNodeButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.plus, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "${widget.coin.prettyName} nodes", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("manageNodesAddNewNodeButtonKey"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddEditNodeView.routeName, + arguments: Tuple4( + AddEditNodeViewType.add, + widget.coin, + null, + CoinNodesView.routeName, + ), + ); + }, ), - onPressed: () { - Navigator.of(context).pushNamed( - AddEditNodeView.routeName, - arguments: Tuple4( - AddEditNodeViewType.add, - widget.coin, - null, - CoinNodesView.routeName, - ), - ); - }, ), ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 12, - right: 12, + ], ), - child: SingleChildScrollView( - child: NodesList( - coin: widget.coin, - popBackToRoute: CoinNodesView.routeName, + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: SingleChildScrollView( + child: NodesList( + coin: widget.coin, + popBackToRoute: CoinNodesView.routeName, + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart index 22f239232..743fc6957 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.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'; @@ -47,88 +48,91 @@ class _ManageNodesViewState extends ConsumerState { ? _coins : _coins.sublist(0, _coins.length - kTestNetCoinCount); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Manage nodes", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Manage nodes", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 12, - right: 12, - ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ...coins.map( - (coin) { - final count = ref - .watch(nodeServiceChangeNotifierProvider - .select((value) => value.getNodesFor(coin))) - .length; + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...coins.map( + (coin) { + final count = ref + .watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))) + .length; - return Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + return Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () { - Navigator.of(context).pushNamed( - CoinNodesView.routeName, - arguments: coin, - ); - }, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 24, - height: 24, - ), - const SizedBox( - width: 12, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${coin.prettyName} nodes", - style: STextStyles.titleBold12(context), - ), - Text( - count > 1 ? "$count nodes" : "Default", - style: STextStyles.label(context), - ), - ], - ) - ], + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + CoinNodesView.routeName, + arguments: coin, + ); + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${coin.prettyName} nodes", + style: STextStyles.titleBold12(context), + ), + Text( + count > 1 ? "$count nodes" : "Default", + style: STextStyles.label(context), + ), + ], + ) + ], + ), ), ), ), - ), - ); - }, - ), - ], + ); + }, + ), + ], + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 71d764135..2e43b5595 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/delete_button.dart'; @@ -179,85 +180,89 @@ class _NodeDetailsViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Node details", - style: STextStyles.navBarTitle(context), - ), - actions: [ - if (!nodeId.startsWith("default")) - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("nodeDetailsEditNodeAppBarButtonKey"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.pencil, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Node details", + style: STextStyles.navBarTitle(context), + ), + actions: [ + if (!nodeId.startsWith("default")) + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("nodeDetailsEditNodeAppBarButtonKey"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .accentColorDark, - width: 20, - height: 20, + .background, + icon: SvgPicture.asset( + Assets.svg.pencil, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddEditNodeView.routeName, + arguments: Tuple4( + AddEditNodeViewType.edit, + coin, + nodeId, + popRouteName, + ), + ); + }, ), - onPressed: () { - Navigator.of(context).pushNamed( - AddEditNodeView.routeName, - arguments: Tuple4( - AddEditNodeViewType.edit, - coin, - nodeId, - popRouteName, - ), - ); - }, ), ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 12, - right: 12, + ], ), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: constraints.maxHeight - 8), - child: IntrinsicHeight( - child: child, + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight - 8), + child: IntrinsicHeight( + child: child, + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart index c88da0521..fb5722594 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.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/custom_pin_put/custom_pin_put.dart'; @@ -65,182 +66,186 @@ class _ChangePinViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), ), - ), - body: SafeArea( - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - // page 1 - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Text( - "Create new PIN", - style: STextStyles.pageTitleH1(context), + body: SafeArea( + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + // page 1 + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Text( + "Create new PIN", + style: STextStyles.pageTitleH1(context), + ), ), - ), - const SizedBox( - height: 52, - ), - CustomPinPut( - fieldsCount: Constants.pinLength, - eachFieldHeight: 12, - eachFieldWidth: 12, - textStyle: STextStyles.label(context).copyWith( - fontSize: 1, + const SizedBox( + height: 52, ), - focusNode: _pinPutFocusNode1, - controller: _pinPutController1, - useNativeKeyboard: false, - obscureText: "", - inputDecoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - fillColor: - Theme.of(context).extension()!.background, - counterText: "", - ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, + CustomPinPut( + fieldsCount: Constants.pinLength, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.label(context).copyWith( + fontSize: 1, + ), + focusNode: _pinPutFocusNode1, + controller: _pinPutController1, + useNativeKeyboard: false, + obscureText: "", + inputDecoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Theme.of(context) + .extension()! + .background, + counterText: "", + ), + submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! .infoItemIcons, + border: Border.all( + width: 1, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, - onSubmit: (String pin) { - if (pin.length == Constants.pinLength) { - _pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - ); - } - }, - ), - ], - ), - - // page 2 - - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Text( - "Confirm new PIN", - style: STextStyles.pageTitleH1(context), - ), - ), - const SizedBox( - height: 52, - ), - CustomPinPut( - fieldsCount: Constants.pinLength, - eachFieldHeight: 12, - eachFieldWidth: 12, - textStyle: STextStyles.infoSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle3, - fontSize: 1, - ), - focusNode: _pinPutFocusNode2, - controller: _pinPutController2, - useNativeKeyboard: false, - obscureText: "", - inputDecoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - fillColor: - Theme.of(context).extension()!.background, - counterText: "", - ), - submittedFieldDecoration: _pinPutDecoration.copyWith( - color: Theme.of(context) - .extension()! - .infoItemIcons, - border: Border.all( - width: 1, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - ), - selectedFieldDecoration: _pinPutDecoration, - followingFieldDecoration: _pinPutDecoration, - onSubmit: (String pin) async { - if (_pinPutController1.text == _pinPutController2.text) { - // This should never fail as we are overwriting the existing pin - assert( - (await _secureStore.read(key: "stack_pin")) != null); - await _secureStore.write(key: "stack_pin", value: pin); - - showFloatingFlushBar( - type: FlushBarType.success, - message: "New PIN is set up", - context: context, - iconAsset: Assets.svg.check, - ); - - await Future.delayed( - const Duration(milliseconds: 1200)); - - if (mounted) { - Navigator.of(context).popUntil( - ModalRoute.withName(SecurityView.routeName), + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration, + onSubmit: (String pin) { + if (pin.length == Constants.pinLength) { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.linear, ); } - } else { - _pageController.animateTo( - 0, - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - ); + }, + ), + ], + ), - showFloatingFlushBar( - type: FlushBarType.warning, - message: "PIN codes do not match. Try again.", - context: context, - iconAsset: Assets.svg.alertCircle, - ); + // page 2 - _pinPutController1.text = ''; - _pinPutController2.text = ''; - } - }, - ), - ], - ), - ], + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Text( + "Confirm new PIN", + style: STextStyles.pageTitleH1(context), + ), + ), + const SizedBox( + height: 52, + ), + CustomPinPut( + fieldsCount: Constants.pinLength, + eachFieldHeight: 12, + eachFieldWidth: 12, + textStyle: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + fontSize: 1, + ), + focusNode: _pinPutFocusNode2, + controller: _pinPutController2, + useNativeKeyboard: false, + obscureText: "", + inputDecoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Theme.of(context) + .extension()! + .background, + counterText: "", + ), + submittedFieldDecoration: _pinPutDecoration.copyWith( + color: Theme.of(context) + .extension()! + .infoItemIcons, + border: Border.all( + width: 1, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration, + onSubmit: (String pin) async { + if (_pinPutController1.text == _pinPutController2.text) { + // This should never fail as we are overwriting the existing pin + assert((await _secureStore.read(key: "stack_pin")) != + null); + await _secureStore.write(key: "stack_pin", value: pin); + + showFloatingFlushBar( + type: FlushBarType.success, + message: "New PIN is set up", + context: context, + iconAsset: Assets.svg.check, + ); + + await Future.delayed( + const Duration(milliseconds: 1200)); + + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName(SecurityView.routeName), + ); + } + } else { + _pageController.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); + + showFloatingFlushBar( + type: FlushBarType.warning, + message: "PIN codes do not match. Try again.", + context: context, + iconAsset: Assets.svg.alertCircle, + ); + + _pinPutController1.text = ''; + _pinPutController2.text = ''; + } + }, + ), + ], + ), + ], + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart index 24fce5cd8..c2a64bb50 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/route_generator.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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -22,128 +23,131 @@ class SecurityView extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Security", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Security", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - routeOnSuccess: ChangePinView.routeName, - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: "Authenticate to change PIN", - biometricsAuthenticationTitle: "Change PIN", - ), - settings: - const RouteSettings(name: "/changepinlockscreen"), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, ), - child: Row( - children: [ - Text( - "Change PIN", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, + onPressed: () { + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + routeOnSuccess: ChangePinView.routeName, + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to change PIN", + biometricsAuthenticationTitle: "Change PIN", + ), + settings: + const RouteSettings(name: "/changepinlockscreen"), ), - ], + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Change PIN", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: null, - // () { - // final useBio = - // ref.read(prefsChangeNotifierProvider).useBiometrics; - // - // debugPrint("useBio: $useBio"); - // ref.read(prefsChangeNotifierProvider).useBiometrics = - // !useBio; - // - // debugPrint( - // "useBio set to: ${ref.read(prefsChangeNotifierProvider).useBiometrics}"); - // }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Enable biometric authentication", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider - .select((value) => value.useBiometrics), - ), - onValueChanged: (newValue) { - ref - .read(prefsChangeNotifierProvider) - .useBiometrics = newValue; - }, + onPressed: null, + // () { + // final useBio = + // ref.read(prefsChangeNotifierProvider).useBiometrics; + // + // debugPrint("useBio: $useBio"); + // ref.read(prefsChangeNotifierProvider).useBiometrics = + // !useBio; + // + // debugPrint( + // "useBio set to: ${ref.read(prefsChangeNotifierProvider).useBiometrics}"); + // }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Enable biometric authentication", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, ), - ), - ], + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider + .select((value) => value.useBiometrics), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .useBiometrics = newValue; + }, + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart index a94375742..15b5a12fa 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart @@ -11,6 +11,8 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.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/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -19,8 +21,6 @@ import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stackwallet/utilities/util.dart'; - class AutoBackupView extends ConsumerStatefulWidget { const AutoBackupView({Key? key}) : super(key: key); @@ -225,239 +225,241 @@ class _AutoBackupViewState extends ConsumerState { frequencyController.text = Format.prettyFrequencyType(next); }); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Auto Backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Auto Backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: null, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Auto Backup", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - key: const Key("autoBackupToggleButtonKey"), - isOn: _toggle, - controller: toggleController, - onValueChanged: (newValue) async { - _toggle = newValue; - - if (_toggle) { - attemptEnable(); - } else { - attemptDisable(); - } - }, - ), - ), - ], - ), - ), - ), - ), - const SizedBox( - height: 8, - ), - if (!isEnabledAutoBackup) + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ RoundedWhiteContainer( - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - style: STextStyles.label(context), - children: [ - const TextSpan( - text: - "Auto Backup is a custom Stack Wallet feature that offers a convenient backup of your data.\n\nTo ensure maximum security, we recommend using a unique password that you haven't used anywhere else on the internet before. Your password is not stored.\n\nFor more information, please see our website "), - TextSpan( - text: "stackwallet.com.", - style: STextStyles.richLink(context), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl( - Uri.parse("https://stackwallet.com"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - ), - if (isEnabledAutoBackup) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RoundedWhiteContainer( + onPressed: null, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BlueTextButton( - text: "Back up now", - onTap: () { - ref.read(autoSWBServiceProvider).doBackup(); - }, - ), Text( - "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}", - style: STextStyles.itemSubtitle(context), - ) + "Auto Backup", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + key: const Key("autoBackupToggleButtonKey"), + isOn: _toggle, + controller: toggleController, + onValueChanged: (newValue) async { + _toggle = newValue; + + if (_toggle) { + attemptEnable(); + } else { + attemptDisable(); + } + }, + ), + ), ], ), ), - const SizedBox( - height: 32, - ), - Text( - "Auto Backup file", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("backupSavedToFileLocationTextFieldKey"), - focusNode: fileLocationFocusNode, - controller: fileLocationController, - enabled: false, - style: STextStyles.field(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark - .withOpacity(0.5), - ), - readOnly: true, - enableSuggestions: false, - autocorrect: false, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: true, - ), - decoration: standardInputDecoration( - "Saved to", - fileLocationFocusNode, - context, - ), - ), - ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("backupPasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, - enabled: false, - style: STextStyles.field(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark - .withOpacity(0.5), - ), - obscureText: true, - enableSuggestions: false, - autocorrect: false, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: true, - ), - decoration: standardInputDecoration( - "Passphrase", - passwordFocusNode, - context, - ), - ), - ), - const SizedBox( - height: 12, - ), - Text( - "Auto Backup frequency", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - key: const Key("backupFrequencyFieldKey"), - controller: frequencyController, - enabled: false, - style: STextStyles.field(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark - .withOpacity(0.5), - ), - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: true, - ), - ), - const SizedBox( - height: 20, - ), - Center( - child: BlueTextButton( - text: "Edit Auto Backup", - onTap: () async { - Navigator.of(context) - .pushNamed(EditAutoBackupView.routeName); - }, - ), - ) - ], + ), ), - ], + const SizedBox( + height: 8, + ), + if (!isEnabledAutoBackup) + RoundedWhiteContainer( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: STextStyles.label(context), + children: [ + const TextSpan( + text: + "Auto Backup is a custom Stack Wallet feature that offers a convenient backup of your data.\n\nTo ensure maximum security, we recommend using a unique password that you haven't used anywhere else on the internet before. Your password is not stored.\n\nFor more information, please see our website "), + TextSpan( + text: "stackwallet.com.", + style: STextStyles.richLink(context), + recognizer: TapGestureRecognizer() + ..onTap = () { + launchUrl( + Uri.parse("https://stackwallet.com"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), + ), + if (isEnabledAutoBackup) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BlueTextButton( + text: "Back up now", + onTap: () { + ref.read(autoSWBServiceProvider).doBackup(); + }, + ), + Text( + "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}", + style: STextStyles.itemSubtitle(context), + ) + ], + ), + ), + const SizedBox( + height: 32, + ), + Text( + "Auto Backup file", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("backupSavedToFileLocationTextFieldKey"), + focusNode: fileLocationFocusNode, + controller: fileLocationController, + enabled: false, + style: STextStyles.field(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark + .withOpacity(0.5), + ), + readOnly: true, + enableSuggestions: false, + autocorrect: false, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: true, + ), + decoration: standardInputDecoration( + "Saved to", + fileLocationFocusNode, + context, + ), + ), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("backupPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + enabled: false, + style: STextStyles.field(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark + .withOpacity(0.5), + ), + obscureText: true, + enableSuggestions: false, + autocorrect: false, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: true, + ), + decoration: standardInputDecoration( + "Passphrase", + passwordFocusNode, + context, + ), + ), + ), + const SizedBox( + height: 12, + ), + Text( + "Auto Backup frequency", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 10, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + key: const Key("backupFrequencyFieldKey"), + controller: frequencyController, + enabled: false, + style: STextStyles.field(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark + .withOpacity(0.5), + ), + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: true, + ), + ), + const SizedBox( + height: 20, + ), + Center( + child: BlueTextButton( + text: "Edit Auto Backup", + onTap: () async { + Navigator.of(context) + .pushNamed(EditAutoBackupView.routeName); + }, + ), + ) + ], + ), + ], + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index bf8bd40e7..8e8731105 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -22,6 +22,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -108,549 +109,559 @@ class _EnableAutoBackupViewState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Create Auto Backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Create Auto Backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder(builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Create your backup file", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - if (!Platform.isAndroid) - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - onTap: Platform.isAndroid - ? null - : () async { - try { - await stackFileSystem.prepareStorage(); - - if (mounted) { - await stackFileSystem.pickDir(context); - } - - if (mounted) { - setState(() { - fileLocationController.text = - stackFileSystem.dirPath ?? ""; - }); - } - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - } - }, - controller: fileLocationController, - style: STextStyles.field(context), - decoration: InputDecoration( - hintText: "Save to...", - hintStyle: STextStyles.fieldLabel(context), - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - SvgPicture.asset( - Assets.svg.folder, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - key: const Key( - "createBackupSaveToFileLocationTextFieldKey"), - readOnly: true, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: false, - ), - onChanged: (newValue) {}, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Create your backup file", + style: STextStyles.smallMed12(context), ), - if (!Platform.isAndroid) const SizedBox( height: 10, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPasswordFieldKey1"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Create passphrase", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - if (newValue.isEmpty) { - setState(() { - passwordFeedback = ""; - }); - return; - } - final result = zxcvbn.evaluate(newValue); - String suggestionsAndTips = ""; - for (var sug - in result.feedback.suggestions!.toSet()) { - suggestionsAndTips += "$sug\n"; - } - suggestionsAndTips += result.feedback.warning!; - String feedback = - // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" - suggestionsAndTips; - - passwordStrength = result.score! / 4; - - // hack fix to format back string returned from zxcvbn - if (feedback.contains("phrasesNo need")) { - feedback = feedback.replaceFirst( - "phrasesNo need", "phrases\nNo need"); - } - - if (feedback.endsWith("\n")) { - feedback = - feedback.substring(0, feedback.length - 2); - } - - setState(() { - passwordFeedback = feedback; - }); - }, - ), - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: passwordFeedback.isNotEmpty ? 4 : 0, - ), - child: passwordFeedback.isNotEmpty - ? Text( - passwordFeedback, - style: STextStyles.infoSmall(context), - ) - : null, - ), - if (passwordFocusNode.hasFocus || - passwordRepeatFocusNode.hasFocus || - passwordController.text.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 12, - top: 10, - ), - child: ProgressBar( - key: const Key("createStackBackUpProgressBar"), - width: MediaQuery.of(context).size.width - 32 - 24, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension()! - .accentColorYellow - : Theme.of(context) - .extension()! - .accentColorGreen, - backgroundColor: Theme.of(context) - .extension()! - .buttonBackSecondary, - percent: - passwordStrength < 0.25 ? 0.03 : passwordStrength, - ), - ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("createBackupPasswordFieldKey2"), - focusNode: passwordRepeatFocusNode, - controller: passwordRepeatController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Confirm passphrase", - passwordRepeatFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "createBackupPasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 16, - height: 16, - ), - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - onChanged: (newValue) { - setState(() {}); - // TODO: ? check if passwords match? - }, - ), - ), - const SizedBox( - height: 32, - ), - Text( - "Auto Backup frequency", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 10, - ), - Stack( - children: [ + if (!Platform.isAndroid) TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Positioned.fill( - child: RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => - const BackupFrequencyTypeSelectSheet(), - ); - }, - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12.0), + onTap: Platform.isAndroid + ? null + : () async { + try { + await stackFileSystem.prepareStorage(); + + if (mounted) { + await stackFileSystem.pickDir(context); + } + + if (mounted) { + setState(() { + fileLocationController.text = + stackFileSystem.dirPath ?? ""; + }); + } + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + } + }, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, children: [ - Text( - Format.prettyFrequencyType(ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.backupFrequencyType))), - style: STextStyles.itemSubtitle12(context), + const SizedBox( + width: 16, ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: SvgPicture.asset( - Assets.svg.chevronDown, - color: Theme.of(context) - .extension()! - .textSubtitle2, - width: 12, - height: 6, - ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, ), ], ), ), ), - ) - ], - ), - const Spacer(), - const SizedBox( - height: 10, - ), - TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: !shouldEnableCreate - ? null - : () async { - final String pathToSave = - fileLocationController.text; - final String passphrase = passwordController.text; - final String repeatPassphrase = - passwordRepeatController.text; + key: const Key( + "createBackupSaveToFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, + ), + if (!Platform.isAndroid) + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Create passphrase", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + if (newValue.isEmpty) { + setState(() { + passwordFeedback = ""; + }); + return; + } + final result = zxcvbn.evaluate(newValue); + String suggestionsAndTips = ""; + for (var sug + in result.feedback.suggestions!.toSet()) { + suggestionsAndTips += "$sug\n"; + } + suggestionsAndTips += result.feedback.warning!; + String feedback = + // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n" + suggestionsAndTips; - if (pathToSave.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory not chosen", - context: context, - ); - return; - } - if (!(await Directory(pathToSave).exists())) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Directory does not exist", - context: context, - ); - return; - } - if (passphrase.isEmpty) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "A passphrase is required", - context: context, - ); - return; - } - if (passphrase != repeatPassphrase) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Passphrase does not match", - context: context, - ); - return; - } + passwordStrength = result.score! / 4; - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting initial backup", - message: "This shouldn't take long", - ), - ); + // hack fix to format back string returned from zxcvbn + if (feedback.contains("phrasesNo need")) { + feedback = feedback.replaceFirst( + "phrasesNo need", "phrases\nNo need"); + } - // make sure the dialog is able to be displayed for at least some time - final fut = Future.delayed( - const Duration(milliseconds: 300)); + if (feedback.endsWith("\n")) { + feedback = + feedback.substring(0, feedback.length - 2); + } - String adkString; - int adkVersion; - try { - final adk = - await compute(generateAdk, passphrase); - adkString = Format.uint8listToString(adk.item2); - adkVersion = adk.item1; - } on Exception catch (e, s) { - String err = getErrorMessageFromSWBException(e); - Logging.instance - .log("$err\n$s", level: LogLevel.Error); - // pop encryption progress dialog - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: err, - context: context, - ); - return; - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - // pop encryption progress dialog - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.warning, - message: "$e", - context: context, - ); - return; - } - - await secureStore.write( - key: "auto_adk_string", value: adkString); - await secureStore.write( - key: "auto_adk_version_string", - value: adkVersion.toString()); - - final DateTime now = DateTime.now(); - final String fileToSave = - createAutoBackupFilename(pathToSave, now); - - final backup = await SWB.createStackWalletJSON( - secureStorage: secureStore, - ); - - bool result = await SWB.encryptStackWalletWithADK( - fileToSave, - adkString, - jsonEncode(backup), - adkVersion: adkVersion, - ); - - // this future should already be complete unless there was an error encrypting - await Future.wait([fut]); - - if (mounted) { - // pop encryption progress dialog - Navigator.of(context).pop(); - - if (result) { - ref - .read(prefsChangeNotifierProvider) - .autoBackupLocation = pathToSave; - ref - .read(prefsChangeNotifierProvider) - .lastAutoBackup = now; - - ref - .read(prefsChangeNotifierProvider) - .isAutoBackupEnabled = true; - - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Platform.isAndroid - ? StackOkDialog( - title: - "Stack Auto Backup enabled and saved to:", - message: fileToSave, - ) - : const StackOkDialog( - title: - "Stack Auto Backup enabled!"), - ); - if (mounted) { - passwordController.text = ""; - passwordRepeatController.text = ""; - - Navigator.of(context).popUntil( - ModalRoute.withName( - AutoBackupView.routeName)); - } - } else { - await showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const StackOkDialog( - title: "Failed to enable Auto Backup"), - ); - } - } - }, - child: Text( - "Enable Auto Backup", - style: STextStyles.button(context), + setState(() { + passwordFeedback = feedback; + }); + }, + ), ), - ), - ], + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: passwordFeedback.isNotEmpty ? 4 : 0, + ), + child: passwordFeedback.isNotEmpty + ? Text( + passwordFeedback, + style: STextStyles.infoSmall(context), + ) + : null, + ), + if (passwordFocusNode.hasFocus || + passwordRepeatFocusNode.hasFocus || + passwordController.text.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 10, + ), + child: ProgressBar( + key: const Key("createStackBackUpProgressBar"), + width: MediaQuery.of(context).size.width - 32 - 24, + height: 5, + fillColor: passwordStrength < 0.51 + ? Theme.of(context) + .extension()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension()! + .accentColorYellow + : Theme.of(context) + .extension()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension()! + .buttonBackSecondary, + percent: passwordStrength < 0.25 + ? 0.03 + : passwordStrength, + ), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("createBackupPasswordFieldKey2"), + focusNode: passwordRepeatFocusNode, + controller: passwordRepeatController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Confirm passphrase", + passwordRepeatFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + GestureDetector( + key: const Key( + "createBackupPasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + onChanged: (newValue) { + setState(() {}); + // TODO: ? check if passwords match? + }, + ), + ), + const SizedBox( + height: 32, + ), + Text( + "Auto Backup frequency", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 10, + ), + Stack( + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + readOnly: true, + textInputAction: TextInputAction.none, + ), + Positioned.fill( + child: RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + const BackupFrequencyTypeSelectSheet(), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + Format.prettyFrequencyType(ref.watch( + prefsChangeNotifierProvider.select( + (value) => + value.backupFrequencyType))), + style: + STextStyles.itemSubtitle12(context), + ), + Padding( + padding: + const EdgeInsets.only(right: 4.0), + child: SvgPicture.asset( + Assets.svg.chevronDown, + color: Theme.of(context) + .extension()! + .textSubtitle2, + width: 12, + height: 6, + ), + ), + ], + ), + ), + ), + ) + ], + ), + const Spacer(), + const SizedBox( + height: 10, + ), + TextButton( + style: shouldEnableCreate + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: !shouldEnableCreate + ? null + : () async { + final String pathToSave = + fileLocationController.text; + final String passphrase = + passwordController.text; + final String repeatPassphrase = + passwordRepeatController.text; + + if (pathToSave.isEmpty) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory not chosen", + context: context, + ); + return; + } + if (!(await Directory(pathToSave).exists())) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Directory does not exist", + context: context, + ); + return; + } + if (passphrase.isEmpty) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "A passphrase is required", + context: context, + ); + return; + } + if (passphrase != repeatPassphrase) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Passphrase does not match", + context: context, + ); + return; + } + + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackDialog( + title: "Encrypting initial backup", + message: "This shouldn't take long", + ), + ); + + // make sure the dialog is able to be displayed for at least some time + final fut = Future.delayed( + const Duration(milliseconds: 300)); + + String adkString; + int adkVersion; + try { + final adk = + await compute(generateAdk, passphrase); + adkString = + Format.uint8listToString(adk.item2); + adkVersion = adk.item1; + } on Exception catch (e, s) { + String err = + getErrorMessageFromSWBException(e); + Logging.instance + .log("$err\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.warning, + message: err, + context: context, + ); + return; + } catch (e, s) { + Logging.instance + .log("$e\n$s", level: LogLevel.Error); + // pop encryption progress dialog + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$e", + context: context, + ); + return; + } + + await secureStore.write( + key: "auto_adk_string", value: adkString); + await secureStore.write( + key: "auto_adk_version_string", + value: adkVersion.toString()); + + final DateTime now = DateTime.now(); + final String fileToSave = + createAutoBackupFilename(pathToSave, now); + + final backup = await SWB.createStackWalletJSON( + secureStorage: secureStore, + ); + + bool result = + await SWB.encryptStackWalletWithADK( + fileToSave, + adkString, + jsonEncode(backup), + adkVersion: adkVersion, + ); + + // this future should already be complete unless there was an error encrypting + await Future.wait([fut]); + + if (mounted) { + // pop encryption progress dialog + Navigator.of(context).pop(); + + if (result) { + ref + .read(prefsChangeNotifierProvider) + .autoBackupLocation = pathToSave; + ref + .read(prefsChangeNotifierProvider) + .lastAutoBackup = now; + + ref + .read(prefsChangeNotifierProvider) + .isAutoBackupEnabled = true; + + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Platform.isAndroid + ? StackOkDialog( + title: + "Stack Auto Backup enabled and saved to:", + message: fileToSave, + ) + : const StackOkDialog( + title: + "Stack Auto Backup enabled!"), + ); + if (mounted) { + passwordController.text = ""; + passwordRepeatController.text = ""; + + Navigator.of(context).popUntil( + ModalRoute.withName( + AutoBackupView.routeName)); + } + } else { + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const StackOkDialog( + title: + "Failed to enable Auto Backup"), + ); + } + } + }, + child: Text( + "Enable Auto Backup", + style: STextStyles.button(context), + ), + ), + ], + ), ), ), - ), - ); - }), + ); + }), + ), ), ); } diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart index 772c446f2..012477a5b 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.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'; @@ -12,75 +13,77 @@ class CreateBackupInfoView extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Create backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Create backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Text( - "Info", - style: STextStyles.pageTitleH2(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + "Info", + style: STextStyles.pageTitleH2(context), + ), ), - ), - const SizedBox( - height: 16, - ), - RoundedWhiteContainer( - child: Text( - // TODO: need info - "{lorem ipsum}", - style: STextStyles.baseXS(context), + const SizedBox( + height: 16, ), - ), - const SizedBox( - height: 16, - ), - const Spacer(), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Navigator.of(context) - .pushNamed(CreateBackupView.routeName); - }, - child: Text( - "Next", - style: STextStyles.button(context), + RoundedWhiteContainer( + child: Text( + // TODO: need info + "{lorem ipsum}", + style: STextStyles.baseXS(context), + ), ), - ), - ], + const SizedBox( + height: 16, + ), + const Spacer(), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.of(context) + .pushNamed(CreateBackupView.routeName); + }, + child: Text( + "Next", + style: STextStyles.button(context), + ), + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index a6241d25a..4d69ce4e9 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -16,6 +16,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -103,41 +104,44 @@ class _RestoreFromFileViewState extends State { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Create backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Create backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), ), - child: IntrinsicHeight( - child: child, - ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 310be9f2b..7187c5311 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -25,6 +25,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -282,34 +283,37 @@ class _EditAutoBackupViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: (child) => Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Edit Auto Backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit Auto Backup", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ); + }), ), ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder(builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: child, - ), - ), - ); - }), - ), ), child: Column( crossAxisAlignment: diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart index 3173bc402..14a262d99 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart @@ -9,9 +9,9 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.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/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -62,204 +62,209 @@ class _RestoreFromEncryptedStringViewState Widget build(BuildContext context) { return WillPopScope( onWillPop: _onWillPop, - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - _onWillPop(); - } - }, + child: Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + _onWillPop(); + } + }, + ), + title: Text( + "Restore from file", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Restore from file", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("restoreFromFilePasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.field(context), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - GestureDetector( - key: const Key( - "restoreFromFilePasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("restoreFromFilePasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( width: 16, - height: 16, ), - ), - const SizedBox( - width: 12, - ), - ], + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), ), ), + onChanged: (newValue) { + setState(() {}); + }, ), - onChanged: (newValue) { - setState(() {}); - }, ), - ), - const SizedBox( - height: 16, - ), - const Spacer(), - TextButton( - style: passwordController.text.isEmpty - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: passwordController.text.isEmpty - ? null - : () async { - final String passphrase = - passwordController.text; + const SizedBox( + height: 16, + ), + const Spacer(), + TextButton( + style: passwordController.text.isEmpty + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonColor(context), + onPressed: passwordController.text.isEmpty + ? null + : () async { + final String passphrase = + passwordController.text; - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } - bool shouldPop = false; - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Text( - "Decrypting Stack backup file", - style: STextStyles.pageTitleH2( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textWhite, + bool shouldPop = false; + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: + STextStyles.pageTitleH2( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textWhite, + ), ), ), ), - ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, + const SizedBox( + height: 64, ), - ), - ], - ), - ), - ); - - final String? jsonString = await compute( - SWB.decryptStackWalletStringWithPassphrase, - Tuple2(widget.encrypted, passphrase), - debugLabel: - "stack wallet decryption compute", - ); - - if (mounted) { - // pop LoadingIndicator - shouldPop = true; - Navigator.of(context).pop(); - - passwordController.text = ""; - - if (jsonString == null) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Failed to decrypt backup file", - context: context, - ); - return; - } - - Navigator.of(context).push( - RouteGenerator.getRoute( - builder: (_) => - StackRestoreProgressView( - jsonString: jsonString, - fromFile: true, + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], ), ), ); - } - }, - child: Text( - "Restore", - style: STextStyles.button(context), + + final String? jsonString = await compute( + SWB.decryptStackWalletStringWithPassphrase, + Tuple2(widget.encrypted, passphrase), + debugLabel: + "stack wallet decryption compute", + ); + + if (mounted) { + // pop LoadingIndicator + shouldPop = true; + Navigator.of(context).pop(); + + passwordController.text = ""; + + if (jsonString == null) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Failed to decrypt backup file", + context: context, + ); + return; + } + + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => + StackRestoreProgressView( + jsonString: jsonString, + fromFile: true, + ), + ), + ); + } + }, + child: Text( + "Restore", + style: STextStyles.button(context), + ), ), - ), - ], + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index d6571967d..6350feb52 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -75,42 +76,44 @@ class _RestoreFromFileViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Restore from file", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Restore from file", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), ), - child: IntrinsicHeight( - child: child, - ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart index 679043fe7..fe163cb66 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/assets.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'; @@ -21,147 +22,149 @@ class StackBackupView extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Stack backup", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Stack backup", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: () { - Navigator.of(context).pushNamed(AutoBackupView.routeName); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.backupAuto, - height: 28, - width: 28, - ), - const SizedBox( - width: 12, - ), - Text( - "Auto Backup", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], + onPressed: () { + Navigator.of(context).pushNamed(AutoBackupView.routeName); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.backupAuto, + height: 28, + width: 28, + ), + const SizedBox( + width: 12, + ), + Text( + "Auto Backup", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: () { - Navigator.of(context).pushNamed(CreateBackupView.routeName); - // .pushNamed(CreateBackupInfoView.routeName); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.backupAdd, - height: 28, - width: 28, - ), - const SizedBox( - width: 12, - ), - Text( - "Create manual backup", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], + onPressed: () { + Navigator.of(context).pushNamed(CreateBackupView.routeName); + // .pushNamed(CreateBackupInfoView.routeName); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.backupAdd, + height: 28, + width: 28, + ), + const SizedBox( + width: 12, + ), + Text( + "Create manual backup", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - onPressed: () { - Navigator.of(context) - .pushNamed(RestoreFromFileView.routeName); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.backupRestore, - height: 28, - width: 28, - ), - const SizedBox( - width: 12, - ), - Text( - "Restore backup", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - ], + onPressed: () { + Navigator.of(context) + .pushNamed(RestoreFromFileView.routeName); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.backupRestore, + height: 28, + width: 28, + ), + const SizedBox( + width: 12, + ), + Text( + "Restore backup", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart index baf649ba2..186b9b293 100644 --- a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart +++ b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/providers/global/prefs_provider.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'; @@ -22,255 +23,263 @@ class _StartupPreferencesViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Startup preferences", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Startup preferences", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(4.0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(4.0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup = false; + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: false, + groupValue: ref.watch( + prefsChangeNotifierProvider + .select((value) => value + .gotoWalletOnStartup), + ), + onChanged: (value) { + if (value is bool) { + ref + .read( + prefsChangeNotifierProvider) + .gotoWalletOnStartup = value; + } + }, + ), + ), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Home screen", + style: + STextStyles.titleBold12( + context), + textAlign: TextAlign.left, + ), + Text( + "Stack Wallet home screen", + style: + STextStyles.itemSubtitle( + context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ], + ), + ), ), ), - onPressed: () { - ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup = false; - }, - child: Container( + ), + Padding( + padding: const EdgeInsets.all(4), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup = true; + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: true, + groupValue: ref.watch( + prefsChangeNotifierProvider + .select((value) => value + .gotoWalletOnStartup), + ), + onChanged: (value) { + if (value is bool) { + ref + .read( + prefsChangeNotifierProvider) + .gotoWalletOnStartup = value; + } + }, + ), + ), + const SizedBox( + width: 12, + ), + Flexible( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Specific wallet", + style: + STextStyles.titleBold12( + context), + textAlign: TextAlign.left, + ), + Text( + "Select a specific wallet to load into on startup", + style: + STextStyles.itemSubtitle( + context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + if (!ref.watch(prefsChangeNotifierProvider.select( + (value) => value.gotoWalletOnStartup))) + const SizedBox( + height: 12, + ), + if (ref.watch(prefsChangeNotifierProvider.select( + (value) => value.gotoWalletOnStartup))) + Container( color: Colors.transparent, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.only( + left: 12.0, + right: 12, + bottom: 12, + ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: false, - groupValue: ref.watch( - prefsChangeNotifierProvider - .select((value) => value - .gotoWalletOnStartup), - ), - onChanged: (value) { - if (value is bool) { - ref - .read( - prefsChangeNotifierProvider) - .gotoWalletOnStartup = value; - } - }, - ), - ), const SizedBox( - width: 12, + width: 12 + 20, + height: 12, ), Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Home screen", - style: STextStyles.titleBold12( - context), - textAlign: TextAlign.left, + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: + MaterialTapTargetSize + .shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + Constants + .size.circularBorderRadius, ), - Text( - "Stack Wallet home screen", - style: STextStyles.itemSubtitle( - context), - textAlign: TextAlign.left, - ), - ], + ), + onPressed: () { + Navigator.of(context).pushNamed( + StartupWalletSelectionView + .routeName); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Select wallet...", + style: STextStyles.link2( + context), + textAlign: TextAlign.left, + ), + ], + ), ), ), ], ), ), ), - ), - ), - Padding( - padding: const EdgeInsets.all(4), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup = true; - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: true, - groupValue: ref.watch( - prefsChangeNotifierProvider - .select((value) => value - .gotoWalletOnStartup), - ), - onChanged: (value) { - if (value is bool) { - ref - .read( - prefsChangeNotifierProvider) - .gotoWalletOnStartup = value; - } - }, - ), - ), - const SizedBox( - width: 12, - ), - Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Specific wallet", - style: STextStyles.titleBold12( - context), - textAlign: TextAlign.left, - ), - Text( - "Select a specific wallet to load into on startup", - style: STextStyles.itemSubtitle( - context), - textAlign: TextAlign.left, - ), - ], - ), - ), - ], - ), - ), - ), - ), - ), - if (!ref.watch(prefsChangeNotifierProvider - .select((value) => value.gotoWalletOnStartup))) - const SizedBox( - height: 12, - ), - if (ref.watch(prefsChangeNotifierProvider - .select((value) => value.gotoWalletOnStartup))) - Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.only( - left: 12.0, - right: 12, - bottom: 12, - ), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox( - width: 12 + 20, - height: 12, - ), - Flexible( - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants - .size.circularBorderRadius, - ), - ), - onPressed: () { - Navigator.of(context).pushNamed( - StartupWalletSelectionView - .routeName); - }, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Select wallet...", - style: - STextStyles.link2(context), - textAlign: TextAlign.left, - ), - ], - ), - ), - ), - ], - ), - ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart index 5d9f2edb1..975e8394d 100644 --- a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart +++ b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/utilities/assets.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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -33,170 +34,173 @@ class _StartupWalletSelectionViewState _controllers[manager.walletId] = DSBController(); } - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "Select startup wallet", - style: STextStyles.navBarTitle(context), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "Select startup wallet", + style: STextStyles.navBarTitle(context), + ), ), ), - ), - body: LayoutBuilder(builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 4, - ), - Text( - "Select a wallet to load into immediately on startup", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: Column( - children: [ - ...managers.map( - (manager) => Padding( - padding: const EdgeInsets.all(12), - child: Row( - key: Key( - "startupWalletSelectionGroupKey_${manager.walletId}"), - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .colorForCoin(manager.coin) - .withOpacity(0.5), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg - .iconFor(coin: manager.coin), - width: 20, - height: 20, - ), - ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - manager.walletName, - style: STextStyles.titleBold12( - context), + body: LayoutBuilder(builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 4, + ), + Text( + "Select a wallet to load into immediately on startup", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + ...managers.map( + (manager) => Padding( + padding: const EdgeInsets.all(12), + child: Row( + key: Key( + "startupWalletSelectionGroupKey_${manager.walletId}"), + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .colorForCoin(manager.coin) + .withOpacity(0.5), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg + .iconFor(coin: manager.coin), + width: 20, + height: 20, ), - // const SizedBox( - // height: 2, - // ), - // FutureBuilder( - // future: manager.totalBalance, - // builder: (builderContext, - // AsyncSnapshot snapshot) { - // if (snapshot.connectionState == - // ConnectionState.done && - // snapshot.hasData) { - // return Text( - // "${Format.localizedStringAsFixed( - // value: snapshot.data!, - // locale: ref.watch( - // localeServiceChangeNotifierProvider - // .select((value) => - // value.locale)), - // decimalPlaces: 8, - // )} ${manager.coin.ticker}", - // style: STextStyles.itemSubtitle(context), - // ); - // } else { - // return AnimatedText( - // stringsToLoopThrough: const [ - // "Loading balance", - // "Loading balance.", - // "Loading balance..", - // "Loading balance..." - // ], - // style: STextStyles.itemSubtitle(context), - // ); - // } - // }, - // ), - ], - ), - ), - SizedBox( - height: 20, - width: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: manager.walletId, - groupValue: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.startupWalletId), ), - onChanged: (value) { - if (value is String) { - ref - .read( - prefsChangeNotifierProvider) - .startupWalletId = value; - } - }, ), - ), - ], + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + manager.walletName, + style: STextStyles.titleBold12( + context), + ), + // const SizedBox( + // height: 2, + // ), + // FutureBuilder( + // future: manager.totalBalance, + // builder: (builderContext, + // AsyncSnapshot snapshot) { + // if (snapshot.connectionState == + // ConnectionState.done && + // snapshot.hasData) { + // return Text( + // "${Format.localizedStringAsFixed( + // value: snapshot.data!, + // locale: ref.watch( + // localeServiceChangeNotifierProvider + // .select((value) => + // value.locale)), + // decimalPlaces: 8, + // )} ${manager.coin.ticker}", + // style: STextStyles.itemSubtitle(context), + // ); + // } else { + // return AnimatedText( + // stringsToLoopThrough: const [ + // "Loading balance", + // "Loading balance.", + // "Loading balance..", + // "Loading balance..." + // ], + // style: STextStyles.itemSubtitle(context), + // ); + // } + // }, + // ), + ], + ), + ), + SizedBox( + height: 20, + width: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: manager.walletId, + groupValue: ref.watch( + prefsChangeNotifierProvider.select( + (value) => + value.startupWalletId), + ), + onChanged: (value) { + if (value is String) { + ref + .read( + prefsChangeNotifierProvider) + .startupWalletId = value; + } + }, + ), + ), + ], + ), ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), ), - ), - ); - }), + ); + }), + ), ); } } diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index ee00bdb00..1cc3d35a1 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -26,24 +27,26 @@ class SupportView extends StatelessWidget { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Support", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Support", - style: STextStyles.navBarTitle(context), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, ), ), - body: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), ); }, child: Column( diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart index 3681009a9..10a93d7e8 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -24,35 +25,37 @@ class SyncingOptionsView extends ConsumerWidget { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Syncing", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Syncing", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), ), - child: IntrinsicHeight( - child: child, - ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart index e48ebb342..6ce84627e 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.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/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -28,131 +29,136 @@ class SyncingPreferencesView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Syncing preferences", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Syncing preferences", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( padding: const EdgeInsets.all(0), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - Navigator.of(context) - .pushNamed(SyncingOptionsView.routeName); - }, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Syncing", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - Text( - _currentTypeDescription(ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.syncType))), - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ) - ], - ), - const Spacer(), - ], - ), - ), - ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onPressed: null, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "AutoSync only on Wi-Fi", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.wifiOnly), - ), - onValueChanged: (newValue) { - ref - .read(prefsChangeNotifierProvider) - .wifiOnly = newValue; - }, + ), + onPressed: () { + Navigator.of(context) + .pushNamed(SyncingOptionsView.routeName); + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Syncing", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, ), - ), - ], - ), + Text( + _currentTypeDescription(ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.syncType))), + style: + STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ) + ], + ), + const Spacer(), + ], ), - ); - }, + ), + ), ), - ), - ], + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "AutoSync only on Wi-Fi", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.wifiOnly), + ), + onValueChanged: (newValue) { + ref + .read( + prefsChangeNotifierProvider) + .wifiOnly = newValue; + }, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart index 9724356a1..7cbc86b7a 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart @@ -12,6 +12,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -31,30 +32,32 @@ class WalletSyncingOptionsView extends ConsumerWidget { return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "Sync only selected wallets at startup", - style: STextStyles.navBarTitle(context), + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "Sync only selected wallets at startup", + style: STextStyles.navBarTitle(context), + ), ), ), - ), - body: Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, + body: Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: child, ), - child: child, ), ); }, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart index 3d557d245..a9235172f 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.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/stack_dialog.dart'; @@ -35,186 +36,188 @@ class WalletBackupView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Wallet backup", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.all(10), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - color: Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.copy, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: () async { - await clipboardInterface - .setData(ClipboardData(text: mnemonic.join(" "))); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - )); - }, - ), - ), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, ), - ], - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox( - height: 4, - ), - Text( - ref - .watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletId))) - .walletName, - textAlign: TextAlign.center, - style: STextStyles.label(context).copyWith( - fontSize: 12, - ), - ), - const SizedBox( - height: 4, - ), - Text( - "Recovery Phrase", - textAlign: TextAlign.center, - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 16, - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Please write down your backup key. Keep it safe and never share it with anyone. Your backup key is the only way you can access your funds if you forget your PIN, lose your phone, etc.\n\nStack Wallet does not keep nor is able to restore your backup key. Only you have access to your wallet.", - style: STextStyles.label(context), + title: Text( + "Wallet backup", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.all(10), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.copy, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () async { + await clipboardInterface + .setData(ClipboardData(text: mnemonic.join(" "))); + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + )); + }, ), ), ), - const SizedBox( - height: 8, - ), - Expanded( - child: SingleChildScrollView( - child: MnemonicTable( - words: mnemonic, - isDesktop: false, + ], + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 4, + ), + Text( + ref + .watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))) + .walletName, + textAlign: TextAlign.center, + style: STextStyles.label(context).copyWith( + fontSize: 12, ), ), - ), - const SizedBox( - height: 12, - ), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - String data = AddressUtils.encodeQRSeedData(mnemonic); + const SizedBox( + height: 4, + ), + Text( + "Recovery Phrase", + textAlign: TextAlign.center, + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 16, + ), + Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + "Please write down your backup key. Keep it safe and never share it with anyone. Your backup key is the only way you can access your funds if you forget your PIN, lose your phone, etc.\n\nStack Wallet does not keep nor is able to restore your backup key. Only you have access to your wallet.", + style: STextStyles.label(context), + ), + ), + ), + const SizedBox( + height: 8, + ), + Expanded( + child: SingleChildScrollView( + child: MnemonicTable( + words: mnemonic, + isDesktop: false, + ), + ), + ), + const SizedBox( + height: 12, + ), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + String data = AddressUtils.encodeQRSeedData(mnemonic); - showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (_) { - final width = MediaQuery.of(context).size.width / 2; - return StackDialogBase( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Text( - "Recovery phrase QR code", - style: STextStyles.pageTitleH2(context), - ), - ), - const SizedBox( - height: 12, - ), - Center( - child: RepaintBoundary( - // key: _qrKey, - child: SizedBox( - width: width + 20, - height: width + 20, - child: QrImage( - data: data, - size: width, - backgroundColor: Theme.of(context) - .extension()! - .popupBG, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark), + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (_) { + final width = MediaQuery.of(context).size.width / 2; + return StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + "Recovery phrase QR code", + style: STextStyles.pageTitleH2(context), ), ), - ), - const SizedBox( - height: 12, - ), - Center( - child: SizedBox( - width: width, - child: TextButton( - onPressed: () async { - // await _capturePng(true); - Navigator.of(context).pop(); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) + const SizedBox( + height: 12, + ), + Center( + child: RepaintBoundary( + // key: _qrKey, + child: SizedBox( + width: width + 20, + height: width + 20, + child: QrImage( + data: data, + size: width, + backgroundColor: Theme.of(context) + .extension()! + .popupBG, + foregroundColor: Theme.of(context) .extension()! .accentColorDark), ), ), ), - ), - ], - ), - ); - }, - ); - }, - child: Text( - "Show QR Code", - style: STextStyles.button(context), + const SizedBox( + height: 12, + ), + Center( + child: SizedBox( + width: width, + child: TextButton( + onPressed: () async { + // await _capturePng(true); + Navigator.of(context).pop(); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + child: Text( + "Show QR Code", + style: STextStyles.button(context), + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 3044467aa..7e29010b1 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -26,6 +26,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -312,118 +313,122 @@ class _WalletNetworkSettingsViewState return ConditionalParent( condition: !isDesktop, builder: (child) { - return Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Network", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletNetworkSettingsAddNewNodeViewButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.verticalEllipsis, + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Network", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key( + "walletNetworkSettingsAddNewNodeViewButton"), + size: 36, + shadows: const [], color: Theme.of(context) .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - showDialog( - barrierColor: Colors.transparent, - barrierDismissible: true, - context: context, - builder: (_) { - return Stack( - children: [ - Positioned( - top: 9, - right: 10, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius), - // boxShadow: [CFColors.standardBoxShadow], - boxShadow: const [], - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - Navigator.of(context).pop(); - showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return ConfirmFullRescanDialog( - onConfirm: _attemptRescan, - ); - }, - ); - }, - child: RoundedWhiteContainer( - child: Material( - color: Colors.transparent, - child: Text( - "Rescan blockchain", - style: - STextStyles.baseXS(context), + .background, + icon: SvgPicture.asset( + Assets.svg.verticalEllipsis, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + showDialog( + barrierColor: Colors.transparent, + barrierDismissible: true, + context: context, + builder: (_) { + return Stack( + children: [ + Positioned( + top: 9, + right: 10, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius), + // boxShadow: [CFColors.standardBoxShadow], + boxShadow: const [], + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return ConfirmFullRescanDialog( + onConfirm: _attemptRescan, + ); + }, + ); + }, + child: RoundedWhiteContainer( + child: Material( + color: Colors.transparent, + child: Text( + "Rescan blockchain", + style: + STextStyles.baseXS(context), + ), ), ), ), - ), - ], + ], + ), ), ), - ), - ], - ); - }, - ); - }, + ], + ); + }, + ); + }, + ), ), ), - ), - ], - ), - body: Padding( - padding: EdgeInsets.only( - top: 12, - left: _padding, - right: _padding, + ], ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - child, - ], + body: Padding( + padding: EdgeInsets.only( + top: 12, + left: _padding, + right: _padding, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + child, + ], + ), ), ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 2d8909245..92b712111 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -23,15 +23,14 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_ import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.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:tuple/tuple.dart'; -import 'package:stackwallet/utilities/util.dart'; - /// [eventBus] should only be set during testing class WalletSettingsView extends StatefulWidget { const WalletSettingsView({ @@ -134,196 +133,199 @@ class _WalletSettingsViewState extends State { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Settings", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Settings", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - SettingsListButton( - iconAssetName: Assets.svg.addressBook, - iconSize: 16, - title: "Address book", - onPressed: () { - Navigator.of(context).pushNamed( - AddressBookView.routeName, - arguments: coin, - ); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.node, - iconSize: 16, - title: "Network", - onPressed: () { - Navigator.of(context).pushNamed( - WalletNetworkSettingsView.routeName, - arguments: Tuple3( - walletId, - _currentSyncStatus, - widget.initialNodeStatus, - ), - ); - }, - ), - const SizedBox( - height: 8, - ), - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.lock, - iconSize: 16, - title: "Wallet backup", - onPressed: () async { - final mnemonic = await ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .mnemonic; + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: Column( + children: [ + SettingsListButton( + iconAssetName: Assets.svg.addressBook, + iconSize: 16, + title: "Address book", + onPressed: () { + Navigator.of(context).pushNamed( + AddressBookView.routeName, + arguments: coin, + ); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.node, + iconSize: 16, + title: "Network", + onPressed: () { + Navigator.of(context).pushNamed( + WalletNetworkSettingsView.routeName, + arguments: Tuple3( + walletId, + _currentSyncStatus, + widget.initialNodeStatus, + ), + ); + }, + ), + const SizedBox( + height: 8, + ), + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.lock, + iconSize: 16, + title: "Wallet backup", + onPressed: () async { + final mnemonic = await ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .mnemonic; - if (mounted) { - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (_) => LockscreenView( - routeOnSuccessArguments: - Tuple2(walletId, mnemonic), - showBackButton: true, - routeOnSuccess: - WalletBackupView.routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to view recovery phrase", - biometricsAuthenticationTitle: - "View recovery phrase", + if (mounted) { + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator + .useMaterialPageRoute, + builder: (_) => LockscreenView( + routeOnSuccessArguments: + Tuple2(walletId, mnemonic), + showBackButton: true, + routeOnSuccess: + WalletBackupView.routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to view recovery phrase", + biometricsAuthenticationTitle: + "View recovery phrase", + ), + settings: const RouteSettings( + name: + "/viewRecoverPhraseLockscreen"), ), - settings: const RouteSettings( - name: - "/viewRecoverPhraseLockscreen"), - ), - ); - } - }, - ); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.downloadFolder, - title: "Wallet settings", - iconSize: 16, - onPressed: () { - Navigator.of(context).pushNamed( - WalletSettingsWalletSettingsView.routeName, - arguments: walletId, - ); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.arrowRotate3, - title: "Syncing preferences", - onPressed: () { - Navigator.of(context).pushNamed( - SyncingPreferencesView.routeName); - }, - ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.ellipsis, - title: "Debug Info", - onPressed: () { - Navigator.of(context) - .pushNamed(DebugView.routeName); - }, - ), - ], + ); + } + }, + ); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.downloadFolder, + title: "Wallet settings", + iconSize: 16, + onPressed: () { + Navigator.of(context).pushNamed( + WalletSettingsWalletSettingsView + .routeName, + arguments: walletId, + ); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.arrowRotate3, + title: "Syncing preferences", + onPressed: () { + Navigator.of(context).pushNamed( + SyncingPreferencesView.routeName); + }, + ), + const SizedBox( + height: 8, + ), + SettingsListButton( + iconAssetName: Assets.svg.ellipsis, + title: "Debug Info", + onPressed: () { + Navigator.of(context) + .pushNamed(DebugView.routeName); + }, + ), + ], + ), ), - ), - const SizedBox( - height: 12, - ), - const Spacer(), - Consumer( - builder: (_, ref, __) { - return TextButton( - onPressed: () { - ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .isActiveWallet = false; - ref - .read(transactionFilterProvider.state) - .state = null; + const SizedBox( + height: 12, + ), + const Spacer(), + Consumer( + builder: (_, ref, __) { + return TextButton( + onPressed: () { + ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .isActiveWallet = false; + ref + .read(transactionFilterProvider.state) + .state = null; - Navigator.of(context).popUntil( - ModalRoute.withName(HomeView.routeName), - ); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Log out", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ); - }, - ), - ], + Navigator.of(context).popUntil( + ModalRoute.withName(HomeView.routeName), + ); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Log out", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ); + }, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart index 66d666c20..5543bf1c0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.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/stack_dialog.dart'; @@ -54,164 +55,167 @@ class _DeleteWalletRecoveryPhraseViewState Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - actions: [ - Padding( - padding: const EdgeInsets.all(10), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - color: Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.copy, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: () async { - final words = await _manager.mnemonic; - await _clipboardInterface - .setData(ClipboardData(text: words.join(" "))); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - ), - ), + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, ), - ], - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox( - height: 4, - ), - Text( - _manager.walletName, - textAlign: TextAlign.center, - style: STextStyles.label(context).copyWith( - fontSize: 12, - ), - ), - const SizedBox( - height: 4, - ), - Text( - "Recovery Phrase", - textAlign: TextAlign.center, - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 16, - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", - style: STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: SingleChildScrollView( - child: MnemonicTable( - words: _mnemonic, - isDesktop: false, - ), - ), - ), - const SizedBox( - height: 16, - ), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - showDialog( - barrierDismissible: true, - context: context, - builder: (_) => StackDialog( - title: "Thanks! Your wallet will be deleted.", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: () { - Navigator.pop(context); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () async { - final walletId = _manager.walletId; - final walletsInstance = - ref.read(walletsChangeNotifierProvider); - await ref - .read(walletsServiceChangeNotifierProvider) - .deleteWallet(_manager.walletName, true); - - if (mounted) { - Navigator.of(context).popUntil( - ModalRoute.withName(HomeView.routeName)); - } - - // wait for widget tree to dispose of any widgets watching the manager - await Future.delayed(const Duration(seconds: 1)); - walletsInstance.removeWallet(walletId: walletId); - }, - child: Text( - "Ok", - style: STextStyles.button(context), - ), - ), + actions: [ + Padding( + padding: const EdgeInsets.all(10), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.copy, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, ), - ); - }, - child: Text( - "Continue", - style: STextStyles.button(context), + onPressed: () async { + final words = await _manager.mnemonic; + await _clipboardInterface + .setData(ClipboardData(text: words.join(" "))); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + ), ), ), ], ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 4, + ), + Text( + _manager.walletName, + textAlign: TextAlign.center, + style: STextStyles.label(context).copyWith( + fontSize: 12, + ), + ), + const SizedBox( + height: 4, + ), + Text( + "Recovery Phrase", + textAlign: TextAlign.center, + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 16, + ), + Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + const SizedBox( + height: 8, + ), + Expanded( + child: SingleChildScrollView( + child: MnemonicTable( + words: _mnemonic, + isDesktop: false, + ), + ), + ), + const SizedBox( + height: 16, + ), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + showDialog( + barrierDismissible: true, + context: context, + builder: (_) => StackDialog( + title: "Thanks! Your wallet will be deleted.", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () async { + final walletId = _manager.walletId; + final walletsInstance = + ref.read(walletsChangeNotifierProvider); + await ref + .read(walletsServiceChangeNotifierProvider) + .deleteWallet(_manager.walletName, true); + + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName(HomeView.routeName)); + } + + // wait for widget tree to dispose of any widgets watching the manager + await Future.delayed( + const Duration(seconds: 1)); + walletsInstance.removeWallet(walletId: walletId); + }, + child: Text( + "Ok", + style: STextStyles.button(context), + ), + ), + ), + ); + }, + child: Text( + "Continue", + style: STextStyles.button(context), + ), + ), + ], + ), + ), ), ); } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart index ec8c5a128..f740eee12 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart @@ -4,6 +4,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/providers/providers.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_container.dart'; import 'package:tuple/tuple.dart'; @@ -20,93 +21,96 @@ class DeleteWalletWarningView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), ), - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox( - height: 32, - ), - Center( - child: Text( - "Attention!", - style: STextStyles.pageTitleH1(context), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 32, ), - ), - const SizedBox( - height: 16, - ), - RoundedContainer( - color: - Theme.of(context).extension()!.warningBackground, - child: Text( - "You are going to permanently delete you wallet.\n\nIf you delete your wallet, the only way you can have access to your funds is by using your backup key.\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet.\n\nPLEASE SAVE YOUR BACKUP KEY.", - style: STextStyles.baseXS(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + Center( + child: Text( + "Attention!", + style: STextStyles.pageTitleH1(context), ), ), - ), - const Spacer(), - TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: () { - Navigator.pop(context); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .warningBackground, + child: Text( + "You are going to permanently delete you wallet.\n\nIf you delete your wallet, the only way you can have access to your funds is by using your backup key.\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet.\n\nPLEASE SAVE YOUR BACKUP KEY.", + style: STextStyles.baseXS(context).copyWith( color: Theme.of(context) .extension()! - .accentColorDark), - ), - ), - const SizedBox( - height: 12, - ), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () async { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId); - final mnemonic = await manager.mnemonic; - Navigator.of(context).pushNamed( - DeleteWalletRecoveryPhraseView.routeName, - arguments: Tuple2( - manager, - mnemonic, + .warningForeground, ), - ); - }, - child: Text( - "View Backup Key", - style: STextStyles.button(context), + ), ), - ), - const SizedBox( - height: 16, - ), - ], + const Spacer(), + TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + const SizedBox( + height: 12, + ), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () async { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId); + final mnemonic = await manager.mnemonic; + Navigator.of(context).pushNamed( + DeleteWalletRecoveryPhraseView.routeName, + arguments: Tuple2( + manager, + mnemonic, + ), + ); + }, + child: Text( + "View Backup Key", + style: STextStyles.button(context), + ), + ), + const SizedBox( + height: 16, + ), + ], + ), ), ), ); diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart index e9eb14868..f76422750 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart @@ -6,13 +6,13 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:stackwallet/utilities/util.dart'; - class RenameWalletView extends ConsumerStatefulWidget { const RenameWalletView({ Key? key, @@ -53,102 +53,104 @@ class _RenameWalletViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Rename wallet", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Rename wallet", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _controller, - focusNode: _focusNode, - style: STextStyles.field(context), - onChanged: (_) => setState(() {}), - decoration: standardInputDecoration( - "Wallet name", - _focusNode, - context, - ).copyWith( - suffixIcon: _controller.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _controller.text = ""; - }); - }, - ), - ], + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _controller, + focusNode: _focusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Wallet name", + _focusNode, + context, + ).copyWith( + suffixIcon: _controller.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _controller.text = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), - ), - const Spacer(), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () async { - final newName = _controller.text; - final success = await ref - .read(walletsServiceChangeNotifierProvider) - .renameWallet( - from: originalName, - to: newName, - shouldNotifyListeners: true, - ); + const Spacer(), + TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () async { + final newName = _controller.text; + final success = await ref + .read(walletsServiceChangeNotifierProvider) + .renameWallet( + from: originalName, + to: newName, + shouldNotifyListeners: true, + ); - if (success) { - ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .walletName = newName; - Navigator.of(context).pop(); - showFloatingFlushBar( - type: FlushBarType.success, - message: "Wallet renamed", - context: context, - ); - } else { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Wallet named \"$newName\" already exists", - context: context, - ); - } - }, - child: Text( - "Save", - style: STextStyles.button(context), + if (success) { + ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .walletName = newName; + Navigator.of(context).pop(); + showFloatingFlushBar( + type: FlushBarType.success, + message: "Wallet renamed", + context: context, + ); + } else { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Wallet named \"$newName\" already exists", + context: context, + ); + } + }, + child: Text( + "Save", + style: STextStyles.button(context), + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index 52aa6027a..a9c0f92af 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/route_generator.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/stack_dialog.dart'; @@ -24,149 +25,151 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Wallet settings", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Wallet settings", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, - ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () { - Navigator.of(context).pushNamed( - RenameWalletView.routeName, - arguments: walletId, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 20, - ), - child: Row( - children: [ - Text( - "Rename wallet", - style: STextStyles.titleBold12(context), - ), - ], - ), - ), - ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( padding: const EdgeInsets.all(0), - onPressed: () { - showDialog( - barrierDismissible: true, - context: context, - builder: (_) => StackDialog( - title: - "Do you want to delete ${ref.read(walletsChangeNotifierProvider).getManager(walletId).walletName}?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: () { - Navigator.pop(context); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Navigator.pop(context); - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => LockscreenView( - routeOnSuccessArguments: walletId, - showBackButton: true, - routeOnSuccess: - DeleteWalletWarningView.routeName, - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to delete wallet", - biometricsAuthenticationTitle: - "Delete wallet", - ), - settings: const RouteSettings( - name: "/deleteWalletLockscreen"), - ), - ); - }, - child: Text( - "Delete", - style: STextStyles.button(context), - ), - ), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 20, ), - child: Row( - children: [ - Text( - "Delete wallet", - style: STextStyles.titleBold12(context), - ), - ], + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + RenameWalletView.routeName, + arguments: walletId, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Rename wallet", + style: STextStyles.titleBold12(context), + ), + ], + ), ), ), ), - ), - ], + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: const EdgeInsets.all(0), + onPressed: () { + showDialog( + barrierDismissible: true, + context: context, + builder: (_) => StackDialog( + title: + "Do you want to delete ${ref.read(walletsChangeNotifierProvider).getManager(walletId).walletName}?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => LockscreenView( + routeOnSuccessArguments: walletId, + showBackButton: true, + routeOnSuccess: + DeleteWalletWarningView.routeName, + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to delete wallet", + biometricsAuthenticationTitle: + "Delete wallet", + ), + settings: const RouteSettings( + name: "/deleteWalletLockscreen"), + ), + ); + }, + child: Text( + "Delete", + style: STextStyles.button(context), + ), + ), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Delete wallet", + style: STextStyles.titleBold12(context), + ), + ], + ), + ), + ), + ), + ], + ), ), ), ), diff --git a/lib/pages/wallet_view/transaction_views/edit_note_view.dart b/lib/pages/wallet_view/transaction_views/edit_note_view.dart index 4baaaffc9..e8d7b05f9 100644 --- a/lib/pages/wallet_view/transaction_views/edit_note_view.dart +++ b/lib/pages/wallet_view/transaction_views/edit_note_view.dart @@ -5,6 +5,8 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -53,120 +55,141 @@ class _EditNoteViewState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, - appBar: isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: child, + ), + child: Scaffold( + backgroundColor: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit note", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Edit note", - style: STextStyles.navBarTitle(context), - ), - ), - body: MobileEditNoteScaffold( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (isDesktop) + body: MobileEditNoteScaffold( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 12, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Edit note", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + ), Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 12, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Edit note", - style: STextStyles.desktopH3(context), - ), - const DesktopDialogCloseButton(), - ], - ), - ), - Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - horizontal: 32, - ) - : const EdgeInsets.all(0), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _noteController, - style: isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - height: 1.8, - ) - : STextStyles.field(context), - focusNode: noteFieldFocusNode, - decoration: standardInputDecoration( - "Note", - noteFieldFocusNode, - context, - desktopMed: isDesktop, - ).copyWith( - contentPadding: isDesktop - ? const EdgeInsets.only( - left: 16, - top: 11, - bottom: 12, - right: 5, + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _noteController, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, ) - : null, - suffixIcon: _noteController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _noteController.text = ""; - }); - }, - ), - ], + : STextStyles.field(context), + focusNode: noteFieldFocusNode, + decoration: standardInputDecoration( + "Note", + noteFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + contentPadding: isDesktop + ? const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ) + : null, + suffixIcon: _noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _noteController.text = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), ), - ), - // if (!isDesktop) - const Spacer(), - if (isDesktop) - Padding( - padding: const EdgeInsets.all(32), - child: PrimaryButton( - label: "Save", + // if (!isDesktop) + const Spacer(), + if (isDesktop) + Padding( + padding: const EdgeInsets.all(32), + child: PrimaryButton( + label: "Save", + onPressed: () async { + await ref + .read(notesServiceChangeNotifierProvider( + widget.walletId)) + .editOrAddNote( + txid: widget.txid, + note: _noteController.text, + ); + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + if (!isDesktop) + TextButton( onPressed: () async { await ref .read( @@ -179,30 +202,16 @@ class _EditNoteViewState extends ConsumerState { Navigator.of(context).pop(); } }, - ), - ), - if (!isDesktop) - TextButton( - onPressed: () async { - await ref - .read(notesServiceChangeNotifierProvider(widget.walletId)) - .editOrAddNote( - txid: widget.txid, - note: _noteController.text, - ); - if (mounted) { - Navigator.of(context).pop(); - } - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Save", - style: STextStyles.button(context), - ), - ) - ], + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Save", + style: STextStyles.button(context), + ), + ) + ], + ), ), ), ); diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index c2a0590e4..738ef721b 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -25,6 +25,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -297,177 +298,290 @@ class _TransactionDetailsViewState @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, - appBar: isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Transaction details", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(12), - child: Column( - children: [ - if (isDesktop) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transaction details", - style: STextStyles.desktopH3(context), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: Padding( - padding: isDesktop - ? const EdgeInsets.only( - right: 32, - bottom: 32, - ) - : const EdgeInsets.all(0), - child: ConditionalParent( - condition: isDesktop, - builder: (child) { - return RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context) - .extension()! - .background - : null, - padding: const EdgeInsets.all(0), - child: child, - ); + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: child, + ), + child: Scaffold( + backgroundColor: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); }, - child: SingleChildScrollView( - primary: isDesktop ? false : null, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), - child: Container( - decoration: isDesktop - ? BoxDecoration( - color: Theme.of(context) - .extension()! - .background, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, + ), + title: Text( + "Transaction details", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(12), + child: Column( + children: [ + if (isDesktop) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction details", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: isDesktop + ? const EdgeInsets.only( + right: 32, + bottom: 32, + ) + : const EdgeInsets.all(0), + child: ConditionalParent( + condition: isDesktop, + builder: (child) { + return RoundedWhiteContainer( + borderColor: isDesktop + ? Theme.of(context) + .extension()! + .background + : null, + padding: const EdgeInsets.all(0), + child: child, + ); + }, + child: SingleChildScrollView( + primary: isDesktop ? false : null, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), + child: Container( + decoration: isDesktop + ? BoxDecoration( + color: Theme.of(context) + .extension()! + .background, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), ), - ), - ) - : null, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - if (isDesktop) - Row( - children: [ - TxIcon( - transaction: _transaction, - ), - const SizedBox( - width: 16, - ), - SelectableText( - _transaction.isCancelled - ? "Cancelled" - : whatIsIt(_transaction.txType), - style: - STextStyles.desktopTextMedium( - context), - ), - ], - ), - Column( - crossAxisAlignment: isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - SelectableText( - "$amountPrefix${Format.localizedStringAsFixed( - value: amount, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), + ) + : null, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + if (isDesktop) + Row( + children: [ + TxIcon( + transaction: _transaction, ), - decimalPlaces: - Constants.decimalPlacesForCoin( - coin), - )} ${coin.ticker}", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.titleBold12( - context), + const SizedBox( + width: 16, + ), + SelectableText( + _transaction.isCancelled + ? "Cancelled" + : whatIsIt( + _transaction.txType), + style: + STextStyles.desktopTextMedium( + context), + ), + ], ), - const SizedBox( - height: 2, - ), - if (ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.externalCalls))) + Column( + crossAxisAlignment: isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ SelectableText( "$amountPrefix${Format.localizedStringAsFixed( - value: amount * - ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => value - .getPrice(coin) - .item1), - ), + value: amount, locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), - decimalPlaces: 2, - )} ${ref.watch( + decimalPlaces: Constants + .decimalPlacesForCoin(coin), + )} ${coin.ticker}", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles.titleBold12( + context), + ), + const SizedBox( + height: 2, + ), + if (ref.watch( prefsChangeNotifierProvider - .select( - (value) => value.currency, - ), - )}", + .select((value) => + value.externalCalls))) + SelectableText( + "$amountPrefix${Format.localizedStringAsFixed( + value: amount * + ref.watch( + priceAnd24hChangeNotifierProvider + .select((value) => + value + .getPrice( + coin) + .item1), + ), + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => + value.locale), + ), + decimalPlaces: 2, + )} ${ref.watch( + prefsChangeNotifierProvider + .select( + (value) => value.currency, + ), + )}", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + ], + ), + if (!isDesktop) + TxIcon( + transaction: _transaction, + ), + ], + ), + ), + ), + ), + + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), + ), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.isCancelled + ? "Cancelled" + : whatIsIt(_transaction.txType), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: _transaction.txType == "Sent" + ? Theme.of(context) + .extension()! + .accentColorOrange + : Theme.of(context) + .extension()! + .accentColorGreen, + ) + : STextStyles.itemSubtitle12(context), + ), + // ), + // ), + ], + ), + ), + if (!((coin == Coin.monero || + coin == Coin.wownero) && + _transaction.txType.toLowerCase() == + "sent") && + !((coin == Coin.firo || + coin == Coin.firoTestNet) && + _transaction.subType == "mint")) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (!((coin == Coin.monero || + coin == Coin.wownero) && + _transaction.txType.toLowerCase() == + "sent") && + !((coin == Coin.firo || + coin == Coin.firoTestNet) && + _transaction.subType == "mint")) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + _transaction.txType.toLowerCase() == + "sent" + ? "Sent to" + : "Receiving address", style: isDesktop ? STextStyles .desktopTextExtraExtraSmall( @@ -475,81 +589,192 @@ class _TransactionDetailsViewState : STextStyles.itemSubtitle( context), ), - ], + const SizedBox( + height: 8, + ), + _transaction.txType.toLowerCase() == + "received" + ? FutureBuilder( + future: fetchContactNameFor( + _transaction.address), + builder: (builderContext, + AsyncSnapshot + snapshot) { + String + addressOrContactName = + _transaction.address; + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + addressOrContactName = + snapshot.data!; + } + return SelectableText( + addressOrContactName, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles + .itemSubtitle12( + context), + ); + }, + ) + : SelectableText( + _transaction.address, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of( + context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles + .itemSubtitle12( + context), + ), + ], + ), ), - if (!isDesktop) - TxIcon( - transaction: _transaction, + if (isDesktop) + IconCopyButton( + data: _transaction.address, ), ], ), ), - ), - ), - - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Status", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - _transaction.isCancelled - ? "Cancelled" - : whatIsIt(_transaction.txType), - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context) - .copyWith( - color: _transaction.txType == "Sent" - ? Theme.of(context) - .extension()! - .accentColorOrange - : Theme.of(context) - .extension()! - .accentColorGreen, - ) - : STextStyles.itemSubtitle12(context), - ), - // ), - // ), - ], - ), - ), - if (!((coin == Coin.monero || coin == Coin.wownero) && - _transaction.txType.toLowerCase() == - "sent") && - !((coin == Coin.firo || - coin == Coin.firoTestNet) && - _transaction.subType == "mint")) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (!((coin == Coin.monero || coin == Coin.wownero) && - _transaction.txType.toLowerCase() == - "sent") && - !((coin == Coin.firo || - coin == Coin.firoTestNet) && - _transaction.subType == "mint")) + + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Note", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + isDesktop + ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: _transaction.txid, + walletId: walletId, + note: _note, + ), + ); + }, + ); + }, + ) + : GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple3( + _transaction.txid, + walletId, + _note, + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: Theme.of(context) + .extension< + StackColors>()! + .infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Edit", + style: STextStyles.link2( + context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 8, + ), + FutureBuilder( + future: ref.watch( + notesServiceChangeNotifierProvider( + walletId) + .select((value) => value.getNoteFor( + txid: _transaction.txid))), + builder: (builderContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + _note = snapshot.data ?? ""; + } + return SelectableText( + _note, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ); + }, + ), + ], + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -558,6 +783,249 @@ class _TransactionDetailsViewState mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Date", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + if (isDesktop) + const SizedBox( + height: 2, + ), + if (isDesktop) + SelectableText( + Format.extractDateFrom( + _transaction.timestamp, + ), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + ], + ), + if (!isDesktop) + SelectableText( + Format.extractDateFrom( + _transaction.timestamp, + ), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12(context), + ), + if (isDesktop) + IconCopyButton( + data: Format.extractDateFrom( + _transaction.timestamp, + ), + ), + ], + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Builder(builder: (context) { + final feeString = showFeePending + ? _transaction.confirmedStatus + ? Format.localizedStringAsFixed( + value: fee, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => + value.locale)), + decimalPlaces: + Constants.decimalPlacesForCoin( + coin)) + : "Pending" + : Format.localizedStringAsFixed( + value: fee, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale)), + decimalPlaces: + Constants.decimalPlacesForCoin(coin)); + + return Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Transaction fee", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + if (isDesktop) + const SizedBox( + height: 2, + ), + if (isDesktop) + SelectableText( + feeString, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + ], + ), + if (!isDesktop) + SelectableText( + feeString, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + if (isDesktop) + IconCopyButton(data: feeString) + ], + ); + }), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Builder(builder: (context) { + final height = widget.coin != Coin.epicCash && + _transaction.confirmedStatus + ? "${_transaction.height == 0 ? "Unknown" : _transaction.height}" + : _transaction.confirmations > 0 + ? "${_transaction.height}" + : "Pending"; + + return Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Block height", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + if (isDesktop) + const SizedBox( + height: 2, + ), + if (isDesktop) + SelectableText( + height, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + ], + ), + if (!isDesktop) + SelectableText( + height, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + if (isDesktop) IconCopyButton(data: height), + ], + ); + }), + ), + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( @@ -565,10 +1033,7 @@ class _TransactionDetailsViewState CrossAxisAlignment.start, children: [ Text( - _transaction.txType.toLowerCase() == - "sent" - ? "Sent to" - : "Receiving address", + "Transaction ID", style: isDesktop ? STextStyles .desktopTextExtraExtraSmall( @@ -579,771 +1044,338 @@ class _TransactionDetailsViewState const SizedBox( height: 8, ), - _transaction.txType.toLowerCase() == - "received" - ? FutureBuilder( - future: fetchContactNameFor( - _transaction.address), - builder: (builderContext, - AsyncSnapshot - snapshot) { - String addressOrContactName = - _transaction.address; - if (snapshot.connectionState == - ConnectionState - .done && - snapshot.hasData) { - addressOrContactName = - snapshot.data!; - } - return SelectableText( - addressOrContactName, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context), - ); - }, - ) - : SelectableText( - _transaction.address, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context), - ), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.txid, + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + if (coin != Coin.epicCash) + const SizedBox( + height: 8, + ), + if (coin != Coin.epicCash) + BlueTextButton( + text: "Open in block explorer", + onTap: () async { + final uri = + getBlockExplorerTransactionUrlFor( + coin: coin, + txid: _transaction.txid, + ); + + if (ref + .read( + prefsChangeNotifierProvider) + .hideBlockExplorerWarning == + false) { + final shouldContinue = + await showExplorerWarning( + "${uri.scheme}://${uri.host}"); + + if (!shouldContinue) { + return; + } + } + + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + try { + await launchUrl( + uri, + mode: LaunchMode + .externalApplication, + ); + } catch (_) { + unawaited( + showDialog( + context: context, + builder: (_) => + StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), + ), + ); + } finally { + // Future.delayed( + // const Duration(seconds: 1), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + } + }, + ), + // ), + // ), ], ), ), - if (isDesktop) - IconCopyButton( - data: _transaction.address, - ), - ], - ), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Note", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - isDesktop - ? IconPencilButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: _transaction.txid, - walletId: walletId, - note: _note, - ), - ); - }, - ); - }, - ) - : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple3( - _transaction.txid, - walletId, - _note, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Edit", - style: STextStyles.link2( - context), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 8, - ), - FutureBuilder( - future: ref.watch( - notesServiceChangeNotifierProvider( - walletId) - .select((value) => value.getNoteFor( - txid: _transaction.txid))), - builder: (builderContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - _note = snapshot.data ?? ""; - } - return SelectableText( - _note, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), - ); - }, - ), - ], - ), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Date", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - if (isDesktop) - const SizedBox( - height: 2, - ), - if (isDesktop) - SelectableText( - Format.extractDateFrom( - _transaction.timestamp, - ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - ], - ), - if (!isDesktop) - SelectableText( - Format.extractDateFrom( - _transaction.timestamp, - ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), - ), - if (isDesktop) - IconCopyButton( - data: Format.extractDateFrom( - _transaction.timestamp, - ), - ), - ], - ), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Builder(builder: (context) { - final feeString = showFeePending - ? _transaction.confirmedStatus - ? Format.localizedStringAsFixed( - value: fee, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale)), - decimalPlaces: - Constants.decimalPlacesForCoin( - coin)) - : "Pending" - : Format.localizedStringAsFixed( - value: fee, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale)), - decimalPlaces: - Constants.decimalPlacesForCoin(coin)); - - return Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Transaction fee", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - if (isDesktop) - const SizedBox( - height: 2, - ), - if (isDesktop) - SelectableText( - feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - ], - ), - if (!isDesktop) - SelectableText( - feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), - ), - if (isDesktop) IconCopyButton(data: feeString) - ], - ); - }), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Builder(builder: (context) { - final height = widget.coin != Coin.epicCash && - _transaction.confirmedStatus - ? "${_transaction.height == 0 ? "Unknown" : _transaction.height}" - : _transaction.confirmations > 0 - ? "${_transaction.height}" - : "Pending"; - - return Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Block height", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - if (isDesktop) - const SizedBox( - height: 2, - ), - if (isDesktop) - SelectableText( - height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - ], - ), - if (!isDesktop) - SelectableText( - height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), - ), - if (isDesktop) IconCopyButton(data: height), - ], - ); - }), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Transaction ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 8, - ), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - _transaction.txid, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - if (coin != Coin.epicCash) - const SizedBox( - height: 8, - ), - if (coin != Coin.epicCash) - BlueTextButton( - text: "Open in block explorer", - onTap: () async { - final uri = - getBlockExplorerTransactionUrlFor( - coin: coin, - txid: _transaction.txid, - ); - - if (ref - .read( - prefsChangeNotifierProvider) - .hideBlockExplorerWarning == - false) { - final shouldContinue = - await showExplorerWarning( - "${uri.scheme}://${uri.host}"); - - if (!shouldContinue) { - return; - } - } - - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - try { - await launchUrl( - uri, - mode: LaunchMode - .externalApplication, - ); - } catch (_) { - unawaited( - showDialog( - context: context, - builder: (_) => StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), - ), - ); - } finally { - // Future.delayed( - // const Duration(seconds: 1), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - } - }, - ), - // ), - // ), - ], - ), - ), - if (isDesktop) - const SizedBox( - width: 12, - ), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, - ), - ], - ), - ), - // if ((coin == Coin.firoTestNet || coin == Coin.firo) && - // _transaction.subType == "mint") - // const SizedBox( - // height: 12, - // ), - // if ((coin == Coin.firoTestNet || coin == Coin.firo) && - // _transaction.subType == "mint") - // RoundedWhiteContainer( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text( - // "Mint Transaction ID", - // style: STextStyles.itemSubtitle(context), - // ), - // ], - // ), - // const SizedBox( - // height: 8, - // ), - // // Flexible( - // // child: FittedBox( - // // fit: BoxFit.scaleDown, - // // child: - // SelectableText( - // _transaction.otherData ?? "Unknown", - // style: STextStyles.itemSubtitle12(context), - // ), - // // ), - // // ), - // const SizedBox( - // height: 8, - // ), - // BlueTextButton( - // text: "Open in block explorer", - // onTap: () async { - // final uri = getBlockExplorerTransactionUrlFor( - // coin: coin, - // txid: _transaction.otherData ?? "Unknown", - // ); - // // ref - // // .read( - // // shouldShowLockscreenOnResumeStateProvider - // // .state) - // // .state = false; - // try { - // await launchUrl( - // uri, - // mode: LaunchMode.externalApplication, - // ); - // } catch (_) { - // unawaited(showDialog( - // context: context, - // builder: (_) => StackOkDialog( - // title: "Could not open in block explorer", - // message: - // "Failed to open \"${uri.toString()}\"", - // ), - // )); - // } finally { - // // Future.delayed( - // // const Duration(seconds: 1), - // // () => ref - // // .read( - // // shouldShowLockscreenOnResumeStateProvider - // // .state) - // // .state = true, - // // ); - // } - // }, - // ), - // ], - // ), - // ), - if (coin == Coin.epicCash) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - if (coin == Coin.epicCash) - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Slate ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle(context), - ), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - _transaction.slateId ?? "Unknown", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context), - ), - // ), - // ), - ], - ), if (isDesktop) const SizedBox( width: 12, ), if (isDesktop) IconCopyButton( - data: _transaction.slateId ?? "Unknown", + data: _transaction.txid, ), ], ), ), - if (!isDesktop) - const SizedBox( - height: 12, - ), - ], + // if ((coin == Coin.firoTestNet || coin == Coin.firo) && + // _transaction.subType == "mint") + // const SizedBox( + // height: 12, + // ), + // if ((coin == Coin.firoTestNet || coin == Coin.firo) && + // _transaction.subType == "mint") + // RoundedWhiteContainer( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "Mint Transaction ID", + // style: STextStyles.itemSubtitle(context), + // ), + // ], + // ), + // const SizedBox( + // height: 8, + // ), + // // Flexible( + // // child: FittedBox( + // // fit: BoxFit.scaleDown, + // // child: + // SelectableText( + // _transaction.otherData ?? "Unknown", + // style: STextStyles.itemSubtitle12(context), + // ), + // // ), + // // ), + // const SizedBox( + // height: 8, + // ), + // BlueTextButton( + // text: "Open in block explorer", + // onTap: () async { + // final uri = getBlockExplorerTransactionUrlFor( + // coin: coin, + // txid: _transaction.otherData ?? "Unknown", + // ); + // // ref + // // .read( + // // shouldShowLockscreenOnResumeStateProvider + // // .state) + // // .state = false; + // try { + // await launchUrl( + // uri, + // mode: LaunchMode.externalApplication, + // ); + // } catch (_) { + // unawaited(showDialog( + // context: context, + // builder: (_) => StackOkDialog( + // title: "Could not open in block explorer", + // message: + // "Failed to open \"${uri.toString()}\"", + // ), + // )); + // } finally { + // // Future.delayed( + // // const Duration(seconds: 1), + // // () => ref + // // .read( + // // shouldShowLockscreenOnResumeStateProvider + // // .state) + // // .state = true, + // // ); + // } + // }, + // ), + // ], + // ), + // ), + if (coin == Coin.epicCash) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (coin == Coin.epicCash) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Slate ID", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.slateId ?? "Unknown", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + // ), + // ), + ], + ), + if (isDesktop) + const SizedBox( + width: 12, + ), + if (isDesktop) + IconCopyButton( + data: _transaction.slateId ?? "Unknown", + ), + ], + ), + ), + if (!isDesktop) + const SizedBox( + height: 12, + ), + ], + ), ), ), ), ), ), - ), - ], + ], + ), ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin == Coin.epicCash && - _transaction.confirmedStatus == false && - _transaction.isCancelled == false && - _transaction.txType == "Sent") - ? SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: (coin == Coin.epicCash && + _transaction.confirmedStatus == false && + _transaction.isCancelled == false && + _transaction.txType == "Sent") + ? SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, + ), ), - ), - onPressed: () async { - final Manager manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId); + onPressed: () async { + final Manager manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId); - if (manager.wallet is EpicCashWallet) { - final String? id = _transaction.slateId; - if (id == null) { + if (manager.wallet is EpicCashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, + )); + return; + } + + unawaited(showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + )); + + final result = await (manager.wallet as EpicCashWallet) + .cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + manager.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName(WalletView.routeName)); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { unawaited(showFloatingFlushBar( type: FlushBarType.warning, - message: "Could not find Epic transaction ID", + message: "ERROR: Wallet type is not Epic Cash", context: context, )); return; } - - unawaited(showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - )); - - final result = await (manager.wallet as EpicCashWallet) - .cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - manager.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName)); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); - } - } - } else { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - )); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), ), - ), - ) - : null, + ) + : null, + ), ); } } diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index 0b3fd3acb..7e1b53cbb 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -428,42 +429,46 @@ class _TransactionSearchViewState ), ); } else { - return Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( + return Background( + child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Transactions filter", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Transactions filter", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: EdgeInsets.symmetric( - horizontal: Constants.size.standardPadding, - ), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight( - child: _buildContent(context), + body: Padding( + padding: EdgeInsets.symmetric( + horizontal: Constants.size.standardPadding, + ), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: _buildContent(context), + ), ), - ), - ); - }, + ); + }, + ), ), ), ); @@ -869,7 +874,7 @@ class _TransactionSearchViewState Expanded( child: SecondaryButton( label: "Cancel", - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { if (!isDesktop) { if (FocusScope.of(context).hasFocus) { @@ -919,7 +924,7 @@ class _TransactionSearchViewState ), Expanded( child: PrimaryButton( - buttonHeight: ButtonHeight.l, + buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { await _onApplyPressed(); }, diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index c84ddf2b9..61a1da0ac 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -40,6 +40,7 @@ import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/logger.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/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -378,401 +379,415 @@ class _WalletViewState extends ConsumerState { return WillPopScope( onWillPop: _onWillPop, - child: Scaffold( - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - _logout(); - Navigator.of(context).pop(); - }, - ), - titleSpacing: 0, - title: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - // color: Theme.of(context).extension()!.accentColorDark - width: 24, - height: 24, - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Text( - ref.watch( - managerProvider.select((value) => value.walletName)), - style: STextStyles.navBarTitle(context), - overflow: TextOverflow.ellipsis, + child: Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + _logout(); + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + // color: Theme.of(context).extension()!.accentColorDark + width: 24, + height: 24, ), - ) + const SizedBox( + width: 16, + ), + Expanded( + child: Text( + ref.watch( + managerProvider.select((value) => value.walletName)), + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("walletViewRadioButton"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: _buildNetworkIcon(_currentSyncStatus), + onPressed: () { + Navigator.of(context).pushNamed( + WalletNetworkSettingsView.routeName, + arguments: Tuple3( + walletId, + _currentSyncStatus, + _currentNodeStatus, + ), + ); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("walletViewAlertsButton"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + ref.watch(notificationsProvider.select((value) => + value.hasUnreadNotificationsFor(walletId))) + ? Assets.svg.bellNew(context) + : Assets.svg.bell, + width: 20, + height: 20, + color: ref.watch(notificationsProvider.select((value) => + value.hasUnreadNotificationsFor(walletId))) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // reset unread state + ref.refresh(unreadNotificationsStateProvider); + + Navigator.of(context) + .pushNamed( + NotificationsView.routeName, + arguments: walletId, + ) + .then((_) { + final Set unreadNotificationIds = ref + .read(unreadNotificationsStateProvider.state) + .state; + if (unreadNotificationIds.isEmpty) return; + + List> futures = []; + for (int i = 0; + i < unreadNotificationIds.length - 1; + i++) { + futures.add(ref + .read(notificationsProvider) + .markAsRead( + unreadNotificationIds.elementAt(i), false)); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.last, true); + }); + }); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("walletViewSettingsButton"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.bars, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + debugPrint("wallet view settings tapped"); + Navigator.of(context).pushNamed( + WalletSettingsView.routeName, + arguments: Tuple4( + walletId, + ref.read(managerProvider).coin, + _currentSyncStatus, + _currentNodeStatus, + ), + ); + }, + ), + ), + ), ], ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewRadioButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: _buildNetworkIcon(_currentSyncStatus), - onPressed: () { - Navigator.of(context).pushNamed( - WalletNetworkSettingsView.routeName, - arguments: Tuple3( - walletId, - _currentSyncStatus, - _currentNodeStatus, - ), - ); - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewAlertsButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - ref.watch(notificationsProvider.select((value) => - value.hasUnreadNotificationsFor(walletId))) - ? Assets.svg.bellNew(context) - : Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch(notificationsProvider.select((value) => - value.hasUnreadNotificationsFor(walletId))) - ? null - : Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: () { - // reset unread state - ref.refresh(unreadNotificationsStateProvider); - - Navigator.of(context) - .pushNamed( - NotificationsView.routeName, - arguments: walletId, - ) - .then((_) { - final Set unreadNotificationIds = ref - .read(unreadNotificationsStateProvider.state) - .state; - if (unreadNotificationIds.isEmpty) return; - - List> futures = []; - for (int i = 0; - i < unreadNotificationIds.length - 1; - i++) { - futures.add(ref.read(notificationsProvider).markAsRead( - unreadNotificationIds.elementAt(i), false)); - } - - // wait for multiple to update if any - Future.wait(futures).then((_) { - // only notify listeners once - ref - .read(notificationsProvider) - .markAsRead(unreadNotificationIds.last, true); - }); - }); - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewSettingsButton"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.bars, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - debugPrint("wallet view settings tapped"); - Navigator.of(context).pushNamed( - WalletSettingsView.routeName, - arguments: Tuple4( - walletId, - ref.read(managerProvider).coin, - _currentSyncStatus, - _currentNodeStatus, - ), - ); - }, - ), - ), - ), - ], - ), - body: SafeArea( - child: Container( - color: Theme.of(context).extension()!.background, - child: Column( - children: [ - const SizedBox( - height: 10, - ), - Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: WalletSummary( - walletId: walletId, - managerProvider: managerProvider, - initialSyncStatus: ref.watch(managerProvider - .select((value) => value.isRefreshing)) - ? WalletSyncStatus.syncing - : WalletSyncStatus.synced, - ), - ), - ), - if (coin == Coin.firo) + body: SafeArea( + child: Container( + color: Theme.of(context).extension()!.background, + child: Column( + children: [ const SizedBox( height: 10, ), - if (coin == Coin.firo) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: () async { - await showDialog( - context: context, - builder: (context) => StackDialog( - title: "Attention!", - message: - "You're about to anonymize all of your public funds.", - leftButton: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - "Cancel", - style: - STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: WalletSummary( + walletId: walletId, + managerProvider: managerProvider, + initialSyncStatus: ref.watch(managerProvider + .select((value) => value.isRefreshing)) + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + ), + ), + if (coin == Coin.firo) + const SizedBox( + height: 10, + ), + if (coin == Coin.firo) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + onPressed: () async { + await showDialog( + context: context, + builder: (context) => StackDialog( + title: "Attention!", + message: + "You're about to anonymize all of your public funds.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + "Cancel", + style: STextStyles.button(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + rightButton: TextButton( + onPressed: () async { + Navigator.of(context).pop(); + + unawaited(attemptAnonymize()); + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor( + context), + child: Text( + "Continue", + style: STextStyles.button(context), ), ), ), - rightButton: TextButton( - onPressed: () async { - Navigator.of(context).pop(); - - unawaited(attemptAnonymize()); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Continue", - style: STextStyles.button(context), - ), - ), + ); + }, + child: Text( + "Anonymize funds", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, ), - ); - }, - child: Text( - "Anonymize funds", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, ), ), ), + ], + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transactions", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + BlueTextButton( + text: "See all", + onTap: () { + Navigator.of(context).pushNamed( + AllTransactionsView.routeName, + arguments: walletId, + ); + }, ), ], ), ), - const SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transactions", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - BlueTextButton( - text: "See all", - onTap: () { - Navigator.of(context).pushNamed( - AllTransactionsView.routeName, - arguments: walletId, - ); - }, - ), - ], + const SizedBox( + height: 12, ), - ), - const SizedBox( - height: 12, - ), - Expanded( - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Padding( - padding: const EdgeInsets.only(bottom: 14), - child: ClipRRect( - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, - ), - bottom: Radius.circular( - // WalletView.navBarHeight / 2.0, - Constants.size.circularBorderRadius, - ), - ), - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( + Expanded( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.only(bottom: 14), + child: ClipRRect( + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottom: Radius.circular( + // WalletView.navBarHeight / 2.0, Constants.size.circularBorderRadius, ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: TransactionsList( - managerProvider: managerProvider, - walletId: walletId, - ), + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ], + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TransactionsList( + managerProvider: managerProvider, + walletId: walletId, + ), + ), + ], + ), ), ), ), ), - ), - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 14, - left: 16, - right: 16, - ), - child: SizedBox( - height: WalletView.navBarHeight, - child: WalletNavigationBar( - enableExchange: Constants.enableExchange && - ref.watch(managerProvider.select( - (value) => value.coin)) != - Coin.epicCash, + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 14, + left: 16, + right: 16, + ), + child: SizedBox( height: WalletView.navBarHeight, - onExchangePressed: () => - _onExchangePressed(context), - onReceivePressed: () async { - final coin = - ref.read(managerProvider).coin; - if (mounted) { - unawaited( - Navigator.of(context).pushNamed( - ReceiveView.routeName, + child: WalletNavigationBar( + enableExchange: + Constants.enableExchange && + ref.watch(managerProvider.select( + (value) => value.coin)) != + Coin.epicCash, + height: WalletView.navBarHeight, + onExchangePressed: () => + _onExchangePressed(context), + onReceivePressed: () async { + final coin = + ref.read(managerProvider).coin; + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + ReceiveView.routeName, + arguments: Tuple2( + walletId, + coin, + ), + )); + } + }, + onSendPressed: () { + final walletId = + ref.read(managerProvider).walletId; + final coin = + ref.read(managerProvider).coin; + switch (ref + .read( + walletBalanceToggleStateProvider + .state) + .state) { + case WalletBalanceToggleState.full: + ref + .read( + publicPrivateBalanceStateProvider + .state) + .state = "Public"; + break; + case WalletBalanceToggleState + .available: + ref + .read( + publicPrivateBalanceStateProvider + .state) + .state = "Private"; + break; + } + Navigator.of(context).pushNamed( + SendView.routeName, arguments: Tuple2( walletId, coin, ), - )); - } - }, - onSendPressed: () { - final walletId = - ref.read(managerProvider).walletId; - final coin = - ref.read(managerProvider).coin; - switch (ref - .read(walletBalanceToggleStateProvider - .state) - .state) { - case WalletBalanceToggleState.full: - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state = "Public"; - break; - case WalletBalanceToggleState.available: - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state = "Private"; - break; - } - Navigator.of(context).pushNamed( - SendView.routeName, - arguments: Tuple2( - walletId, - coin, - ), - ); - }, - onBuyPressed: () {}, + ); + }, + onBuyPressed: () {}, + ), ), ), - ), - ], - ), - ], - ) - ], + ], + ), + ], + ) + ], + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart index eb5dec18a..9bddda3da 100644 --- a/lib/pages_desktop_specific/desktop_login_view.dart +++ b/lib/pages_desktop_specific/desktop_login_view.dart @@ -10,7 +10,6 @@ import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.da import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 9791cd867..54c74fe88 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -13,6 +13,7 @@ import 'package:stackwallet/providers/global/notifications_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; class DesktopHomeView extends ConsumerStatefulWidget { const DesktopHomeView({Key? key}) : super(key: key); @@ -98,21 +99,23 @@ class _DesktopHomeViewState extends ConsumerState { Widget build(BuildContext context) { return Material( color: Theme.of(context).extension()!.background, - child: Row( - children: [ - DesktopMenu( - // onSelectionChanged: onMenuSelectionChanged, - onSelectionWillChange: onMenuSelectionWillChange, - ), - Container( - width: 1, - color: Theme.of(context).extension()!.background, - ), - Expanded( - child: contentViews[ - ref.watch(currentDesktopMenuItemProvider.state).state]!, - ), - ], + child: Background( + child: Row( + children: [ + DesktopMenu( + // onSelectionChanged: onMenuSelectionChanged, + onSelectionWillChange: onMenuSelectionWillChange, + ), + Container( + width: 1, + color: Theme.of(context).extension()!.background, + ), + Expanded( + child: contentViews[ + ref.watch(currentDesktopMenuItemProvider.state).state]!, + ), + ], + ), ), ); } diff --git a/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart b/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart index 6b60902c4..6710c23a4 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_wallets import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; class MyStackView extends ConsumerStatefulWidget { @@ -23,36 +24,38 @@ class _MyStackViewState extends ConsumerState { debugPrint("BUILD: $runtimeType"); final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets; - return Column( - children: [ - DesktopAppBar( - isCompactHeight: true, - leading: Row( - children: [ - const SizedBox( - width: 24, - ), - SizedBox( - width: 32, - height: 32, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + return Background( + child: Column( + children: [ + DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox( + width: 24, ), - ), - const SizedBox( - width: 12, - ), - Text( - "My Stack", - style: STextStyles.desktopH3(context), - ) - ], + SizedBox( + width: 32, + height: 32, + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + ), + ), + const SizedBox( + width: 12, + ), + Text( + "My Stack", + style: STextStyles.desktopH3(context), + ) + ], + ), ), - ), - Expanded( - child: hasWallets ? const MyWallets() : const EmptyWallets(), - ), - ], + Expanded( + child: hasWallets ? const MyWallets() : const EmptyWallets(), + ), + ], + ), ); } } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 149d46b3c..27c8fe3b4 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; abstract class Assets { @@ -28,6 +29,16 @@ class _EXCHANGE { class _SVG { const _SVG(); + String? background(BuildContext context) { + switch (Theme.of(context).extension()!.themeType) { + case ThemeType.light: + case ThemeType.dark: + return null; + + case ThemeType.oceanBreeze: + return "assets/svg/${Theme.of(context).extension()!.themeType.name}/bg.svg"; + } + } String bellNew(BuildContext context) => "assets/svg/${Theme.of(context).extension()!.themeType.name}/bell-new.svg"; diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart index 852e2f586..4a480491c 100644 --- a/lib/utilities/theme/color_theme.dart +++ b/lib/utilities/theme/color_theme.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; enum ThemeType { @@ -12,6 +11,10 @@ abstract class StackColorTheme { ThemeType get themeType; Color get background; + Color get backgroundAppBar; + + Gradient? get gradientBackground; + Color get overlay; Color get accentColorBlue; diff --git a/lib/utilities/theme/dark_colors.dart b/lib/utilities/theme/dark_colors.dart index e7c4e51db..d55581921 100644 --- a/lib/utilities/theme/dark_colors.dart +++ b/lib/utilities/theme/dark_colors.dart @@ -7,6 +7,11 @@ class DarkColors extends StackColorTheme { @override Color get background => const Color(0xFF2A2D34); + @override + Color get backgroundAppBar => background; + @override + Gradient? get gradientBackground => null; + @override Color get overlay => const Color(0xFF111215); diff --git a/lib/utilities/theme/light_colors.dart b/lib/utilities/theme/light_colors.dart index 896ae4e5e..1303d0b75 100644 --- a/lib/utilities/theme/light_colors.dart +++ b/lib/utilities/theme/light_colors.dart @@ -7,6 +7,11 @@ class LightColors extends StackColorTheme { @override Color get background => const Color(0xFFF7F7F7); + @override + Color get backgroundAppBar => background; + @override + Gradient? get gradientBackground => null; + @override Color get overlay => const Color(0xFF111215); diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart index 665eaa0c3..8c4259bb9 100644 --- a/lib/utilities/theme/ocean_breeze_colors.dart +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -6,7 +6,19 @@ class OceanBreezeColors extends StackColorTheme { ThemeType get themeType => ThemeType.oceanBreeze; @override - Color get background => const Color(0xFFF3F7FA); + Color get background => Colors.transparent; + @override + Color get backgroundAppBar => const Color(0xFFF3F7FA); + @override + Gradient? get gradientBackground => const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFF3F7FA), + Color(0xFFE8F2F9), + ], + ); + @override Color get overlay => const Color(0xFF111215); diff --git a/lib/utilities/theme/stack_colors.dart b/lib/utilities/theme/stack_colors.dart index 8249dccf4..935fa03ae 100644 --- a/lib/utilities/theme/stack_colors.dart +++ b/lib/utilities/theme/stack_colors.dart @@ -6,6 +6,9 @@ class StackColors extends ThemeExtension { final ThemeType themeType; final Color background; + final Color backgroundAppBar; + final Gradient? gradientBackground; + final Color overlay; final Color accentColorBlue; @@ -173,6 +176,8 @@ class StackColors extends ThemeExtension { StackColors({ required this.themeType, required this.background, + required this.backgroundAppBar, + required this.gradientBackground, required this.overlay, required this.accentColorBlue, required this.accentColorGreen, @@ -307,6 +312,8 @@ class StackColors extends ThemeExtension { return StackColors( themeType: colorTheme.themeType, background: colorTheme.background, + backgroundAppBar: colorTheme.backgroundAppBar, + gradientBackground: colorTheme.gradientBackground, overlay: colorTheme.overlay, accentColorBlue: colorTheme.accentColorBlue, accentColorGreen: colorTheme.accentColorGreen, @@ -444,6 +451,8 @@ class StackColors extends ThemeExtension { ThemeExtension copyWith({ ThemeType? themeType, Color? background, + Color? backgroundAppBar, + Gradient? gradientBackground, Color? overlay, Color? accentColorBlue, Color? accentColorGreen, @@ -576,6 +585,8 @@ class StackColors extends ThemeExtension { return StackColors( themeType: themeType ?? this.themeType, background: background ?? this.background, + backgroundAppBar: backgroundAppBar ?? this.backgroundAppBar, + gradientBackground: gradientBackground ?? this.gradientBackground, overlay: overlay ?? this.overlay, accentColorBlue: accentColorBlue ?? this.accentColorBlue, accentColorGreen: accentColorGreen ?? this.accentColorGreen, @@ -755,11 +766,17 @@ class StackColors extends ThemeExtension { return StackColors( themeType: other.themeType, + gradientBackground: other.gradientBackground, background: Color.lerp( background, other.background, t, )!, + backgroundAppBar: Color.lerp( + backgroundAppBar, + other.backgroundAppBar, + t, + )!, overlay: Color.lerp( overlay, other.overlay, diff --git a/lib/widgets/background.dart b/lib/widgets/background.dart new file mode 100644 index 000000000..4f70f5252 --- /dev/null +++ b/lib/widgets/background.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/ui/color_theme_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/theme/color_theme.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; + +class Background extends ConsumerWidget { + const Background({ + Key? key, + required this.child, + }) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorTheme = ref.watch(colorThemeProvider.state).state; + + Color? color; + + switch (colorTheme.themeType) { + case ThemeType.light: + case ThemeType.dark: + color = Theme.of(context).extension()!.background; + break; + case ThemeType.oceanBreeze: + color = null; + break; + } + + final bgAsset = Assets.svg.background(context); + + return Container( + decoration: BoxDecoration( + color: color, + gradient: + Theme.of(context).extension()!.gradientBackground, + ), + child: ConditionalParent( + condition: bgAsset != null, + builder: (child) => Stack( + children: [ + Positioned.fill( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).size.height * (1 / 8), + bottom: MediaQuery.of(context).size.height * (1 / 12), + ), + child: SvgPicture.asset( + bgAsset!, + fit: BoxFit.fill, + ), + ), + ), + Positioned.fill( + child: child, + ), + ], + ), + child: child, + ), + ); + } +} diff --git a/lib/widgets/desktop/desktop_scaffold.dart b/lib/widgets/desktop/desktop_scaffold.dart index 439289518..51fa9f3a5 100644 --- a/lib/widgets/desktop/desktop_scaffold.dart +++ b/lib/widgets/desktop/desktop_scaffold.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; class DesktopScaffold extends StatelessWidget { const DesktopScaffold({ @@ -18,15 +19,17 @@ class DesktopScaffold extends StatelessWidget { return Material( color: background ?? Theme.of(context).extension()!.background, - child: Column( - // crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (appBar != null) appBar!, - if (body != null) - Expanded( - child: body!, - ), - ], + child: Background( + child: Column( + // crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (appBar != null) appBar!, + if (body != null) + Expanded( + child: body!, + ), + ], + ), ), ); } @@ -50,17 +53,18 @@ class MasterScaffold extends StatelessWidget { Widget build(BuildContext context) { if (isDesktop) { return DesktopScaffold( - background: background ?? - Theme.of(context).extension()!.background, + background: background, appBar: appBar, body: body, ); } else { - return Scaffold( - backgroundColor: background ?? - Theme.of(context).extension()!.background, - appBar: appBar as PreferredSizeWidget?, - body: body, + return Background( + child: Scaffold( + backgroundColor: background ?? + Theme.of(context).extension()!.background, + appBar: appBar as PreferredSizeWidget?, + body: body, + ), ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 7ca368d29..2397fbfbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -380,6 +380,7 @@ flutter: - assets/svg/oceanBreeze/bell-new.svg - assets/svg/oceanBreeze/stack-icon1.svg - assets/svg/oceanBreeze/buy-coins-icon.svg + - assets/svg/oceanBreeze/bg.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. From 05bdc8c52fe53d1ff3d07dc1f7899480fa20a4d2 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 13:50:13 -0600 Subject: [PATCH 204/225] fix node loading on initial start for desktop, only add default node back if there are no nodes exist for a certain coin --- .../create_password/create_password_view.dart | 6 +++++- lib/services/node_service.dart | 15 ++++++++++----- lib/utilities/default_nodes.dart | 3 ++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart index 8e752f508..a8c9e7758 100644 --- a/lib/pages_desktop_specific/create_password/create_password_view.dart +++ b/lib/pages_desktop_specific/create_password/create_password_view.dart @@ -7,9 +7,9 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -81,6 +81,10 @@ class _CreatePasswordViewState extends ConsumerState { await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase); await (ref.read(secureStoreProvider).store as DesktopSecureStore).init(); + + // load default nodes now as node service requires storage handler to exist + + await ref.read(nodeServiceChangeNotifierProvider).updateDefaults(); } catch (e) { unawaited(showFloatingFlushBar( type: FlushBarType.warning, diff --git a/lib/services/node_service.dart b/lib/services/node_service.dart index aa8d5a6d9..ca1aae082 100644 --- a/lib/services/node_service.dart +++ b/lib/services/node_service.dart @@ -24,11 +24,14 @@ class NodeService extends ChangeNotifier { final savedNode = DB.instance .get(boxName: DB.boxNameNodeModels, key: defaultNode.id); if (savedNode == null) { - // save the default node to hive - await DB.instance.put( + // save the default node to hive only if no other nodes for the specific coin exist + if (getNodesFor(coinFromPrettyName(defaultNode.coinName)).isEmpty) { + await DB.instance.put( boxName: DB.boxNameNodeModels, key: defaultNode.id, - value: defaultNode); + value: defaultNode, + ); + } } else { // update all fields but copy over previously set enabled state await DB.instance.put( @@ -81,14 +84,16 @@ class NodeService extends ChangeNotifier { final list = DB.instance .values(boxName: DB.boxNameNodeModels) .where((e) => - e.coinName == coin.name && e.name != DefaultNodes.defaultName) + e.coinName == coin.name && + !e.id.startsWith(DefaultNodes.defaultNodeIdPrefix)) .toList(); // add default to end of list list.addAll(DB.instance .values(boxName: DB.boxNameNodeModels) .where((e) => - e.coinName == coin.name && e.name == DefaultNodes.defaultName) + e.coinName == coin.name && + e.id.startsWith(DefaultNodes.defaultNodeIdPrefix)) .toList()); // return reversed list so default node appears at beginning diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index abe702b78..c9e96fbac 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -4,7 +4,8 @@ import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class DefaultNodes { - static String _nodeId(Coin coin) => "default_${coin.name}"; + static const String defaultNodeIdPrefix = "default_"; + static String _nodeId(Coin coin) => "$defaultNodeIdPrefix${coin.name}"; static const String defaultName = "Stack Default"; static List get all => [ From 276d08d22f6c174ea2db9204c07f52b24f592826 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 14:28:53 -0600 Subject: [PATCH 205/225] allow default node deletion if other nodes exist --- lib/models/node_model.dart | 3 +- .../add_edit_node_view.dart | 7 +- .../manage_nodes_views/node_details_view.dart | 124 +++++++++++------- lib/widgets/node_card.dart | 17 +-- lib/widgets/node_options_sheet.dart | 6 +- 5 files changed, 94 insertions(+), 63 deletions(-) diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index 2628c5dd9..af5f8cbc1 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -1,4 +1,5 @@ import 'package:hive/hive.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; part 'type_adaptors/node_model.g.dart'; @@ -84,7 +85,7 @@ class NodeModel { return map; } - bool get isDefault => id.startsWith("default_"); + bool get isDefault => id.startsWith(DefaultNodes.defaultNodeIdPrefix); @override String toString() { diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index bd6e5c6d8..32fa3974a 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -432,7 +432,12 @@ class _AddEditNodeViewState extends ConsumerState { style: STextStyles.navBarTitle(context), ), actions: [ - if (viewType == AddEditNodeViewType.edit) + if (viewType == AddEditNodeViewType.edit && + ref + .watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))) + .length > + 1) Padding( padding: const EdgeInsets.only( top: 10, diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 2e43b5595..24af0e78e 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -10,7 +10,6 @@ import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; @@ -178,6 +177,11 @@ class _NodeDetailsViewState extends ConsumerState { final node = ref.watch(nodeServiceChangeNotifierProvider .select((value) => value.getNodeById(id: nodeId))); + final nodesForCoin = ref.watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))); + + final canDelete = nodesForCoin.length > 1; + return ConditionalParent( condition: !isDesktop, builder: (child) => Background( @@ -201,44 +205,43 @@ class _NodeDetailsViewState extends ConsumerState { style: STextStyles.navBarTitle(context), ), actions: [ - if (!nodeId.startsWith("default")) - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("nodeDetailsEditNodeAppBarButtonKey"), - size: 36, - shadows: const [], + // if (!nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix)) + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("nodeDetailsEditNodeAppBarButtonKey"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.pencil, color: Theme.of(context) .extension()! - .background, - icon: SvgPicture.asset( - Assets.svg.pencil, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - Navigator.of(context).pushNamed( - AddEditNodeView.routeName, - arguments: Tuple4( - AddEditNodeViewType.edit, - coin, - nodeId, - popRouteName, - ), - ); - }, + .accentColorDark, + width: 20, + height: 20, ), + onPressed: () { + Navigator.of(context).pushNamed( + AddEditNodeView.routeName, + arguments: Tuple4( + AddEditNodeViewType.edit, + coin, + nodeId, + popRouteName, + ), + ); + }, ), ), + ), ], ), body: Padding( @@ -315,7 +318,7 @@ class _NodeDetailsViewState extends ConsumerState { const SizedBox( height: 22, ), - if (isDesktop) + if (isDesktop && canDelete) SizedBox( height: 56, child: _desktopReadOnly @@ -345,7 +348,7 @@ class _NodeDetailsViewState extends ConsumerState { ], ), ), - if (isDesktop && !_desktopReadOnly) + if (isDesktop && !_desktopReadOnly && canDelete) const SizedBox( height: 45, ), @@ -366,22 +369,41 @@ class _NodeDetailsViewState extends ConsumerState { ), if (isDesktop) Expanded( - child: !nodeId.startsWith("default") - ? PrimaryButton( - label: _desktopReadOnly ? "Edit" : "Save", - buttonHeight: ButtonHeight.l, - onPressed: () async { - final shouldSave = _desktopReadOnly == false; - setState(() { - _desktopReadOnly = !_desktopReadOnly; - }); + child: + // !nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix) + // ? + PrimaryButton( + label: _desktopReadOnly ? "Edit" : "Save", + buttonHeight: ButtonHeight.l, + onPressed: () async { + final shouldSave = _desktopReadOnly == false; + setState(() { + _desktopReadOnly = !_desktopReadOnly; + }); - if (shouldSave) { - // todo save node - } - }, - ) - : Container(), + if (shouldSave) { + final editedNode = node!.copyWith( + host: ref.read(nodeFormDataProvider).host, + port: ref.read(nodeFormDataProvider).port, + name: ref.read(nodeFormDataProvider).name, + useSSL: ref.read(nodeFormDataProvider).useSSL, + loginName: ref.read(nodeFormDataProvider).login, + isFailover: + ref.read(nodeFormDataProvider).isFailover, + ); + + await ref + .read(nodeServiceChangeNotifierProvider) + .edit( + editedNode, + ref.read(nodeFormDataProvider).password, + true, + ); + } + }, + ) + // : Container() + , ), ], ), diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index c3fb36c70..fb8260b24 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -306,7 +306,7 @@ class _NodeCardState extends ConsumerState { width: isDesktop ? 40 : 24, height: isDesktop ? 40 : 24, decoration: BoxDecoration( - color: _node.name == DefaultNodes.defaultName + color: _node.id.startsWith(DefaultNodes.defaultNodeIdPrefix) ? Theme.of(context) .extension()! .buttonBackSecondary @@ -321,13 +321,14 @@ class _NodeCardState extends ConsumerState { Assets.svg.node, height: isDesktop ? 18 : 11, width: isDesktop ? 20 : 14, - color: _node.name == DefaultNodes.defaultName - ? Theme.of(context) - .extension()! - .accentColorDark - : Theme.of(context) - .extension()! - .infoItemIcons, + color: + _node.id.startsWith(DefaultNodes.defaultNodeIdPrefix) + ? Theme.of(context) + .extension()! + .accentColorDark + : Theme.of(context) + .extension()! + .infoItemIcons, ), ), ), diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index 7ffd290f3..1ef9b07fe 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -234,7 +234,8 @@ class NodeOptionsSheet extends ConsumerWidget { width: 32, height: 32, decoration: BoxDecoration( - color: node.name == DefaultNodes.defaultName + color: node.id + .startsWith(DefaultNodes.defaultNodeIdPrefix) ? Theme.of(context) .extension()! .textSubtitle4 @@ -249,7 +250,8 @@ class NodeOptionsSheet extends ConsumerWidget { Assets.svg.node, height: 15, width: 19, - color: node.name == DefaultNodes.defaultName + color: node.id.startsWith( + DefaultNodes.defaultNodeIdPrefix) ? Theme.of(context) .extension()! .accentColorDark From 4b0d44a239d5e94248b30eb07fca21a45d4e86b3 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 16:49:43 -0600 Subject: [PATCH 206/225] emoji search --- lib/widgets/emoji_select_sheet.dart | 178 ++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 35 deletions(-) diff --git a/lib/widgets/emoji_select_sheet.dart b/lib/widgets/emoji_select_sheet.dart index 7bf02e967..d5a37d142 100644 --- a/lib/widgets/emoji_select_sheet.dart +++ b/lib/widgets/emoji_select_sheet.dart @@ -1,14 +1,19 @@ import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/utilities/assets.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/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; -class EmojiSelectSheet extends ConsumerWidget { +class EmojiSelectSheet extends ConsumerStatefulWidget { const EmojiSelectSheet({ Key? key, }) : super(key: key); @@ -18,17 +23,59 @@ class EmojiSelectSheet extends ConsumerWidget { final double minimumEmojiSpacing = 25; @override - Widget build(BuildContext context, WidgetRef ref) { - final isDesktop = Util.isDesktop; + ConsumerState createState() => _EmojiSelectSheetState(); +} +class _EmojiSelectSheetState extends ConsumerState { + final isDesktop = Util.isDesktop; + + late final TextEditingController _searchController; + late final FocusNode _searchFocusNode; + late final double horizontalPadding = 24; + late final double emojiSize = 24; + late final double minimumEmojiSpacing = 25; + + String _searchTerm = ""; + + List filtered(String text) { + if (text.isEmpty) { + return Emoji.all(); + } + + text = text.toLowerCase(); + + return Emoji.all() + .where((e) => e.keywords + .where( + (e) => e.contains(text), + ) + .isNotEmpty) + .toList(growable: false); + } + + @override + void initState() { + _searchController = TextEditingController(); + _searchFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { final size = isDesktop ? const Size(600, 700) : MediaQuery.of(context).size; - final double maxHeight = size.height * 0.60; - final double availableWidth = size.width - (2 * horizontalPadding); - final int emojisPerRow = + final maxHeight = size.height * (isDesktop ? 0.6 : 0.9); + final availableWidth = size.width - (2 * horizontalPadding); + final emojisPerRow = ((availableWidth - emojiSize) ~/ (emojiSize + minimumEmojiSpacing)) + 1; - final itemCount = Emoji.all().length; - return ConditionalParent( condition: !isDesktop, builder: (child) => Container( @@ -81,40 +128,101 @@ class EmojiSelectSheet extends ConsumerWidget { : STextStyles.pageTitleH2(context), textAlign: TextAlign.left, ), + SizedBox( + height: isDesktop ? 16 : 12, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => _searchTerm = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), SizedBox( height: isDesktop ? 28 : 16, ), - Flexible( + Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Flexible( - child: GridView.builder( - itemCount: itemCount, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: emojisPerRow, - ), - itemBuilder: (context, index) { - final emoji = Emoji.all()[index]; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(emoji); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Colors.transparent, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - emoji.char, - style: isDesktop - ? STextStyles.desktopTextSmall(context) - : null, - ), - ), + Expanded( + child: Builder( + builder: (context) { + final emojis = filtered(_searchTerm); + final itemCount = emojis.length; + return GridView.builder( + itemCount: itemCount, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: emojisPerRow, ), + itemBuilder: (context, index) { + final emoji = emojis[index]; + return GestureDetector( + onTap: () { + Navigator.of(context).pop(emoji); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Colors.transparent, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + emoji.char, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : null, + ), + ), + ), + ); + }, ); }, ), From 9fce8ca107b1ce1dae9d96184b4f5affd2576840 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 17:14:06 -0600 Subject: [PATCH 207/225] familiarity fix --- lib/hive/db.dart | 12 ------ lib/main.dart | 5 +++ .../global_settings_view/hidden_settings.dart | 37 +++++++++++++++++++ lib/utilities/prefs.dart | 22 +++++++++++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/lib/hive/db.dart b/lib/hive/db.dart index 48e6ba154..1a52d64df 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -9,7 +9,6 @@ import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/notification_model.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; import 'package:stackwallet/services/wallets_service.dart'; -import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -151,17 +150,6 @@ class DB { _loadSharedCoinCacheBoxes(), ]); _initialized = true; - - try { - if (_boxPrefs!.get("familiarity") == null) { - await _boxPrefs!.put("familiarity", 0); - } - int count = _boxPrefs!.get("familiarity") as int; - await _boxPrefs!.put("familiarity", count + 1); - Constants.exchangeForExperiencedUsers(count + 1); - } catch (e, s) { - print("$e $s"); - } } } diff --git a/lib/main.dart b/lib/main.dart index d5980409f..2086d351e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -235,6 +235,11 @@ class _MaterialAppWithThemeState extends ConsumerState await DB.instance.init(); await ref.read(prefsChangeNotifierProvider).init(); + final familiarity = ref.read(prefsChangeNotifierProvider).familiarity + 1; + ref.read(prefsChangeNotifierProvider).familiarity = familiarity; + + Constants.exchangeForExperiencedUsers(familiarity); + if (Util.isDesktop) { _desktopHasPassword = await ref.read(storageCryptoHandlerProvider).hasPassword(); diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index 7f35fcc86..d92b166d7 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/providers/providers.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'; @@ -127,6 +128,42 @@ class HiddenSettings extends StatelessWidget { ), ); }), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + if (ref.watch(prefsChangeNotifierProvider + .select((value) => value.familiarity)) < + 6) { + return GestureDetector( + onTap: () async { + final familiarity = ref + .read(prefsChangeNotifierProvider) + .familiarity; + if (familiarity < 6) { + ref + .read(prefsChangeNotifierProvider) + .familiarity = 6; + + Constants.exchangeForExperiencedUsers(6); + } + }, + child: RoundedWhiteContainer( + child: Text( + "Enable exchange", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ); + } else { + return Container(); + } + }, + ), // const SizedBox( // height: 12, // ), diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 246291053..6b4b9821a 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -37,6 +37,7 @@ class Prefs extends ChangeNotifier { _gotoWalletOnStartup = await _getGotoWalletOnStartup(); _startupWalletId = await _getStartupWalletId(); _externalCalls = await _getHasExternalCalls(); + _familiarity = await _getHasFamiliarity(); _initialized = true; } @@ -328,6 +329,27 @@ class Prefs extends ChangeNotifier { false; } + // familiarity + + int _familiarity = 0; + + int get familiarity => _familiarity; + + set familiarity(int familiarity) { + if (_familiarity != familiarity) { + DB.instance.put( + boxName: DB.boxNamePrefs, key: "familiarity", value: familiarity); + _familiarity = familiarity; + notifyListeners(); + } + } + + Future _getHasFamiliarity() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "familiarity") as int? ?? + 0; + } + // show testnet coins bool _showTestNetCoins = false; From 56f54ac4872e8f063e20ab01cac53f6844f6e54d Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 25 Nov 2022 17:49:47 -0600 Subject: [PATCH 208/225] clean up and test fixes --- lib/widgets/background.dart | 10 +- lib/widgets/emoji_select_sheet.dart | 97 ++++++++++--------- .../widget_tests/emoji_select_sheet_test.dart | 14 ++- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/lib/widgets/background.dart b/lib/widgets/background.dart index 4f70f5252..67ff44f55 100644 --- a/lib/widgets/background.dart +++ b/lib/widgets/background.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; -class Background extends ConsumerWidget { +class Background extends StatelessWidget { const Background({ Key? key, required this.child, @@ -16,12 +14,10 @@ class Background extends ConsumerWidget { final Widget child; @override - Widget build(BuildContext context, WidgetRef ref) { - final colorTheme = ref.watch(colorThemeProvider.state).state; - + Widget build(BuildContext context) { Color? color; - switch (colorTheme.themeType) { + switch (Theme.of(context).extension()!.themeType) { case ThemeType.light: case ThemeType.dark: color = Theme.of(context).extension()!.background; diff --git a/lib/widgets/emoji_select_sheet.dart b/lib/widgets/emoji_select_sheet.dart index d5a37d142..ecb6d1a1b 100644 --- a/lib/widgets/emoji_select_sheet.dart +++ b/lib/widgets/emoji_select_sheet.dart @@ -131,55 +131,58 @@ class _EmojiSelectSheetState extends ConsumerState { SizedBox( height: isDesktop ? 16 : 12, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => _searchTerm = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, + Material( + color: Colors.transparent, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (newString) { + setState(() => _searchTerm = newString); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchTerm = ""; - }); - }, - ), - ], + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, + ), ), ), ), diff --git a/test/widget_tests/emoji_select_sheet_test.dart b/test/widget_tests/emoji_select_sheet_test.dart index 368a1d99b..aec05d580 100644 --- a/test/widget_tests/emoji_select_sheet_test.dart +++ b/test/widget_tests/emoji_select_sheet_test.dart @@ -1,8 +1,8 @@ import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:stackwallet/utilities/theme/light_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; @@ -43,15 +43,21 @@ void main() { ], ), home: mockingjay.MockNavigatorProvider( - navigator: navigator, child: emojiSelectSheet), + navigator: navigator, + child: Column( + children: const [ + Expanded(child: emojiSelectSheet), + ], + ), + ), ), ), ); - final gestureDetector = find.byType(GestureDetector).first; + final gestureDetector = find.byType(GestureDetector).at(5); expect(gestureDetector, findsOneWidget); - final emoji = Emoji.all()[0]; + final emoji = Emoji.byChar("😅"); await tester.tap(gestureDetector); await tester.pumpAndSettle(); From 8f157ccfc4dcc5e4ed3b1313b70e61b40746aa93 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Sat, 26 Nov 2022 13:12:38 -0700 Subject: [PATCH 209/225] Bump version. Mm! --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2397fbfbc..ef1b495a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.19+91 +version: 1.5.20+92 environment: sdk: ">=2.17.0 <3.0.0" From d0b2a5a3fed1a41382ce109c85d9e55b45c37e01 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 28 Nov 2022 08:59:23 -0800 Subject: [PATCH 210/225] Add tomli python lib to build script comment. --- scripts/linux/build_secure_storage_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/linux/build_secure_storage_deps.sh b/scripts/linux/build_secure_storage_deps.sh index 7a725d65c..69b452e2d 100755 --- a/scripts/linux/build_secure_storage_deps.sh +++ b/scripts/linux/build_secure_storage_deps.sh @@ -20,7 +20,7 @@ cd "$LINUX_DIRECTORY" || exit # Build libSecret # sudo apt install meson libgirepository1.0-dev valac xsltproc gi-docgen docbook-xsl # sudo apt install python3-pip -#pip3 install --user meson markdown --upgrade +#pip3 install --user meson markdown tomli --upgrade # pip3 install --user gi-docgen cd build || exit git -C libsecret pull || git clone https://gitlab.gnome.org/GNOME/libsecret.git libsecret From 76e0616ade79a9ef56012e80a4f4e0ede7eb825d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 28 Nov 2022 10:40:45 -0700 Subject: [PATCH 211/225] view backup keys text changed for wallet deletion --- .../wallet_view/sub_widgets/delete_wallet_keys_popup.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index 70f4a3e13..a2d58465b 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -95,7 +95,8 @@ class _DeleteWalletKeysPopup extends ConsumerState { horizontal: 32, ), child: Text( - "Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.", + "Please write down your recovery phrase in the correct order and " + "save it to keep your funds secure. You will be shown your recovery phrase on the next screen.", style: STextStyles.desktopTextExtraExtraSmall(context), textAlign: TextAlign.center, ), From 8960bb576464ecd5ac415cfa3494a53077d10677 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 12:46:35 -0600 Subject: [PATCH 212/225] linux small screen width check --- lib/main.dart | 8 +++++++- lib/utilities/util.dart | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2086d351e..a49bcab82 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,11 +76,17 @@ void main() async { Util.libraryPath = await getLibraryDirectory(); } + Screen? screen; + if (Platform.isLinux || Util.isDesktop) { + screen = await getCurrentScreen(); + Util.screenWidth = screen?.frame.width; + } + if (Util.isDesktop) { setWindowTitle('Stack Wallet'); setWindowMinSize(const Size(1220, 100)); setWindowMaxSize(Size.infinite); - final screen = await getCurrentScreen(); + final screenHeight = screen?.frame.height; if (screenHeight != null) { // starting to height be 3/4 screen height or 900, whichever is smaller diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index 5963bfee9..2940b6d40 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -1,14 +1,24 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; abstract class Util { static Directory? libraryPath; + static double? screenWidth; + static bool get isDesktop { - if(Platform.isIOS && libraryPath != null && !libraryPath!.path.contains("/var/mobile/")){ + // special check for running on linux based phones + if (Platform.isLinux && screenWidth != null && screenWidth! < 800) { + return false; + } + + // special check for running under ipad mode in macos + if (Platform.isIOS && + libraryPath != null && + !libraryPath!.path.contains("/var/mobile/")) { return true; } + return Platform.isLinux || Platform.isMacOS || Platform.isWindows; } From 66ff5a437dd8a00bb55687aacbfc4fc5139f91e5 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 28 Nov 2022 11:51:13 -0700 Subject: [PATCH 213/225] reverted mobile restore calendar height --- .../restore_options_view/restore_options_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index ac84964ca..1ce5d713a 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -155,7 +155,7 @@ class _RestoreOptionsViewState extends ConsumerState { final date = await showRoundedDatePicker( context: context, initialDate: DateTime.now(), - height: height / 3.2, + height: height * 0.5, theme: ThemeData( primarySwatch: Util.createMaterialColor(fetchedColor), ), From 221e654dd669b24e5e4426131947098b47774b21 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 13:50:55 -0600 Subject: [PATCH 214/225] animated main menu --- .../home/desktop_menu.dart | 92 +++++++++++--- .../home/desktop_menu_item.dart | 119 +++++++++++++++--- 2 files changed, 174 insertions(+), 37 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index d82d62883..6ae4b91a1 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -38,6 +38,9 @@ class _DesktopMenuState extends ConsumerState { static const expandedWidth = 225.0; static const minimizedWidth = 72.0; + final Duration duration = const Duration(milliseconds: 250); + late final List controllers; + double _width = expandedWidth; void updateSelectedMenuItem(DesktopMenuItemId idKey) { @@ -49,26 +52,58 @@ class _DesktopMenuState extends ConsumerState { } void toggleMinimize() { + final expanded = _width == expandedWidth; + + for (var e in controllers) { + e.toggle?.call(); + } + setState(() { - _width = _width == expandedWidth ? minimizedWidth : expandedWidth; + _width = expanded ? minimizedWidth : expandedWidth; }); } + @override + void initState() { + controllers = [ + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + DMIController(), + ]; + + super.initState(); + } + + @override + void dispose() { + for (var e in controllers) { + e.dispose(); + } + super.dispose(); + } + @override Widget build(BuildContext context) { return Material( color: Theme.of(context).extension()!.popupBG, - child: SizedBox( + child: AnimatedContainer( width: _width, + duration: duration, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: _width == expandedWidth ? 22 : 25, + const SizedBox( + height: 25, ), - SizedBox( + AnimatedContainer( + duration: duration, width: _width == expandedWidth ? 70 : 32, - height: _width == expandedWidth ? 70 : 32, + height: 70, //_width == expandedWidth ? 70 : 32, child: SvgPicture.asset( Assets.svg.stackIcon(context), ), @@ -76,18 +111,26 @@ class _DesktopMenuState extends ConsumerState { const SizedBox( height: 10, ), - Text( - _width == expandedWidth ? "Stack Wallet" : "", - style: STextStyles.desktopH2(context).copyWith( - fontSize: 18, - height: 23.4 / 18, + AnimatedOpacity( + duration: duration, + opacity: _width == expandedWidth ? 1 : 0, + child: SizedBox( + height: 28, + child: Text( + "Stack Wallet", + style: STextStyles.desktopH2(context).copyWith( + fontSize: 18, + height: 23.4 / 18, + ), + ), ), ), const SizedBox( height: 60, ), Expanded( - child: SizedBox( + child: AnimatedContainer( + duration: duration, width: _width == expandedWidth ? _width - 32 // 16 padding on either side : _width - 16, // 8 padding on either side @@ -95,6 +138,7 @@ class _DesktopMenuState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.walletDesktop, width: 20, @@ -116,12 +160,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[0], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.exchangeDesktop, width: 20, @@ -143,12 +188,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[1], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( ref.watch(notificationsProvider.select( (value) => value.hasUnreadNotifications)) @@ -177,12 +223,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[2], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.addressBookDesktop, width: 20, @@ -204,12 +251,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[3], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.gear, width: 20, @@ -231,12 +279,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[4], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.messageQuestion, width: 20, @@ -258,12 +307,13 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[5], ), const SizedBox( height: 2, ), DesktopMenuItem( + duration: duration, icon: SvgPicture.asset( Assets.svg.aboutDesktop, width: 20, @@ -285,10 +335,12 @@ class _DesktopMenuState extends ConsumerState { group: ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, - iconOnly: _width == minimizedWidth, + controller: controllers[6], ), const Spacer(), DesktopMenuItem( + duration: duration, + labelLength: 123, icon: SvgPicture.asset( Assets.svg.exitDesktop, width: 20, @@ -306,7 +358,7 @@ class _DesktopMenuState extends ConsumerState { // todo: save stuff/ notify before exit? exit(0); }, - iconOnly: _width == minimizedWidth, + controller: controllers[7], ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_menu_item.dart b/lib/pages_desktop_specific/home/desktop_menu_item.dart index 76d945e2d..e73a4a477 100644 --- a/lib/pages_desktop_specific/home/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/home/desktop_menu_item.dart @@ -2,7 +2,14 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -class DesktopMenuItem extends StatelessWidget { +class DMIController { + VoidCallback? toggle; + void dispose() { + toggle = null; + } +} + +class DesktopMenuItem extends StatefulWidget { const DesktopMenuItem({ Key? key, required this.icon, @@ -10,7 +17,9 @@ class DesktopMenuItem extends StatelessWidget { required this.value, required this.group, required this.onChanged, - required this.iconOnly, + required this.duration, + this.labelLength = 125, + this.controller, }) : super(key: key); final Widget icon; @@ -18,7 +27,67 @@ class DesktopMenuItem extends StatelessWidget { final T value; final T group; final void Function(T) onChanged; - final bool iconOnly; + final Duration duration; + final double labelLength; + final DMIController? controller; + + @override + State> createState() => _DesktopMenuItemState(); +} + +class _DesktopMenuItemState extends State> + with SingleTickerProviderStateMixin { + late final Widget icon; + late final String label; + late final T value; + late final T group; + late final void Function(T) onChanged; + late final Duration duration; + late final double labelLength; + + late final DMIController? controller; + + late final AnimationController animationController; + + bool _iconOnly = false; + + void toggle() { + setState(() { + _iconOnly = !_iconOnly; + }); + if (_iconOnly) { + animationController.reverse(); + } else { + animationController.forward(); + } + } + + @override + void initState() { + icon = widget.icon; + label = widget.label; + value = widget.value; + group = widget.group; + onChanged = widget.onChanged; + duration = widget.duration; + labelLength = widget.labelLength; + controller = widget.controller; + + controller?.toggle = toggle; + animationController = AnimationController( + vsync: this, + duration: duration, + ); + + super.initState(); + } + + @override + void dispose() { + controller?.dispose(); + animationController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -34,26 +103,42 @@ class DesktopMenuItem extends StatelessWidget { onChanged(value); }, child: Padding( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( vertical: 16, - horizontal: iconOnly ? 0 : 16, ), child: Row( - mainAxisAlignment: - iconOnly ? MainAxisAlignment.center : MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ + AnimatedContainer( + duration: duration, + width: _iconOnly ? 0 : 16, + ), icon, - if (!iconOnly) - const SizedBox( - width: 12, - ), - if (!iconOnly) - Text( - label, - style: value == group - ? STextStyles.desktopMenuItemSelected(context) - : STextStyles.desktopMenuItem(context), + AnimatedOpacity( + duration: duration, + opacity: _iconOnly ? 0 : 1.0, + child: SizeTransition( + sizeFactor: animationController, + axis: Axis.horizontal, + axisAlignment: -1, + child: SizedBox( + width: labelLength, + child: Row( + children: [ + const SizedBox( + width: 12, + ), + Text( + label, + style: value == group + ? STextStyles.desktopMenuItemSelected(context) + : STextStyles.desktopMenuItem(context), + ), + ], + ), + ), ), + ) ], ), ), From 6bbabcd729aa50d74e9351a3bb2948d740437b3a Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 28 Nov 2022 13:06:02 -0700 Subject: [PATCH 215/225] MyStackView tab after a restore backup --- .../sub_views/stack_restore_progress_view.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index c7f53378d..92e7742e1 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -25,6 +25,9 @@ import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; +import '../../../../../pages_desktop_specific/home/desktop_home_view.dart'; +import '../../../../../pages_desktop_specific/home/desktop_menu.dart'; +import '../../../../../providers/desktop/current_desktop_menu_item.dart'; import '../../../../../widgets/desktop/primary_button.dart'; class StackRestoreProgressView extends ConsumerStatefulWidget { @@ -685,7 +688,19 @@ class _StackRestoreProgressViewState enabled: true, label: "Done", onPressed: () async { - Navigator.of(context).pop(); + DesktopMenuItemId keyID = + DesktopMenuItemId.myStack; + + ref + .read(currentDesktopMenuItemProvider + .state) + .state = keyID; + + Navigator.of(context, rootNavigator: true) + .popUntil( + ModalRoute.withName( + DesktopHomeView.routeName), + ); }, ) : SecondaryButton( From c3921b01de78978a13874b8c3457a4837bc8846a Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 14:25:49 -0600 Subject: [PATCH 216/225] animated desktop stack icon --- .../home/desktop_menu.dart | 6 +-- .../home/desktop_menu_item.dart | 2 +- lib/widgets/desktop/living_stack_icon.dart | 54 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 lib/widgets/desktop/living_stack_icon.dart diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index 6ae4b91a1..8af307e7b 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/living_stack_icon.dart'; enum DesktopMenuItemId { myStack, @@ -103,9 +104,8 @@ class _DesktopMenuState extends ConsumerState { AnimatedContainer( duration: duration, width: _width == expandedWidth ? 70 : 32, - height: 70, //_width == expandedWidth ? 70 : 32, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: LivingStackIcon( + onPressed: toggleMinimize, ), ), const SizedBox( diff --git a/lib/pages_desktop_specific/home/desktop_menu_item.dart b/lib/pages_desktop_specific/home/desktop_menu_item.dart index e73a4a477..1fb39213b 100644 --- a/lib/pages_desktop_specific/home/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/home/desktop_menu_item.dart @@ -77,7 +77,7 @@ class _DesktopMenuItemState extends State> animationController = AnimationController( vsync: this, duration: duration, - ); + )..forward(); super.initState(); } diff --git a/lib/widgets/desktop/living_stack_icon.dart b/lib/widgets/desktop/living_stack_icon.dart new file mode 100644 index 000000000..7afc8f8d2 --- /dev/null +++ b/lib/widgets/desktop/living_stack_icon.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class LivingStackIcon extends StatefulWidget { + const LivingStackIcon({Key? key, this.onPressed,}) : super(key: key); + + final VoidCallback? onPressed; + + @override + State createState() => _LivingStackIconState(); +} + +class _LivingStackIconState extends State { + bool _hovering = false; + + late final VoidCallback? onPressed; + + @override + void initState() { + onPressed = widget.onPressed; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 76, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: GestureDetector( + onTap: () => onPressed?.call(), + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.2 : 1, + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + ), + ), + ), + ), + ); + } +} From b4cbf078c7a1a6105fc76bd92715b97447802d9e Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 28 Nov 2022 13:50:55 -0700 Subject: [PATCH 217/225] flutter_rounded_date_picker files from picker lib --- .../flutter_rounded_date_picker_dialog.dart | 332 ++++++++++++++++++ .../flutter_rounded_date_picker_widget.dart | 216 ++++++++++++ 2 files changed, 548 insertions(+) create mode 100644 lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart create mode 100644 lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart diff --git a/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart new file mode 100644 index 000000000..6d7f775cd --- /dev/null +++ b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart @@ -0,0 +1,332 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart'; +import 'package:flutter_rounded_date_picker/src/flutter_rounded_button_action.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_date_picker_header.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_month_picker.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_year_picker.dart'; +import 'package:stackwallet/utilities/util.dart'; + +/// +/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker +/// + +class FlutterRoundedDatePickerDialog extends StatefulWidget { + const FlutterRoundedDatePickerDialog( + {Key? key, + this.height, + required this.initialDate, + required this.firstDate, + required this.lastDate, + this.selectableDayPredicate, + required this.initialDatePickerMode, + required this.era, + this.locale, + required this.borderRadius, + this.imageHeader, + this.description = "", + this.fontFamily, + this.textNegativeButton, + this.textPositiveButton, + this.textActionButton, + this.onTapActionButton, + this.styleDatePicker, + this.styleYearPicker, + this.customWeekDays, + this.builderDay, + this.listDateDisabled, + this.onTapDay, + this.onMonthChange}) + : super(key: key); + + final DateTime initialDate; + final DateTime firstDate; + final DateTime lastDate; + final SelectableDayPredicate? selectableDayPredicate; + final DatePickerMode initialDatePickerMode; + + /// double height. + final double? height; + + /// Custom era year. + final EraMode era; + final Locale? locale; + + /// Border + final double borderRadius; + + /// Header; + final ImageProvider? imageHeader; + final String description; + + /// Font + final String? fontFamily; + + /// Button + final String? textNegativeButton; + final String? textPositiveButton; + final String? textActionButton; + + final VoidCallback? onTapActionButton; + + /// Style + final MaterialRoundedDatePickerStyle? styleDatePicker; + final MaterialRoundedYearPickerStyle? styleYearPicker; + + /// Custom Weekday + final List? customWeekDays; + + final BuilderDayOfDatePicker? builderDay; + + final List? listDateDisabled; + final OnTapDay? onTapDay; + + final Function? onMonthChange; + + @override + _FlutterRoundedDatePickerDialogState createState() => + _FlutterRoundedDatePickerDialogState(); +} + +class _FlutterRoundedDatePickerDialogState + extends State { + @override + void initState() { + super.initState(); + _selectedDate = widget.initialDate; + _mode = widget.initialDatePickerMode; + } + + bool _announcedInitialDate = false; + + late MaterialLocalizations localizations; + late TextDirection textDirection; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + localizations = MaterialLocalizations.of(context); + textDirection = Directionality.of(context); + if (!_announcedInitialDate) { + _announcedInitialDate = true; + SemanticsService.announce( + localizations.formatFullDate(_selectedDate), + textDirection, + ); + } + } + + late DateTime _selectedDate; + late DatePickerMode _mode; + final GlobalKey _pickerKey = GlobalKey(); + + void _vibrate() { + switch (Theme.of(context).platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + HapticFeedback.vibrate(); + break; + case TargetPlatform.iOS: + default: + break; + } + } + + void _handleModeChanged(DatePickerMode mode) { + _vibrate(); + setState(() { + _mode = mode; + if (_mode == DatePickerMode.day) { + SemanticsService.announce( + localizations.formatMonthYear(_selectedDate), + textDirection, + ); + } else { + SemanticsService.announce( + localizations.formatYear(_selectedDate), + textDirection, + ); + } + }); + } + + Future _handleYearChanged(DateTime value) async { + if (value.isBefore(widget.firstDate)) { + value = widget.firstDate; + } else if (value.isAfter(widget.lastDate)) { + value = widget.lastDate; + } + if (value == _selectedDate) return; + + if (widget.onMonthChange != null) await widget.onMonthChange!(value); + + _vibrate(); + setState(() { + _mode = DatePickerMode.day; + _selectedDate = value; + }); + } + + void _handleDayChanged(DateTime value) { + _vibrate(); + setState(() { + _selectedDate = value; + }); + } + + void _handleCancel() { + Navigator.of(context).pop(); + } + + void _handleOk() { + Navigator.of(context).pop(_selectedDate); + } + + Widget _buildPicker() { + switch (_mode) { + case DatePickerMode.year: + return FlutterRoundedYearPicker( + key: _pickerKey, + selectedDate: _selectedDate, + onChanged: (DateTime date) async => await _handleYearChanged(date), + firstDate: widget.firstDate, + lastDate: widget.lastDate, + era: widget.era, + fontFamily: widget.fontFamily, + style: widget.styleYearPicker, + ); + case DatePickerMode.day: + default: + return FlutterRoundedMonthPicker( + key: _pickerKey, + selectedDate: _selectedDate, + onChanged: _handleDayChanged, + firstDate: widget.firstDate, + lastDate: widget.lastDate, + era: widget.era, + locale: widget.locale, + selectableDayPredicate: widget.selectableDayPredicate, + fontFamily: widget.fontFamily, + style: widget.styleDatePicker, + borderRadius: widget.borderRadius, + customWeekDays: widget.customWeekDays, + builderDay: widget.builderDay, + listDateDisabled: widget.listDateDisabled, + onTapDay: widget.onTapDay, + onMonthChange: widget.onMonthChange); + } + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final Widget picker = _buildPicker(); + final isDesktop = Util.isDesktop; + + final Widget actions = FlutterRoundedButtonAction( + textButtonNegative: widget.textNegativeButton, + textButtonPositive: widget.textPositiveButton, + onTapButtonNegative: _handleCancel, + onTapButtonPositive: _handleOk, + textActionButton: widget.textActionButton, + onTapButtonAction: widget.onTapActionButton, + localizations: localizations, + textStyleButtonNegative: widget.styleDatePicker?.textStyleButtonNegative, + textStyleButtonPositive: widget.styleDatePicker?.textStyleButtonPositive, + textStyleButtonAction: widget.styleDatePicker?.textStyleButtonAction, + borderRadius: widget.borderRadius, + paddingActionBar: widget.styleDatePicker?.paddingActionBar, + background: widget.styleDatePicker?.backgroundActionBar, + ); + + Color backgroundPicker = theme.dialogBackgroundColor; + if (_mode == DatePickerMode.day) { + backgroundPicker = widget.styleDatePicker?.backgroundPicker ?? + theme.dialogBackgroundColor; + } else { + backgroundPicker = widget.styleYearPicker?.backgroundPicker ?? + theme.dialogBackgroundColor; + } + + final Dialog dialog = Dialog( + child: OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + final Widget header = FlutterRoundedDatePickerHeader( + selectedDate: _selectedDate, + mode: _mode, + onModeChanged: _handleModeChanged, + orientation: orientation, + era: widget.era, + borderRadius: widget.borderRadius, + imageHeader: widget.imageHeader, + description: widget.description, + fontFamily: widget.fontFamily, + style: widget.styleDatePicker); + switch (orientation) { + case Orientation.landscape: + return Container( + height: isDesktop ? 600 : null, + width: isDesktop ? 700 : null, + decoration: BoxDecoration( + color: backgroundPicker, + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible(flex: 1, child: header), + Flexible( + flex: 2, // have the picker take up 2/3 of the dialog width + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: isDesktop ? 530 : null, + width: isDesktop ? 700 : null, + child: picker), + actions, + ], + ), + ), + ], + ), + ); + case Orientation.portrait: + default: + return Container( + decoration: BoxDecoration( + color: backgroundPicker, + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + header, + if (widget.height == null) + Flexible(child: picker) + else + SizedBox( + height: widget.height, + child: picker, + ), + actions, + ], + ), + ); + } + }), + ); + + return Theme( + data: theme.copyWith(dialogBackgroundColor: Colors.transparent), + child: dialog, + ); + } +} diff --git a/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart new file mode 100644 index 000000000..5f576f480 --- /dev/null +++ b/lib/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart @@ -0,0 +1,216 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +// import 'package:flutter_rounded_date_picker/src/dialogs/flutter_rounded_date_picker_dialog.dart'; +import 'package:flutter_rounded_date_picker/src/era_mode.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart'; +import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart'; +import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart'; + +/// +/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker +/// + +// Examples can assume: +// BuildContext context; + +/// Initial display mode of the date picker dialog. +/// +/// Date picker UI mode for either showing a list of available years or a +/// monthly calendar initially in the dialog shown by calling [showDatePicker]. +/// + +// Shows the selected date in large font and toggles between year and day mode + +/// Signature for predicating dates for enabled date selections. +/// +/// See [showDatePicker]. +typedef SelectableDayPredicate = bool Function(DateTime day); + +/// Shows a dialog containing a material design date picker. +/// +/// The returned [Future] resolves to the date selected by the user when the +/// user closes the dialog. If the user cancels the dialog, null is returned. +/// +/// An optional [selectableDayPredicate] function can be passed in to customize +/// the days to enable for selection. If provided, only the days that +/// [selectableDayPredicate] returned true for will be selectable. +/// +/// An optional [initialDatePickerMode] argument can be used to display the +/// date picker initially in the year or month+day picker mode. It defaults +/// to month+day, and must not be null. +/// +/// An optional [locale] argument can be used to set the locale for the date +/// picker. It defaults to the ambient locale provided by [Localizations]. +/// +/// An optional [textDirection] argument can be used to set the text direction +/// (RTL or LTR) for the date picker. It defaults to the ambient text direction +/// provided by [Directionality]. If both [locale] and [textDirection] are not +/// null, [textDirection] overrides the direction chosen for the [locale]. +/// +/// The [context] argument is passed to [showDialog], the documentation for +/// which discusses how it is used. +/// +/// The [builder] parameter can be used to wrap the dialog widget +/// to add inherited widgets like [Theme]. +/// +/// {@tool sample} +/// Show a date picker with the dark theme. +/// +/// ```dart +/// Future selectedDate = showDatePicker( +/// context: context, +/// initialDate: DateTime.now(), +/// firstDate: DateTime(2018), +/// lastDate: DateTime(2030), +/// builder: (BuildContext context, Widget child) { +/// return Theme( +/// data: ThemeData.dark(), +/// child: child, +/// ); +/// }, +/// ); +/// ``` +/// {@end-tool} +/// +/// The [context], [initialDate], [firstDate], and [lastDate] parameters must +/// not be null. +/// +/// See also: +/// +/// * [showTimePicker], which shows a dialog that contains a material design +/// time picker. +/// * [DayPicker], which displays the days of a given month and allows +/// choosing a day. +/// * [MonthPicker], which displays a scrollable list of months to allow +/// picking a month. +/// * [YearPicker], which displays a scrollable list of years to allow picking +/// a year. +/// + +Future showRoundedDatePicker( + {required BuildContext context, + double? height, + DateTime? initialDate, + DateTime? firstDate, + DateTime? lastDate, + SelectableDayPredicate? selectableDayPredicate, + DatePickerMode initialDatePickerMode = DatePickerMode.day, + Locale? locale, + TextDirection? textDirection, + ThemeData? theme, + double borderRadius = 16, + EraMode era = EraMode.CHRIST_YEAR, + ImageProvider? imageHeader, + String description = "", + String? fontFamily, + bool barrierDismissible = false, + Color background = Colors.transparent, + String? textNegativeButton, + String? textPositiveButton, + String? textActionButton, + VoidCallback? onTapActionButton, + MaterialRoundedDatePickerStyle? styleDatePicker, + MaterialRoundedYearPickerStyle? styleYearPicker, + List? customWeekDays, + BuilderDayOfDatePicker? builderDay, + List? listDateDisabled, + OnTapDay? onTapDay, + Function? onMonthChange}) async { + initialDate ??= DateTime.now(); + firstDate ??= DateTime(initialDate.year - 1); + lastDate ??= DateTime(initialDate.year + 1); + theme ??= ThemeData(); + + assert( + !initialDate.isBefore(firstDate), + 'initialDate must be on or after firstDate', + ); + assert( + !initialDate.isAfter(lastDate), + 'initialDate must be on or before lastDate', + ); + assert( + !firstDate.isAfter(lastDate), + 'lastDate must be on or after firstDate', + ); + assert( + selectableDayPredicate == null || selectableDayPredicate(initialDate), + 'Provided initialDate must satisfy provided selectableDayPredicate', + ); + assert( + (onTapActionButton != null && textActionButton != null) || + onTapActionButton == null, + "If you provide onLeftBtn, you must provide leftBtn", + ); + assert(debugCheckHasMaterialLocalizations(context)); + + Widget child = GestureDetector( + onTap: () { + if (!barrierDismissible) { + Navigator.pop(context); + } + }, + child: Container( + color: background, + child: GestureDetector( + onTap: () { + // + }, + child: FlutterRoundedDatePickerDialog( + height: height, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + selectableDayPredicate: selectableDayPredicate, + initialDatePickerMode: initialDatePickerMode, + era: era, + locale: locale, + borderRadius: borderRadius, + imageHeader: imageHeader, + description: description, + fontFamily: fontFamily, + textNegativeButton: textNegativeButton, + textPositiveButton: textPositiveButton, + textActionButton: textActionButton, + onTapActionButton: onTapActionButton, + styleDatePicker: styleDatePicker, + styleYearPicker: styleYearPicker, + customWeekDays: customWeekDays, + builderDay: builderDay, + listDateDisabled: listDateDisabled, + onTapDay: onTapDay, + onMonthChange: onMonthChange, + ), + ), + ), + ); + + if (textDirection != null) { + child = Directionality( + textDirection: textDirection, + child: child, + ); + } + + if (locale != null) { + child = Localizations.override( + context: context, + locale: locale, + child: child, + ); + } + + return await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (_) => Theme(data: theme!, child: child), + ); +} From 3fef1ee67461e4ca3fd9078f3908679f9522953d Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 28 Nov 2022 14:10:44 -0700 Subject: [PATCH 218/225] desktop restore calendar resize --- .../restore_options_view/restore_options_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 1ce5d713a..a66af63fc 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -23,6 +23,8 @@ import 'package:stackwallet/utilities/util.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_date_picker/flutter_rounded_date_picker_widget.dart' + as datePicker; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -152,7 +154,7 @@ class _RestoreOptionsViewState extends ConsumerState { await Future.delayed(const Duration(milliseconds: 125)); } - final date = await showRoundedDatePicker( + final date = await datePicker.showRoundedDatePicker( context: context, initialDate: DateTime.now(), height: height * 0.5, From d7cd5cb8a9c556a29f6ef8e273a3f72503b523e3 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 15:21:18 -0600 Subject: [PATCH 219/225] desktop wallets table hover effects --- .../my_stack_view/coin_wallets_table.dart | 87 ++++++++++++-- lib/widgets/table_view/table_view_row.dart | 106 ++++++++++++++---- .../wallet_info_row/wallet_info_row.dart | 95 ++++++++-------- 3 files changed, 209 insertions(+), 79 deletions(-) diff --git a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart index b16a9bc58..4ed8765ae 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; class CoinWalletsTable extends ConsumerWidget { @@ -24,8 +25,10 @@ class CoinWalletsTable extends ConsumerWidget { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, + // horizontal: 20, + // vertical: 16, + horizontal: 6, + vertical: 6, ), child: Column( children: [ @@ -36,14 +39,26 @@ class CoinWalletsTable extends ConsumerWidget { const SizedBox( height: 32, ), - WalletInfoRow( - walletId: walletIds[i], - onPressed: () async { - await Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: walletIds[i], - ); - }, + Stack( + children: [ + WalletInfoRow( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + walletId: walletIds[i], + ), + Positioned.fill( + child: WalletRowHoverOverlay( + onPressed: () async { + await Navigator.of(context).pushNamed( + DesktopWalletView.routeName, + arguments: walletIds[i], + ); + }, + ), + ), + ], ), ], ), @@ -53,3 +68,55 @@ class CoinWalletsTable extends ConsumerWidget { ); } } + +class WalletRowHoverOverlay extends StatefulWidget { + const WalletRowHoverOverlay({ + Key? key, + required this.onPressed, + }) : super(key: key); + + final VoidCallback onPressed; + + @override + State createState() => _WalletRowHoverOverlayState(); +} + +class _WalletRowHoverOverlayState extends State { + late final VoidCallback onPressed; + + bool _hovering = false; + + @override + void initState() { + onPressed = widget.onPressed; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: GestureDetector( + onTap: onPressed, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: _hovering ? 0.1 : 0, + child: RoundedContainer( + color: + Theme.of(context).extension()!.buttonBackSecondary, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/table_view/table_view_row.dart b/lib/widgets/table_view/table_view_row.dart index e95eb68bd..9c3175efe 100644 --- a/lib/widgets/table_view/table_view_row.dart +++ b/lib/widgets/table_view/table_view_row.dart @@ -3,7 +3,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; -class TableViewRow extends StatelessWidget { +class TableViewRow extends StatefulWidget { const TableViewRow({ Key? key, required this.cells, @@ -17,40 +17,66 @@ class TableViewRow extends StatelessWidget { final List cells; final Widget? expandingChild; - final Decoration? decoration; + final BoxDecoration? decoration; final void Function(ExpandableState)? onExpandChanged; final EdgeInsetsGeometry padding; final double spacing; final CrossAxisAlignment crossAxisAlignment; + @override + State createState() => _TableViewRowState(); +} + +class _TableViewRowState extends State { + late final List cells; + late final Widget? expandingChild; + late final BoxDecoration? decoration; + late final void Function(ExpandableState)? onExpandChanged; + late final EdgeInsetsGeometry padding; + late final double spacing; + late final CrossAxisAlignment crossAxisAlignment; + + bool _hovering = false; + + @override + void initState() { + cells = widget.cells; + expandingChild = widget.expandingChild; + decoration = widget.decoration; + onExpandChanged = widget.onExpandChanged; + padding = widget.padding; + spacing = widget.spacing; + crossAxisAlignment = widget.crossAxisAlignment; + super.initState(); + } + @override Widget build(BuildContext context) { return Container( - decoration: decoration, + decoration: !_hovering + ? decoration + : decoration?.copyWith( + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + Theme.of(context).extension()!.standardBoxShadow, + ], + ), child: expandingChild == null - ? Padding( - padding: padding, - child: Row( - crossAxisAlignment: crossAxisAlignment, - children: [ - for (int i = 0; i < cells.length; i++) ...[ - if (i != 0 && i != cells.length) - SizedBox( - width: spacing, - ), - Expanded( - flex: cells[i].flex, - child: cells[i], - ), - ], - ], - ), - ) - : Expandable( - onExpandChanged: onExpandChanged, - header: Padding( + ? MouseRegion( + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: Padding( padding: padding, child: Row( + crossAxisAlignment: crossAxisAlignment, children: [ for (int i = 0; i < cells.length; i++) ...[ if (i != 0 && i != cells.length) @@ -65,6 +91,38 @@ class TableViewRow extends StatelessWidget { ], ), ), + ) + : Expandable( + onExpandChanged: onExpandChanged, + header: MouseRegion( + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: Padding( + padding: padding, + child: Row( + children: [ + for (int i = 0; i < cells.length; i++) ...[ + if (i != 0 && i != cells.length) + SizedBox( + width: spacing, + ), + Expanded( + flex: cells[i].flex, + child: cells[i], + ), + ], + ], + ), + ), + ), body: Column( children: [ Container( diff --git a/lib/widgets/wallet_info_row/wallet_info_row.dart b/lib/widgets/wallet_info_row/wallet_info_row.dart index fe006a67b..5bb51e2e6 100644 --- a/lib/widgets/wallet_info_row/wallet_info_row.dart +++ b/lib/widgets/wallet_info_row/wallet_info_row.dart @@ -14,10 +14,12 @@ class WalletInfoRow extends ConsumerWidget { Key? key, required this.walletId, this.onPressed, + this.padding = const EdgeInsets.all(0), }) : super(key: key); final String walletId; final VoidCallback? onPressed; + final EdgeInsets padding; @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,53 +32,56 @@ class WalletInfoRow extends ConsumerWidget { cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onPressed, - child: Container( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 4, - child: Row( - children: [ - WalletInfoCoinIcon(coin: manager.coin), - const SizedBox( - width: 12, - ), - Text( - manager.walletName, - style: - STextStyles.desktopTextExtraSmall(context).copyWith( + child: Padding( + padding: padding, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 4, + child: Row( + children: [ + WalletInfoCoinIcon(coin: manager.coin), + const SizedBox( + width: 12, + ), + Text( + manager.walletName, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + Expanded( + flex: 4, + child: WalletInfoRowBalanceFuture( + walletId: walletId, + ), + ), + Expanded( + flex: 6, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SvgPicture.asset( + Assets.svg.chevronRight, + width: 20, + height: 20, color: Theme.of(context) .extension()! - .textDark, - ), - ), - ], - ), - ), - Expanded( - flex: 4, - child: WalletInfoRowBalanceFuture( - walletId: walletId, - ), - ), - Expanded( - flex: 6, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SvgPicture.asset( - Assets.svg.chevronRight, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - ], - ), - ) - ], + .textSubtitle1, + ) + ], + ), + ) + ], + ), ), ), ), From 345a077e0611d3e294325bfd74529938f0c93e14 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 15:37:18 -0600 Subject: [PATCH 220/225] desktop fav card hover effect --- .../sub_widgets/favorite_card.dart | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 7749f264d..24b1e021a 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -49,6 +49,8 @@ class _FavoriteCardState extends ConsumerState { super.initState(); } + bool _hovering = false; + @override Widget build(BuildContext context) { final coin = ref.watch(managerProvider.select((value) => value.coin)); @@ -59,7 +61,48 @@ class _FavoriteCardState extends ConsumerState { condition: Util.isDesktop, builder: (child) => MouseRegion( cursor: SystemMouseCursors.click, - child: child, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.05 : 1, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: _hovering + ? BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + Theme.of(context) + .extension()! + .standardBoxShadow, + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + ) + : BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: child, + ), + ), ), child: GestureDetector( onTap: () { From 178565a19025d0348e04beded423db91110ba5b4 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 15:43:35 -0600 Subject: [PATCH 221/225] date picker file license added --- lib/widgets/rounded_date_picker/LICENSE | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lib/widgets/rounded_date_picker/LICENSE diff --git a/lib/widgets/rounded_date_picker/LICENSE b/lib/widgets/rounded_date_picker/LICENSE new file mode 100644 index 000000000..58665fbd2 --- /dev/null +++ b/lib/widgets/rounded_date_picker/LICENSE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file From 18da658a652863c7599e2602ba4b8472bfaab6f4 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 26 Nov 2022 13:39:52 -0600 Subject: [PATCH 222/225] persist active wallet on desktop --- .../home/desktop_home_view.dart | 71 +++++++++++++++++-- .../my_stack_view/coin_wallets_table.dart | 4 ++ .../wallet_view/desktop_wallet_view.dart | 2 +- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index 54c74fe88..3e0b9311b 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -9,12 +9,19 @@ import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_no import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +final currentWalletIdProvider = StateProvider((_) => null); + class DesktopHomeView extends ConsumerStatefulWidget { const DesktopHomeView({Key? key}) : super(key: key); @@ -25,12 +32,25 @@ class DesktopHomeView extends ConsumerStatefulWidget { } class _DesktopHomeViewState extends ConsumerState { - final Map contentViews = { - DesktopMenuItemId.myStack: const Navigator( - key: Key("desktopStackHomeKey"), + final GlobalKey key = GlobalKey(); + late final Navigator myStackViewNav; + + @override + void initState() { + myStackViewNav = Navigator( + key: key, onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, - ), + ); + super.initState(); + } + + final Map contentViews = { + DesktopMenuItemId.myStack: Container( + // key: Key("desktopStackHomeKey"), + // onGenerateRoute: RouteGenerator.generateRoute, + // initialRoute: MyStackView.routeName, + ), DesktopMenuItemId.exchange: const Navigator( key: Key("desktopExchangeHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, @@ -63,7 +83,30 @@ class _DesktopHomeViewState extends ConsumerState { ), }; + DesktopMenuItemId prev = DesktopMenuItemId.myStack; + void onMenuSelectionWillChange(DesktopMenuItemId newKey) { + if (prev == DesktopMenuItemId.myStack && prev == newKey) { + Navigator.of(key.currentContext!) + .popUntil(ModalRoute.withName(MyStackView.routeName)); + if (ref.read(currentWalletIdProvider.state).state != null) { + final managerProvider = ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(ref.read(currentWalletIdProvider.state).state!); + if (ref.read(managerProvider).shouldAutoSync) { + ref.read(managerProvider).shouldAutoSync = false; + } + ref.read(transactionFilterProvider.state).state = null; + if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled && + ref.read(prefsChangeNotifierProvider).backupFrequencyType == + BackupFrequencyType.afterClosingAWallet) { + ref.read(autoSWBServiceProvider).doBackup(); + } + ref.read(managerProvider.notifier).isActiveWallet = false; + } + } + prev = newKey; + // check for unread notifications and refresh provider before // showing notifications view if (newKey == DesktopMenuItemId.notifications) { @@ -111,9 +154,25 @@ class _DesktopHomeViewState extends ConsumerState { color: Theme.of(context).extension()!.background, ), Expanded( - child: contentViews[ - ref.watch(currentDesktopMenuItemProvider.state).state]!, + child: IndexedStack( + index: ref + .watch(currentDesktopMenuItemProvider.state) + .state + .index > + 0 + ? 1 + : 0, + children: [ + myStackViewNav, + contentViews[ + ref.watch(currentDesktopMenuItemProvider.state).state]!, + ], + ), ), + // Expanded( + // child: contentViews[ + // ref.watch(currentDesktopMenuItemProvider.state).state]!, + // ), ], ), ), diff --git a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart index 4ed8765ae..1edb93e06 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/coin_wallets_table.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -51,6 +52,9 @@ class CoinWalletsTable extends ConsumerWidget { Positioned.fill( child: WalletRowHoverOverlay( onPressed: () async { + ref.read(currentWalletIdProvider.state).state = + walletIds[i]; + await Navigator.of(context).pushNamed( DesktopWalletView.routeName, arguments: walletIds[i], diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart index 5996597b5..d870835f1 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -81,13 +81,13 @@ class _DesktopWalletViewState extends ConsumerState { // disable auto sync if it was enabled only when loading wallet ref.read(managerProvider).shouldAutoSync = false; } - ref.read(managerProvider.notifier).isActiveWallet = false; ref.read(transactionFilterProvider.state).state = null; if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled && ref.read(prefsChangeNotifierProvider).backupFrequencyType == BackupFrequencyType.afterClosingAWallet) { unawaited(ref.read(autoSWBServiceProvider).doBackup()); } + ref.read(managerProvider.notifier).isActiveWallet = false; } void _loadCNData() { From c9a91e10ac3d260a14714394c154bca7f7df591e Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 16:11:02 -0600 Subject: [PATCH 223/225] clean up theme init --- lib/main.dart | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a49bcab82..8136965db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -318,17 +318,17 @@ class _MaterialAppWithThemeState extends ConsumerState final colorScheme = DB.instance .get(boxName: DB.boxNameTheme, key: "colorScheme") as String?; - ThemeType themeType; + StackColorTheme colorTheme; switch (colorScheme) { case "dark": - themeType = ThemeType.dark; + colorTheme = DarkColors(); break; case "oceanBreeze": - themeType = ThemeType.oceanBreeze; + colorTheme = OceanBreezeColors(); break; case "light": default: - themeType = ThemeType.light; + colorTheme = LightColors(); } loadingCompleter = Completer(); WidgetsBinding.instance.addObserver(this); @@ -339,11 +339,7 @@ class _MaterialAppWithThemeState extends ConsumerState WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme(themeType == ThemeType.dark - ? DarkColors() - : (themeType == ThemeType.light - ? LightColors() - : OceanBreezeColors())); + StackColors.fromStackColorTheme(colorTheme); if (Platform.isAndroid) { // fetch open file if it exists From 1aca715397a4146059c53ca446eea229a868d73e Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 28 Nov 2022 16:17:33 -0600 Subject: [PATCH 224/225] animated desktop menu fix --- .../home/desktop_menu.dart | 16 ---------------- .../home/desktop_menu_item.dart | 14 +++++++------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index 8af307e7b..fd404db94 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -157,8 +157,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "My Stack", value: DesktopMenuItemId.myStack, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[0], ), @@ -185,8 +183,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exchange", value: DesktopMenuItemId.exchange, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[1], ), @@ -220,8 +216,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Notifications", value: DesktopMenuItemId.notifications, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[2], ), @@ -248,8 +242,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Address Book", value: DesktopMenuItemId.addressBook, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[3], ), @@ -276,8 +268,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Settings", value: DesktopMenuItemId.settings, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[4], ), @@ -304,8 +294,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Support", value: DesktopMenuItemId.support, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[5], ), @@ -332,8 +320,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "About", value: DesktopMenuItemId.about, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, controller: controllers[6], ), @@ -352,8 +338,6 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exit", value: 7, - group: - ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: (_) { // todo: save stuff/ notify before exit? exit(0); diff --git a/lib/pages_desktop_specific/home/desktop_menu_item.dart b/lib/pages_desktop_specific/home/desktop_menu_item.dart index 1fb39213b..78dcde79b 100644 --- a/lib/pages_desktop_specific/home/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/home/desktop_menu_item.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -9,13 +11,12 @@ class DMIController { } } -class DesktopMenuItem extends StatefulWidget { +class DesktopMenuItem extends ConsumerStatefulWidget { const DesktopMenuItem({ Key? key, required this.icon, required this.label, required this.value, - required this.group, required this.onChanged, required this.duration, this.labelLength = 125, @@ -25,22 +26,20 @@ class DesktopMenuItem extends StatefulWidget { final Widget icon; final String label; final T value; - final T group; final void Function(T) onChanged; final Duration duration; final double labelLength; final DMIController? controller; @override - State> createState() => _DesktopMenuItemState(); + ConsumerState> createState() => _DesktopMenuItemState(); } -class _DesktopMenuItemState extends State> +class _DesktopMenuItemState extends ConsumerState> with SingleTickerProviderStateMixin { late final Widget icon; late final String label; late final T value; - late final T group; late final void Function(T) onChanged; late final Duration duration; late final double labelLength; @@ -67,7 +66,6 @@ class _DesktopMenuItemState extends State> icon = widget.icon; label = widget.label; value = widget.value; - group = widget.group; onChanged = widget.onChanged; duration = widget.duration; labelLength = widget.labelLength; @@ -91,6 +89,8 @@ class _DesktopMenuItemState extends State> @override Widget build(BuildContext context) { + final group = ref.watch(currentDesktopMenuItemProvider.state).state; + debugPrint("============ value:$value ============ group:$group"); return TextButton( style: value == group ? Theme.of(context) From df4592214930849b033e921fb8e6bf275e3ce475 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Mon, 28 Nov 2022 17:02:26 -0700 Subject: [PATCH 225/225] Bump version. 1.5.21, build 93. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index ef1b495a5..8cfce39cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.20+92 +version: 1.5.21+93 environment: sdk: ">=2.17.0 <3.0.0"