From af47c67231eeb6cb1d04f2740a50847af55f9610 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Wed, 9 Nov 2022 12:58:38 -0600 Subject: [PATCH 01/44] 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<Widget>) conditionBranchBuilder; + final Widget Function(List<Widget>) otherBranchBuilder; + final List<Widget> 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 <julian@cypherstack.com> Date: Wed, 9 Nov 2022 13:12:41 -0600 Subject: [PATCH 02/44] 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<TradeDetailsView> { final sendAmount = Decimal.tryParse(trade.payInAmount) ?? Decimal.parse("-1"); - return Scaffold( - backgroundColor: Theme.of(context).extension<StackColors>()!.background, - appBar: AppBar( + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Scaffold( backgroundColor: Theme.of(context).extension<StackColors>()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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<StackColors>()! + .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<TradeDetailsView> { 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<TradeDetailsView> { ), ], ), - 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<StackColors>()! - .colorForStatus(trade.status), - ), - ), - // ), - // ), - ], - ), - ), - if (!sentFromStack && !hasTx) - const SizedBox( + ), + ), + isDesktop + ? const _Divider() + : const SizedBox( height: 12, ), - if (!sentFromStack && !hasTx) - RoundedContainer( - color: Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + .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<StackColors>()! + .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<StackColors>()! + .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<StackColors>()! .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<StackColors>()! - .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<void>( + 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<TradeDetailsView> { ), ], ), - ), - 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<StackColors>()! + .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<dynamic>( + 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<StackColors>()! - .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<StackColors>()! + .popupBG, + foregroundColor: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.button(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark), + ), + ), + ), ), ], ), - ), - ], - ), - const SizedBox( - height: 4, - ), - SelectableText( - trade.payInAddress, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 10, - ), - GestureDetector( - onTap: () { - showDialog<dynamic>( - 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<StackColors>()! - .popupBG, - foregroundColor: Theme.of( - context) - .extension<StackColors>()! - .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<StackColors>()! - .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<StackColors>()! - .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<StackColors>()! + .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<StackColors>()! - .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<void>( + 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<TradeDetailsView> { ], ), ), - ], - ), - const SizedBox( - height: 4, - ), - FutureBuilder( - future: ref.watch( - notesServiceChangeNotifierProvider(walletId!) - .select((value) => value.getNoteFor( - txid: transactionIfSentFromStack!.txid))), - builder: - (builderContext, AsyncSnapshot<String> 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<void>( + 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<StackColors>()! + .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<String> 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<StackColors>()! + .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<StackColors>()! - .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<StackColors>()! + .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<StackColors>()!.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<TransactionsList> { // 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<void>( + 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 <julian@cypherstack.com> Date: Wed, 9 Nov 2022 16:43:26 -0600 Subject: [PATCH 03/44] 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<dynamic> _boxPrefs; late final Box<TradeWalletLookup> _boxTradeLookup; late final Box<dynamic> _boxDBInfo; + late final Box<String> _boxDesktopData; final Map<String, Box<dynamic>> _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<dynamic>(DB.boxNameDBInfo); - int dbVersion = DB.instance.get<dynamic>( - 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<dynamic>( + 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<RestoreWalletView> { 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<CreatePinView> { @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<LockscreenView> { 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<AddEditNodeView> createState() => _AddEditNodeViewState(); @@ -533,7 +529,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> { 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<NodeDetailsView> { @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<ChangePinView> createState() => _ChangePinViewState(); + ConsumerState<ChangePinView> createState() => _ChangePinViewState(); } -class _ChangePinViewState extends State<ChangePinView> { +class _ChangePinViewState extends ConsumerState<ChangePinView> { BoxDecoration get _pinPutDecoration { return BoxDecoration( color: Theme.of(context).extension<StackColors>()!.textSubtitle2, @@ -53,7 +49,7 @@ class _ChangePinViewState extends State<ChangePinView> { @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<CreateAutoBackupView> createState() => _EnableAutoBackupViewState(); @@ -75,7 +69,7 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); @@ -585,7 +579,9 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> { 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<CreateBackupView> { ), if (!isDesktop) const Spacer(), !isDesktop - ? TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension<StackColors>()! + .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<dynamic>( - 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<void>.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<dynamic>( + 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<dynamic>( - 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<dynamic>( + 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<void>.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<dynamic>( + 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<dynamic>( + 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<dynamic>( - 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<void>.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<dynamic>( + 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<dynamic>( - 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<dynamic>( + 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<void>.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<dynamic>( + 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<dynamic>( + 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<EditAutoBackupView> createState() => _EditAutoBackupViewState(); } @@ -74,7 +68,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); passwordController = TextEditingController(); @@ -586,7 +580,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> { 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<Map<String, dynamic>> createStackWalletJSON({ - FlutterSecureStorageInterface? secureStorage, + required FlutterSecureStorageInterface secureStorage, }) async { Logging.instance .log("Starting createStackWalletJSON...", level: LogLevel.Info); final _wallets = Wallets.sharedInstance; Map<String, dynamic> 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<String, dynamic> validJSON, StackRestoringUIState? uiState, Map<String, String> oldToNewWalletIdMap, + FlutterSecureStorageInterface secureStorageInterface, ) async { Map<String, dynamic> prefs = validJSON["prefs"] as Map<String, dynamic>; List<dynamic>? 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<bool?> 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<bool> 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<void> _revert(PreRestoreState revertToState) async { + static Future<void> _revert( + PreRestoreState revertToState, + FlutterSecureStorageInterface secureStorageInterface, + ) async { Map<String, dynamic> prefs = revertToState.validJSON["prefs"] as Map<String, dynamic>; List<dynamic>? 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<void> _restoreNodes( List<dynamic>? nodes, List<dynamic>? 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<CreatePasswordView> 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>((_) => AutoSWBService()); +final autoSWBServiceProvider = ChangeNotifierProvider<AutoSWBService>( + (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<NodeService>((_) { + ChangeNotifierProvider<NodeService>((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<FlutterSecureStorageInterface>((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<WalletsService>((_) { + ChangeNotifierProvider<WalletsService>((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<void> 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<void> 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<ElectrumXNode> 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<void> 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<ElectrumXNode> 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<void> 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<ElectrumXNode> 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<void> 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<void> 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<ElectrumXNode> _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<void> 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<ElectrumXNode> 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<NodeModel> 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<void> 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<ElectrumXNode> 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<NodeModel> 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<void> 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<void> 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<dynamic>(DB.boxNameAllWalletsData); await Hive.openBox<dynamic>(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<ExchangeTransaction>(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<dynamic>(DB.boxNamePrefs); final prefs = Prefs.instance; @@ -154,7 +152,7 @@ class DbVersionMigrator { // update version await DB.instance.put<dynamic>( 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<void> 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<String>(DB.boxNameDesktopData); + await DB.instance.put<String>( + 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<String>(DB.boxNameDesktopData); + final keyBlob = DB.instance.get<String>( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + ); + await box.close(); if (keyBlob == null) { throw Exception( @@ -84,6 +89,12 @@ class DPS { } Future<bool> hasPassword() async { - return (await secureStorageWrapper.read(key: _kKeyBlobKey)) != null; + final box = await Hive.openBox<String>(DB.boxNameDesktopData); + final keyBlob = DB.instance.get<String>( + 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<void> 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<void> init() async {} + + Future<String?> read({ + required String key, + }) async { + // final String encryptedString = + + return ""; + } + + Future<void> write({ + required String key, + required String? value, + }) async { + return; + } + + Future<void> 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<String?> 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<Exception>())) .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<Exception>())) .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<dynamic>('wallets'); await wallets.put('names', null); - final service = WalletsService(); + final service = WalletsService(secureStorageInterface: FakeSecureStorage()); expect(await service.walletNames, <String, WalletInfo>{}); 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<dynamic>('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 <julian@cypherstack.com> Date: Wed, 9 Nov 2022 17:48:43 -0600 Subject: [PATCH 04/44] 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<EncryptedStringValue> 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<int> offsets, + Map<Type, List<int>> 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<int> offsets, + Map<Type, List<int>> allOffsets, +) { + writer.writeString(offsets[0], object.key); + writer.writeString(offsets[1], object.value); + return writer.usedBytes; +} + +EncryptedStringValue _encryptedStringValueDeserializeNative( + int id, + IsarBinaryReader reader, + List<int> offsets, + Map<Type, List<int>> allOffsets, +) { + final object = EncryptedStringValue(); + object.id = id; + object.key = reader.readString(offsets[0]); + object.value = reader.readString(offsets[1]); + return object; +} + +P _encryptedStringValueDeserializePropNative<P>( + Id id, + IsarBinaryReader reader, + int propertyId, + int offset, + Map<Type, List<int>> 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<EncryptedStringValue> collection, + EncryptedStringValue object) { + /*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError(); +} + +EncryptedStringValue _encryptedStringValueDeserializeWeb( + IsarCollection<EncryptedStringValue> 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<P>( + 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<IsarLinkBase<dynamic>> _encryptedStringValueGetLinks( + EncryptedStringValue object) { + return []; +} + +void _encryptedStringValueAttach( + IsarCollection<dynamic> col, Id id, EncryptedStringValue object) { + object.id = id; +} + +extension EncryptedStringValueByIndex on IsarCollection<EncryptedStringValue> { + Future<EncryptedStringValue?> getByKey(String key) { + return getByIndex(r'key', [key]); + } + + EncryptedStringValue? getByKeySync(String key) { + return getByIndexSync(r'key', [key]); + } + + Future<bool> deleteByKey(String key) { + return deleteByIndex(r'key', [key]); + } + + bool deleteByKeySync(String key) { + return deleteByIndexSync(r'key', [key]); + } + + Future<List<EncryptedStringValue?>> getAllByKey(List<String> keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return getAllByIndex(r'key', values); + } + + List<EncryptedStringValue?> getAllByKeySync(List<String> keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'key', values); + } + + Future<int> deleteAllByKey(List<String> keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'key', values); + } + + int deleteAllByKeySync(List<String> keyValues) { + final values = keyValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'key', values); + } + + Future<int> putByKey(EncryptedStringValue object) { + return putByIndex(r'key', object); + } + + int putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) { + return putByIndexSync(r'key', object, saveLinks: saveLinks); + } + + Future<List<int>> putAllByKey(List<EncryptedStringValue> objects) { + return putAllByIndex(r'key', objects); + } + + List<int> putAllByKeySync(List<EncryptedStringValue> objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'key', objects, saveLinks: saveLinks); + } +} + +extension EncryptedStringValueQueryWhereSort + on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhere> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhere> + anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension EncryptedStringValueQueryWhere + on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhereClause> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + idEqualTo(int id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + 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<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + idGreaterThan(int id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + idLessThan(int id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + 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<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + keyEqualTo(String key) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'key', + value: [key], + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause> + 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<EncryptedStringValue, + EncryptedStringValue, QFilterCondition> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> idEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> idGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> idLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> keyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> keyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> keyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> + keyContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'key', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> + keyMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'key', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> 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<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> + valueContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, + QAfterFilterCondition> + 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<EncryptedStringValue, + EncryptedStringValue, QFilterCondition> {} + +extension EncryptedStringValueQueryLinks on QueryBuilder<EncryptedStringValue, + EncryptedStringValue, QFilterCondition> {} + +extension EncryptedStringValueQuerySortBy + on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QSortBy> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + sortByKey() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.asc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + sortByKeyDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.desc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } +} + +extension EncryptedStringValueQuerySortThenBy + on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QSortThenBy> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenByKey() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.asc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenByKeyDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'key', Sort.desc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterSortBy> + thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } +} + +extension EncryptedStringValueQueryWhereDistinct + on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct> { + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct> + distinctByKey({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'key', caseSensitive: caseSensitive); + }); + } + + QueryBuilder<EncryptedStringValue, EncryptedStringValue, QDistinct> + distinctByValue({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value', caseSensitive: caseSensitive); + }); + } +} + +extension EncryptedStringValueQueryProperty on QueryBuilder< + EncryptedStringValue, EncryptedStringValue, QQueryProperty> { + QueryBuilder<EncryptedStringValue, int, QQueryOperations> idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder<EncryptedStringValue, String, QQueryOperations> keyProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'key'); + }); + } + + QueryBuilder<EncryptedStringValue, String, QQueryOperations> 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<String?> getPassword( - FlutterSecureStorageInterface secureStorage) async { + Future<String?> 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<CreatePinView> { 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<LockscreenView> { 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<NodeDetailsView> { - 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<ChangePinView> { 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<CreateAutoBackupView> { - 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<EditAutoBackupView> { - 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<Map<String, dynamic>> createStackWalletJSON({ - required FlutterSecureStorageInterface secureStorage, + required SecureStorageInterface secureStorage, }) async { Logging.instance .log("Starting createStackWalletJSON...", level: LogLevel.Info); @@ -448,7 +448,7 @@ abstract class SWB { Map<String, dynamic> validJSON, StackRestoringUIState? uiState, Map<String, String> oldToNewWalletIdMap, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { Map<String, dynamic> prefs = validJSON["prefs"] as Map<String, dynamic>; List<dynamic>? addressBookEntries = @@ -548,7 +548,7 @@ abstract class SWB { static Future<bool?> 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<void> _revert( PreRestoreState revertToState, - FlutterSecureStorageInterface secureStorageInterface, + SecureStorageInterface secureStorageInterface, ) async { Map<String, dynamic> prefs = revertToState.validJSON["prefs"] as Map<String, dynamic>; @@ -1042,7 +1042,7 @@ abstract class SWB { static Future<void> _restoreNodes( List<dynamic>? nodes, List<dynamic>? 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<CreatePasswordView> { } 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<DesktopLoginView> createState() => _DesktopLoginViewState(); + ConsumerState<DesktopLoginView> createState() => _DesktopLoginViewState(); } -class _DesktopLoginViewState extends State<DesktopLoginView> { +class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> { late final TextEditingController passwordController; late final FocusNode passwordFocusNode; @@ -153,13 +159,28 @@ class _DesktopLoginViewState extends State<DesktopLoginView> { 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<FlutterSecureStorageInterface>((ref) { +final secureStoreProvider = Provider<SecureStorageInterface>((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<String> _deleteWalletWrapper(String wallet) async { Future<String> 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<Map<String, WalletInfo>>? _walletNames; Future<Map<String, WalletInfo>> 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<void> 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<void> 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<void> init() async {} + Future<void> 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<String?> 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<void> 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<void> 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<String, String?> _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<void>.value(), ) as _i4.Future<void>); @override + _i4.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i4.Future<bool>.value(false), + ) as _i4.Future<bool>); + @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<void>.value(), ) as _i3.Future<void>); @override + _i3.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i3.Future<bool>.value(false), + ) as _i3.Future<bool>); + @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<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); + @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<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i16.Future<bool>.value(false), + ) as _i16.Future<bool>); + @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<bool>.value(false), ) as _i16.Future<bool>); + @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); } 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<void>.value(), ) as _i7.Future<void>); @override + _i7.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i7.Future<bool>.value(false), + ) as _i7.Future<bool>); + @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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i8.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i6.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i6.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i6.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i6.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i8.Future<void>.value(), + returnValueForMissingStub: _i8.Future<void>.value(), + ) as _i8.Future<void>); + @override _i8.Future<bool> 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<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i6.Future<Map<String, dynamic>>); @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<Map<String, dynamic>> 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<String?> read({ + _i3.Future<String?> 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<String?>.value(), - ) as _i4.Future<String?>); + returnValue: _i3.Future<String?>.value(), + ) as _i3.Future<String?>); @override - _i4.Future<void> write({ + _i3.Future<void> 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<void>.value(), - returnValueForMissingStub: _i4.Future<void>.value(), - ) as _i4.Future<void>); + returnValue: _i3.Future<void>.value(), + returnValueForMissingStub: _i3.Future<void>.value(), + ) as _i3.Future<void>); @override - _i4.Future<void> delete({ + _i3.Future<void> 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<void>.value(), - returnValueForMissingStub: _i4.Future<void>.value(), - ) as _i4.Future<void>); + returnValue: _i3.Future<void>.value(), + returnValueForMissingStub: _i3.Future<void>.value(), + ) as _i3.Future<void>); } 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<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); + @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<bool>.value(false), ) as _i16.Future<bool>); + @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); } 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<void>.value(), ) as _i10.Future<void>); @override + _i10.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i10.Future<bool>.value(false), + ) as _i10.Future<bool>); + @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<void>.value(), ) as _i15.Future<void>); @override + _i15.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i15.Future<void>.value(), + returnValueForMissingStub: _i15.Future<void>.value(), + ) as _i15.Future<void>); + @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<bool>.value(false), ) as _i15.Future<bool>); + @override + _i15.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i15.Future<void>.value(), + returnValueForMissingStub: _i15.Future<void>.value(), + ) as _i15.Future<void>); } 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<bool>.value(false), ) as _i16.Future<bool>); + @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); } /// 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<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); + @override _i16.Future<bool> testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, @@ -2312,6 +2340,14 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: _i16.Future<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<bool> isExternalCallsSet() => (super.noSuchMethod( + Invocation.method( + #isExternalCallsSet, + [], + ), + returnValue: _i16.Future<bool>.value(false), + ) as _i16.Future<bool>); + @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<void>.value(), ) as _i14.Future<void>); @override + _i14.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i14.Future<void>.value(), + returnValueForMissingStub: _i14.Future<void>.value(), + ) as _i14.Future<void>); + @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<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); + @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<bool>.value(false), ) as _i16.Future<bool>); + @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); } 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<void>.value(), ) as _i16.Future<void>); @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); + @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<bool>.value(false), ) as _i16.Future<bool>); + @override + _i16.Future<void> updateSentCachedTxData(Map<String, dynamic>? txData) => + (super.noSuchMethod( + Invocation.method( + #updateSentCachedTxData, + [txData], + ), + returnValue: _i16.Future<void>.value(), + returnValueForMissingStub: _i16.Future<void>.value(), + ) as _i16.Future<void>); } From 8b7e222d416ba2d38a1236a7a6b60b64efd21ffe Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Wed, 9 Nov 2022 17:54:12 -0600 Subject: [PATCH 05/44] 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 <julian@cypherstack.com> Date: Wed, 9 Nov 2022 17:55:10 -0600 Subject: [PATCH 06/44] 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<MaterialAppWithTheme> _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), ), ), - home: FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot<void> 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<void> 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 <julian@cypherstack.com> Date: Thu, 10 Nov 2022 11:40:33 -0600 Subject: [PATCH 07/44] 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<RestoreFromFileView> { bool hidePassword = true; Future<void> restoreBackupPopup(BuildContext context) async { - await showDialog<dynamic>( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const RestoreBackupDialog(); - }, - ); + // await showDialog<dynamic>( + // context: context, + // useSafeArea: false, + // barrierDismissible: true, + // builder: (context) { + // return const RestoreBackupDialog(); + // }, + // ); } @override From a50520b37ffdf1630aca2a9bd4f5a12f50db0cf7 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Thu, 10 Nov 2022 12:40:16 -0600 Subject: [PATCH 08/44] 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<dynamic> _boxAddressBook; - late final Box<String> _boxDebugInfo; - late final Box<NodeModel> _boxNodeModels; - late final Box<NodeModel> _boxPrimaryNodes; - late final Box<dynamic> _boxAllWalletsData; - late final Box<NotificationModel> _boxNotifications; - late final Box<NotificationModel> _boxWatchedTransactions; - late final Box<NotificationModel> _boxWatchedTrades; - late final Box<ExchangeTransaction> _boxTrades; - late final Box<Trade> _boxTradesV2; - late final Box<String> _boxTradeNotes; - late final Box<String> _boxFavoriteWallets; - late final Box<xmr.WalletInfo> _walletInfoSource; - late final Box<dynamic> _boxPrefs; - late final Box<TradeWalletLookup> _boxTradeLookup; - late final Box<dynamic> _boxDBInfo; - late final Box<String> _boxDesktopData; + Box<dynamic>? _boxAddressBook; + Box<String>? _boxDebugInfo; + Box<NodeModel>? _boxNodeModels; + Box<NodeModel>? _boxPrimaryNodes; + Box<dynamic>? _boxAllWalletsData; + Box<NotificationModel>? _boxNotifications; + Box<NotificationModel>? _boxWatchedTransactions; + Box<NotificationModel>? _boxWatchedTrades; + Box<ExchangeTransaction>? _boxTrades; + Box<Trade>? _boxTradesV2; + Box<String>? _boxTradeNotes; + Box<String>? _boxFavoriteWallets; + Box<xmr.WalletInfo>? _walletInfoSource; + Box<dynamic>? _boxPrefs; + Box<TradeWalletLookup>? _boxTradeLookup; + Box<dynamic>? _boxDBInfo; + Box<String>? _boxDesktopData; final Map<String, Box<dynamic>> _walletBoxes = {}; @@ -68,7 +68,7 @@ class DB { final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {}; // exposed for monero - Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource; + Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!; // mutex for stack backup final mutex = Mutex(); @@ -124,6 +124,12 @@ class DB { _boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData); } + if (Hive.isBoxOpen(boxNameDesktopData)) { + _boxDesktopData = Hive.box<String>(boxNameDesktopData); + } else { + _boxDesktopData = await Hive.openBox<String>(boxNameDesktopData); + } + _boxNotifications = await Hive.openBox<NotificationModel>(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<void> _loadWalletBoxes() async { - final names = _boxAllWalletsData.get("names") as Map? ?? {}; + final names = _boxAllWalletsData!.get("names") as Map? ?? {}; names.removeWhere((name, dyn) { final jsonObject = Map<String, dynamic>.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<MaterialAppWithTheme> static const platform = MethodChannel("STACK_WALLET_RESTORE"); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); - 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<MaterialAppWithTheme> bool didLoad = false; bool _desktopHasPassword = false; + Future<void> loadShared() async { + await DB.instance.init(); + await ref.read(prefsChangeNotifierProvider).init(); + + if (Util.isDesktop) { + _desktopHasPassword = + await ref.read(storageCryptoHandlerProvider).hasPassword(); + } + } + Future<void> load() async { try { if (didLoad) { @@ -239,19 +246,15 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme> } 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<MaterialAppWithTheme> 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<MaterialAppWithTheme> .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<MaterialAppWithTheme> } Future<void> 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<MaterialAppWithTheme> _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), ), ), - home: ConditionalParent( - condition: Util.isDesktop, - builder: (child) { - return child; - }, - child: FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot<void> 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<void> 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<void> Function()? load; @override ConsumerState<DesktopLoginView> createState() => _DesktopLoginViewState(); @@ -39,6 +41,32 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> { bool hidePassword = true; bool _continueEnabled = false; + Future<void> 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<DesktopLoginView> { 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<CreateAutoBackup> createState() => _CreateAutoBackup(); } @@ -51,7 +46,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { 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<CreateAutoBackup> { @override void initState() { - secureStore = widget.secureStore; + secureStore = ref.read(secureStoreProvider); stackFileSystem = StackFileSystem(); fileLocationController = TextEditingController(); @@ -686,7 +681,9 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { 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<bool> hasPassword() async { - final box = await Hive.openBox<String>(DB.boxNameDesktopData); final keyBlob = DB.instance.get<String>( boxName: DB.boxNameDesktopData, key: _kKeyBlobKey, ); - await box.close(); return keyBlob != null; } } From 3299f4ecd910c43e291bda713859db71d530615f Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Thu, 10 Nov 2022 15:07:44 -0600 Subject: [PATCH 09/44] 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>(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>(WalletInfo.boxName); + walletService = monero + .createMoneroWalletService(_walletInfoSource as Box<WalletInfo>); + } + 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>(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>(WalletInfo.boxName); + walletService = wownero + .createWowneroWalletService(_walletInfoSource as Box<WalletInfo>); + } + bool hasThrown = false; try { name = 'namee${Random().nextInt(10000000)}'; From 7105deeb24b49800d98527cf403e0cca224c5eec Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Thu, 10 Nov 2022 15:40:23 -0600 Subject: [PATCH 10/44] 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<DesktopLoginView> { .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<void> 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<void> 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 <rylee@cypherstack.com> Date: Thu, 10 Nov 2022 17:49:49 -0700 Subject: [PATCH 11/44] 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<RestoreFromFileView> { bool hidePassword = true; - Future<void> restoreBackupPopup(BuildContext context) async { - // await showDialog<dynamic>( - // context: context, - // useSafeArea: false, - // barrierDismissible: true, - // builder: (context) { - // return const RestoreBackupDialog(); - // }, - // ); - } - @override void initState() { stackFileSystem = StackFileSystem(); @@ -237,7 +226,7 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> { enableSuggestions: false, autocorrect: false, decoration: standardInputDecoration( - "Enter password", + "Enter passphrase", passwordFocusNode, context, ).copyWith( @@ -534,7 +523,7 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> { const EdgeInsets .all(32), child: Text( - "Restoring Stack Wallet", + "Restore Stack Wallet", style: STextStyles .desktopH3( context), @@ -546,12 +535,10 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> { 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<RestoreFromFileView> { 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<bool> _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<StackColors>()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.gear, - width: 16, - height: 16, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.gear, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .popupBG, + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.gear, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! - .buttonBackSecondary, - child: Center( - child: AddressBookIcon( - width: 16, - height: 16, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: AddressBookIcon( + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .popupBG, + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: AddressBookIcon( + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.node, - width: 16, - height: 16, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, + return !isDesktop + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.node, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .popupBG, + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.node, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 16, - height: 16, - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark, + return !isDesktop + ? Container( + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowRotate2, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .popupBG, + borderColor: Theme.of(context) + .extension<StackColors>()! + .background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowRotate2, + width: 16, + height: 16, + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! - .getPrimaryEnabledButtonColor(context), - child: Text( - _success ? "OK" : "Cancel restore process", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .buttonTextPrimary, - ), - ), - ), + child: !isDesktop + ? TextButton( + onPressed: () async { + if (_success) { + Navigator.of(context).pop(); + } else { + if (await _requestCancel()) { + await _cancel(); + } + } + }, + style: Theme.of(context) + .extension<StackColors>()! + .getPrimaryEnabledButtonColor(context), + child: Text( + _success ? "OK" : "Cancel restore process", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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<RestoringWalletCard> { 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<StackColors>()!.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<StackColors>()! - .buttonBackSecondary, - borderRadius: BorderRadius.circular( - 1000, - ), - ), - child: RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - splashColor: - Theme.of(context).extension<StackColors>()!.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<StackColors>()! + .buttonBackSecondary, + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + splashColor: + Theme.of(context).extension<StackColors>()!.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<StackColors>()! - .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<StackColors>()! + .accentColorDark), + ), + ), + ), + ) + : null, + ) + : RoundedContainer( + padding: EdgeInsets.all(0), + color: Theme.of(context).extension<StackColors>()!.popupBG, + borderColor: Theme.of(context).extension<StackColors>()!.background, + child: RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .buttonBackSecondary, + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + splashColor: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .accentColorDark), + ), + ), + ), + ) + : null, + ), + ); } } From de896d30bcfa87afb74a12bd6d2afe2c9aea141d Mon Sep 17 00:00:00 2001 From: ryleedavis <rylee@cypherstack.com> Date: Thu, 10 Nov 2022 22:14:08 -0700 Subject: [PATCH 12/44] 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<BackupRestoreSettings> { 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<StackColors>()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.button(context).copyWith( - color: - Theme.of(context).extension<StackColors>()!.accentColorDark, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .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 <rylee@cypherstack.com> Date: Thu, 10 Nov 2022 22:14:47 -0700 Subject: [PATCH 13/44] 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<StackColors>()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Back", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Yes, cancel", - style: STextStyles.itemSubtitle12(context).copyWith( - color: - Theme.of(context).extension<StackColors>()!.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<StackColors>()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Back", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Yes, cancel", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .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 <julian@cypherstack.com> Date: Fri, 11 Nov 2022 09:30:13 -0600 Subject: [PATCH 14/44] 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<dynamic>(boxName: walletId, key: "storedSyncingHeight") From ca8f63c07a28ae77cb02a15c4427fbd066e8f2f2 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Fri, 11 Nov 2022 09:31:01 -0600 Subject: [PATCH 15/44] 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<MaterialAppWithTheme> late final Completer<void> loadingCompleter; bool didLoad = false; + bool didLoadShared = false; bool _desktopHasPassword = false; Future<void> 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 <julian@cypherstack.com> Date: Fri, 11 Nov 2022 09:32:55 -0600 Subject: [PATCH 16/44] 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 <julian@cypherstack.com> Date: Fri, 11 Nov 2022 10:45:50 -0600 Subject: [PATCH 17/44] 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<bool> verifyPassphrase(String passphrase) async { + final box = await Hive.openBox<String>(DB.boxNameDesktopData); + final keyBlob = DB.instance.get<String>( + 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<bool> hasPassword() async { final keyBlob = DB.instance.get<String>( boxName: DB.boxNameDesktopData, From 9b09f65f4d44f3b0d8c1829eb5640c8e4eec33d4 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Fri, 11 Nov 2022 12:12:01 -0600 Subject: [PATCH 18/44] 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<dynamic>(DB.boxNameDBInfo); + // todo: db migrate stuff for desktop needs to be handled eventually if (!Util.isDesktop) { int dbVersion = DB.instance.get<dynamic>( 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<dynamic>(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<dynamic>(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 <julian@cypherstack.com> Date: Fri, 11 Nov 2022 12:37:44 -0600 Subject: [PATCH 19/44] 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<CreatePasswordView> { } 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 <rylee@cypherstack.com> Date: Fri, 11 Nov 2022 12:27:39 -0700 Subject: [PATCH 20/44] 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<SecuritySettings> { - Future<void> enableAutoBackup() async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future<void>.delayed( - const Duration(milliseconds: 100), - ); + late bool changePassword = false; - await showDialog<dynamic>( - 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<SecuritySettings> { ), 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<StackColors>()! + .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<StackColors>()! + .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<StackColors>()! + .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<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; + }); + }, + ), + ), + const SizedBox(height: 16), + Text( + "Confirm new password", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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: 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<StackColors>()! .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<StackColors>()!.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 <rylee@cypherstack.com> Date: Fri, 11 Nov 2022 12:42:04 -0700 Subject: [PATCH 21/44] 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<SecuritySettings> { 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<SecuritySettings> { 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<SecuritySettings> { }, ), ), + 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<StackColors>()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension<StackColors>()! + .accentColorYellow + : Theme.of(context) + .extension<StackColors>()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension<StackColors>()! + .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 <rylee@cypherstack.com> Date: Fri, 11 Nov 2022 15:58:47 -0700 Subject: [PATCH 22/44] 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 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_5370_82626)"> +<path d="M9.99935 18.3337C14.6017 18.3337 18.3327 14.6027 18.3327 10.0003C18.3327 5.39795 14.6017 1.66699 9.99935 1.66699C5.39698 1.66699 1.66602 5.39795 1.66602 10.0003C1.66602 14.6027 5.39698 18.3337 9.99935 18.3337Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M10 6.66699V13.3337" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M6.66602 10H13.3327" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> +</g> +<defs> +<clipPath id="clip0_5370_82626"> +<rect width="20" height="20" fill="white"/> +</clipPath> +</defs> +</svg> 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<DesktopAddressBook> { late final FocusNode _searchFocusNode; + List<Contact>? _cache; + List<Contact>? _cacheFav; + + late bool hasContacts = false; + String filter = ""; @override @@ -49,6 +57,7 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { return Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ DesktopAppBar( isCompactHeight: true, @@ -127,12 +136,81 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> { ), ), ), + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .textDark, + ), + ), + ], + ), + ), + ), + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .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<StackColors>()!.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 <julian@cypherstack.com> Date: Sat, 12 Nov 2022 09:16:07 -0600 Subject: [PATCH 23/44] 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<String> pathForWalletDir( } Future<String> 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<String> pathForWalletDir( } Future<String> 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 <julian@cypherstack.com> Date: Sat, 12 Nov 2022 09:17:16 -0600 Subject: [PATCH 24/44] 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 <rylee@cypherstack.com> Date: Sat, 12 Nov 2022 11:10:46 -0700 Subject: [PATCH 25/44] 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<AddressBookFilterView> { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).extension<StackColors>()!.background, - appBar: AppBar( - backgroundColor: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension<StackColors>()!.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<DesktopAddressBook> { String filter = ""; + Future<void> selectCryptocurrency() async { + await showDialog<dynamic>( + 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<DesktopAddressBook> { style: Theme.of(context) .extension<StackColors>()! .getDesktopMenuButtonColorSelected(context), - onPressed: () {}, + onPressed: () { + selectCryptocurrency(); + }, child: SizedBox( width: 200, height: 56, From 0164679cceac10602ceda50a8f93f8004fc076b1 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Sat, 12 Nov 2022 16:04:16 -0600 Subject: [PATCH 26/44] 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<dynamic>(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<DebugView> { 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<CreateAutoBackupView> { 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<CreateAutoBackupView> { @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<CreateBackupView> { 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<CreateBackupView> { @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<EditAutoBackupView> { 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<EditAutoBackupView> { @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<Directory> 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<RestoreFromFileView> { 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<CreateAutoBackup> { 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<CreateAutoBackup> { @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<String> 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<String> 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<void> 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<Directory> 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<Directory> 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<Directory> 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 <julian@cypherstack.com> Date: Sat, 12 Nov 2022 16:34:34 -0600 Subject: [PATCH 27/44] 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 <julian@cypherstack.com> Date: Sat, 12 Nov 2022 16:46:08 -0600 Subject: [PATCH 28/44] 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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 07:37:24 -0600 Subject: [PATCH 29/44] 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<DesktopSend> { Future<String?> _firoBalanceFuture( ChangeNotifierProvider<Manager> 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<DesktopSend> { Widget firoBalanceFutureBuilder( BuildContext context, AsyncSnapshot<String?> 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<DesktopSend> { 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<StackColors>()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet<dynamic>( - 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<StackColors>()! - .textSubtitle2, + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _firoBalanceFuture(provider, locale, true), + builder: (context, AsyncSnapshot<String?> 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<String?> 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<StackColors>()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), ), if (coin == Coin.firo) const SizedBox( From 9a9b10b1b3c244a5b0e0486187a85af8be055688 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 07:56:07 -0600 Subject: [PATCH 30/44] 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<int> _dropDownItems = [ - 12, - 22, - 234, - ]; - Future<void> _attemptSend(BuildContext context) async { unawaited(showDialog<dynamic>( 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<StackColors>()! + .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<StackColors>()!.textDark3, + // ), + // buttonPadding: const EdgeInsets.symmetric( + // horizontal: 16, + // vertical: 8, + // ), + // buttonDecoration: BoxDecoration( + // color: Theme.of(context) + // .extension<StackColors>()! + // .textFieldDefaultBG, + // borderRadius: BorderRadius.circular( + // Constants.size.circularBorderRadius, + // ), + // ), + // dropdownDecoration: BoxDecoration( + // color: Theme.of(context) + // .extension<StackColors>()! + // .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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 09:05:45 -0600 Subject: [PATCH 31/44] 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<void> _attemptSend(BuildContext context) async { - unawaited(showDialog<dynamic>( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return const SendingTransactionDialog(); - }, - )); + unawaited( + showDialog<dynamic>( + 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<StackColors>()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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<bool?>( + 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<SendingTransactionDialog> @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - return false; - }, - child: StackDialog( - title: "Sending transaction", - // // TODO get message from design team - // message: "<PLACEHOLDER>", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: Theme.of(context).extension<StackColors>()!.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<StackColors>()! + .accentColorDark, + width: 24, + height: 24, + ), + ), + ], ), ), - ), - ); + ); + } else { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: StackDialog( + title: "Sending transaction", + // // TODO get message from design team + // message: "<PLACEHOLDER>", + icon: RotationTransition( + turns: _spinAnimation, + child: SvgPicture.asset( + Assets.svg.arrowRotate, + color: + Theme.of(context).extension<StackColors>()!.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<DesktopAuthSend> createState() => _DesktopAuthSendState(); +} + +class _DesktopAuthSendState extends ConsumerState<DesktopAuthSend> { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + + bool _confirmEnabled = false; + + Future<bool> 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<StackColors>()!.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<StackColors>()! + .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<DesktopSend> { child: ConfirmTransactionView( transactionInfo: txData, walletId: walletId, + routeOnSuccessName: DesktopHomeView.routeName, ), ), ), From daa7708ad0196e16145a551b9f5574c0972ff8e0 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 09:20:35 -0600 Subject: [PATCH 32/44] 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<DesktopHomeView> { 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<DesktopMenu> { const SizedBox( height: 2, ), - DesktopMenuItem( - icon: SvgPicture.asset( - Assets.svg.exchangeDesktop, - width: 20, - height: 20, - color: 1 == selectedMenuItem - ? Theme.of(context) - .extension<StackColors>()! - .textDark - : Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + // .textDark + // : Theme.of(context) + // .extension<StackColors>()! + // .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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 09:25:06 -0600 Subject: [PATCH 33/44] 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<String> 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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 09:50:58 -0600 Subject: [PATCH 34/44] 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<DesktopHomeView> { - int currentViewIndex = 0; - final List<Widget> contentViews = [ - const Navigator( + DesktopMenuItemId currentViewKey = DesktopMenuItemId.myStack; + final Map<DesktopMenuItemId, Widget> contentViews = { + DesktopMenuItemId.myStack: const Navigator( key: Key("desktopStackHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, initialRoute: MyStackView.routeName, @@ -30,34 +31,36 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> { // // 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<DesktopHomeView> { color: Theme.of(context).extension<StackColors>()!.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<DesktopMenu> createState() => _DesktopMenuState(); @@ -25,13 +35,13 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { 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<DesktopMenu> { Assets.svg.walletDesktop, width: 20, height: 20, - color: 0 == selectedMenuItem + color: DesktopMenuItemId.myStack == selectedMenuItem ? Theme.of(context) .extension<StackColors>()! .textDark @@ -95,7 +105,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .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<DesktopMenu> { // Assets.svg.exchangeDesktop, // width: 20, // height: 20, - // color: 1 == selectedMenuItem + // color: DesktopMenuItemId.exchange == selectedMenuItem // ? Theme.of(context) // .extension<StackColors>()! // .textDark @@ -118,7 +128,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { // .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<DesktopMenu> { Assets.svg.bell, width: 20, height: 20, - color: 2 == selectedMenuItem - ? Theme.of(context) - .extension<StackColors>()! - .textDark - : Theme.of(context) - .extension<StackColors>()! - .textDark - .withOpacity(0.8), + color: + DesktopMenuItemId.notifications == selectedMenuItem + ? Theme.of(context) + .extension<StackColors>()! + .textDark + : Theme.of(context) + .extension<StackColors>()! + .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<DesktopMenu> { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: 3 == selectedMenuItem + color: DesktopMenuItemId.addressBook == selectedMenuItem ? Theme.of(context) .extension<StackColors>()! .textDark @@ -164,7 +175,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .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<DesktopMenu> { Assets.svg.gear, width: 20, height: 20, - color: 4 == selectedMenuItem + color: DesktopMenuItemId.settings == selectedMenuItem ? Theme.of(context) .extension<StackColors>()! .textDark @@ -187,7 +198,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .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<DesktopMenu> { Assets.svg.messageQuestion, width: 20, height: 20, - color: 5 == selectedMenuItem + color: DesktopMenuItemId.support == selectedMenuItem ? Theme.of(context) .extension<StackColors>()! .textDark @@ -210,7 +221,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .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<DesktopMenu> { Assets.svg.aboutDesktop, width: 20, height: 20, - color: 6 == selectedMenuItem + color: DesktopMenuItemId.about == selectedMenuItem ? Theme.of(context) .extension<StackColors>()! .textDark @@ -233,7 +244,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { .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<DesktopNotificationsView> createState() => + _DesktopNotificationsViewState(); +} + +class _DesktopNotificationsViewState + extends ConsumerState<DesktopNotificationsView> { + @override + Widget build(BuildContext context) { + final notifications = + ref.watch(notificationsProvider.select((value) => value.notifications)); + + return DesktopScaffold( + background: Theme.of(context).extension<StackColors>()!.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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 10:40:31 -0600 Subject: [PATCH 35/44] 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<StackColors>()! .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<StackColors>()! + .accentColorGreen, + ), + ) + ], + ), + child: Text( + notification.title, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .textSubtitle1, + ) + : STextStyles.label(context), ), Text( extractPrettyDateString(notification.date), - style: STextStyles.label(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .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<DesktopHomeView> { - DesktopMenuItemId currentViewKey = DesktopMenuItemId.myStack; final Map<DesktopMenuItemId, Widget> contentViews = { DesktopMenuItemId.myStack: const Navigator( key: Key("desktopStackHomeKey"), @@ -58,10 +60,36 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> { ), }; - 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<int> unreadNotificationIds = + ref.read(unreadNotificationsStateProvider.state).state; + + if (unreadNotificationIds.isNotEmpty) { + List<Future<void>> 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<DesktopHomeView> { child: Row( children: [ DesktopMenu( - onSelectionChanged: onMenuSelectionChanged, + // onSelectionChanged: onMenuSelectionChanged, + onSelectionWillChange: onMenuSelectionWillChange, ), Container( width: 1, color: Theme.of(context).extension<StackColors>()!.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<DesktopMenu> createState() => _DesktopMenuState(); @@ -35,12 +38,12 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { 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<DesktopMenu> { Assets.svg.walletDesktop, width: 20, height: 20, - color: DesktopMenuItemId.myStack == selectedMenuItem + color: DesktopMenuItemId.myStack == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension<StackColors>()! .textDark @@ -106,7 +112,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { ), 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<DesktopMenu> { // Assets.svg.exchangeDesktop, // width: 20, // height: 20, - // color: DesktopMenuItemId.exchange == selectedMenuItem + // color: DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state // ? Theme.of(context) // .extension<StackColors>()! // .textDark @@ -129,7 +136,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { // ), // 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<DesktopMenu> { Assets.svg.bell, width: 20, height: 20, - color: - DesktopMenuItemId.notifications == selectedMenuItem - ? Theme.of(context) - .extension<StackColors>()! - .textDark - : Theme.of(context) - .extension<StackColors>()! - .textDark - .withOpacity(0.8), + color: DesktopMenuItemId.notifications == + ref + .watch(currentDesktopMenuItemProvider.state) + .state + ? Theme.of(context) + .extension<StackColors>()! + .textDark + : Theme.of(context) + .extension<StackColors>()! + .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<DesktopMenu> { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: DesktopMenuItemId.addressBook == selectedMenuItem + color: DesktopMenuItemId.addressBook == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension<StackColors>()! .textDark @@ -176,7 +189,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { ), 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<DesktopMenu> { Assets.svg.gear, width: 20, height: 20, - color: DesktopMenuItemId.settings == selectedMenuItem + color: DesktopMenuItemId.settings == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension<StackColors>()! .textDark @@ -199,7 +216,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { ), 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<DesktopMenu> { Assets.svg.messageQuestion, width: 20, height: 20, - color: DesktopMenuItemId.support == selectedMenuItem + color: DesktopMenuItemId.support == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension<StackColors>()! .textDark @@ -222,7 +243,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { ), 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<DesktopMenu> { Assets.svg.aboutDesktop, width: 20, height: 20, - color: DesktopMenuItemId.about == selectedMenuItem + color: DesktopMenuItemId.about == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension<StackColors>()! .textDark @@ -245,7 +270,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> { ), 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<DesktopMenu> { ), 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<DesktopMenuItemId>((ref) => DesktopMenuItemId.myStack); From 48bfabf74e67331383bb2e5e3b635d9c1316da72 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 10:49:45 -0600 Subject: [PATCH 36/44] 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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 11:25:34 -0600 Subject: [PATCH 37/44] 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<BackupRestoreSettings> { useSafeArea: false, barrierDismissible: true, builder: (context) { - return CreateAutoBackup(); + return const CreateAutoBackup(); }, ); } @@ -428,6 +427,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { 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<CreateAutoBackup> { 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<CreateAutoBackup> { textAlign: TextAlign.center, ), ), - Padding( - padding: const EdgeInsets.all(20.0), - child: AppBarIconButton( - color: Theme.of(context) - .extension<StackColors>()! - .textFieldDefaultBG, - size: 40, - icon: SvgPicture.asset( - Assets.svg.x, - color: Theme.of(context).extension<StackColors>()!.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<CreateAutoBackup> { 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<CreateAutoBackup> { 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<CreateAutoBackup> { ), Expanded( child: PrimaryButton( + desktopMed: true, label: "Enable Auto Backup", enabled: shouldEnableCreate, onPressed: !shouldEnableCreate @@ -595,44 +574,89 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> { 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<dynamic>( - context: context, - barrierDismissible: false, - builder: (_) => const StackDialog( - title: "Encrypting initial backup", - message: "This shouldn't take long", + unawaited( + showDialog<dynamic>( + 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<CreateAutoBackup> { .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<CreateAutoBackup> { .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<CreateAutoBackup> { 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<CreateAutoBackup> { await showDialog<dynamic>( 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<dynamic>( 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 <rylee@cypherstack.com> Date: Mon, 14 Nov 2022 08:26:36 -0700 Subject: [PATCH 38/44] 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<DesktopAddressBook> { 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<DesktopAddressBook> { ); } + Future<void> newContact() async { + await showDialog<dynamic>( + 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<DesktopAddressBook> { 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<DesktopAddressBook> { 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<StackColors>()!.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<StackColors>()! - .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<StackColors>()! - .textDark, + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .textDark, + ), + ), + ], + ), ), ), - ), - const SizedBox(width: 20), - TextButton( - style: Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! - .popupBG, + const SizedBox(width: 20), + TextButton( + style: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .popupBG, + ), + ), + ], + ), ), ), - ), - ], + ], + ), ), ), Padding( From e91c99883b24f8171db4c296845742f7712f552b Mon Sep 17 00:00:00 2001 From: ryleedavis <rylee@cypherstack.com> Date: Mon, 14 Nov 2022 11:43:00 -0700 Subject: [PATCH 39/44] 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<BackupRestoreSettings> { width: 190, label: "Edit auto backup", onPressed: () { - Navigator.of(context).pop(); + // Navigator.of(context).pop(); createAutoBackup(); }, ), From 29ec03fb87572538f67b6d7832cca8896e8d4706 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 11:40:12 -0600 Subject: [PATCH 40/44] 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<BackupRestoreSettings> { ), ) : 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<StackColors>()! - .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<StackColors>()! + .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<BackupRestoreSettings> { crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( + RoundedContainer( width: 403, color: Theme.of(context) .extension<StackColors>()! .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 <julian@cypherstack.com> Date: Mon, 14 Nov 2022 12:19:26 -0600 Subject: [PATCH 41/44] 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<EditAutoBackupView> { late final SWBFileSystem stackFileSystem; final zxcvbn = Zxcvbn(); + late BackupFrequencyType _currentDropDownValue; + + final List<BackupFrequencyType> _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<EditAutoBackupView> { 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<dynamic>( + 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<void>.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<dynamic>( + 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<dynamic>( + 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<EditAutoBackupView> { fileLocationController.text = ref.read(prefsChangeNotifierProvider).autoBackupLocation ?? ""; + _currentDropDownValue = + ref.read(prefsChangeNotifierProvider).backupFrequencyType; + passwordFocusNode = FocusNode(); passwordRepeatFocusNode = FocusNode(); @@ -110,547 +278,509 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Scaffold( - backgroundColor: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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<StackColors>()!.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<StackColors>()! - .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<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); - } + 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<StackColors>()! + .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<StackColors>()!.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<StackColors>()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension<StackColors>()! - .accentColorYellow - : Theme.of(context) - .extension<StackColors>()! - .accentColorGreen, - backgroundColor: Theme.of(context) + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) .extension<StackColors>()! - .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<StackColors>()! - .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<StackColors>()! - .highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet<dynamic>( - 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<StackColors>()! - .textSubtitle2, - width: 12, - height: 6, - ), - ), - ], - ), - ), - ), - ) - ], - ), - const Spacer(), - const SizedBox( - height: 10, - ), - TextButton( - style: shouldEnableCreate - ? Theme.of(context) - .extension<StackColors>()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension<StackColors>()! - .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<dynamic>( - 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<void>.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<dynamic>( - 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<dynamic>( - 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<StackColors>()!.accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension<StackColors>()! + .accentColorYellow + : Theme.of(context) + .extension<StackColors>()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .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<StackColors>()!.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<StackColors>()!.textDark3, + ), + buttonPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + buttonDecoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + dropdownDecoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showModalBottomSheet<dynamic>( + 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<StackColors>()! + .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<StackColors>()! + .getPrimaryEnabledButtonColor(context) + : Theme.of(context) + .extension<StackColors>()! + .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<BackupRestoreSettings> { ); } + Future<void> editAutoBackup() async { + await showDialog<dynamic>( + 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<void> attemptDisable() async { final result = await showDialog<bool?>( context: context, @@ -440,8 +479,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> { 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<CreateAutoBackup> { ), ), const SizedBox( - height: 10, + height: 16, ), ClipRRect( borderRadius: BorderRadius.circular( From e053764554ef5ab32226110a0ed18a9d6ac1c5c5 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 13:24:40 -0600 Subject: [PATCH 42/44] 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<SecuritySettings> { 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<bool> 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<SecuritySettings> { 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<StackColors>()! - .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<StackColors>()! + .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<StackColors>()! - .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<StackColors>()! - .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( + child: TextField( key: const Key( - "desktopSecurityCreateStackBackUpProgressBar"), - width: 450, - height: 5, - fillColor: passwordStrength < 0.51 - ? Theme.of(context) - .extension<StackColors>()! - .accentColorRed - : passwordStrength < 1 - ? Theme.of(context) - .extension<StackColors>()! - .accentColorYellow - : Theme.of(context) - .extension<StackColors>()! - .accentColorGreen, - backgroundColor: Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! - .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, + "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<StackColors>()! + .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<StackColors>()! + .accentColorRed + : passwordStrength < 1 + ? Theme.of(context) + .extension<StackColors>()! + .accentColorYellow + : Theme.of(context) + .extension<StackColors>()! + .accentColorGreen, + backgroundColor: Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .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<StackColors>()! - .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<bool> changePassphrase( + String passphraseOld, + String passphraseNew, + ) async { + final box = await Hive.openBox<String>(DB.boxNameDesktopData); + final keyBlob = DB.instance.get<String>( + 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<String>(DB.boxNameDesktopData); + await DB.instance.put<String>( + 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<bool> hasPassword() async { final keyBlob = DB.instance.get<String>( boxName: DB.boxNameDesktopData, From 9df0569bb16fb17583f97c83227d7b83b5cd0068 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 14 Nov 2022 13:35:14 -0600 Subject: [PATCH 43/44] 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<void>( - // 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 <rylee@cypherstack.com> Date: Mon, 14 Nov 2022 13:29:43 -0700 Subject: [PATCH 44/44] 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<StackColors>()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future<void>.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<StackColors>()!.background, - icon: SvgPicture.asset( - Assets.svg.star, - color: _isFavorite - ? Theme.of(context) - .extension<StackColors>()! - .favoriteStarActive - : Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future<void>.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<StackColors>()! + .background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _isFavorite + ? Theme.of(context) + .extension<StackColors>()! + .favoriteStarActive + : Theme.of(context) + .extension<StackColors>()! + .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<StackColors>()! + .background, + icon: SvgPicture.asset( + Assets.svg.star, + color: _isFavorite + ? Theme.of(context) + .extension<StackColors>()! + .favoriteStarActive + : Theme.of(context) + .extension<StackColors>()! + .favoriteStarInactive, + width: 20, + height: 20, + ), + onPressed: () { setState(() { - _selectedEmoji = null; + _isFavorite = !_isFavorite; }); - return; - } - showModalBottomSheet<dynamic>( - 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<StackColors>()! - .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<dynamic>( + 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<dynamic>( + 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<StackColors>()! - .accentColorDark), + borderRadius: BorderRadius.circular(24), + color: Theme.of(context) + .extension<StackColors>()! + .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: 24, + width: 24, ) - : SvgPicture.asset( - Assets.svg.thickX, - color: Theme.of(context) - .extension<StackColors>()! - .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<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, + ), ), - ) - : 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<StackColors>()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension<StackColors>()! - .accentColorDark), - ), - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future<void>.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<StackColors>()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension<StackColors>()! - .getPrimaryDisabledButtonColor( - context), - onPressed: shouldEnableSave - ? () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future<void>.delayed( - const Duration(milliseconds: 75), - ); - } - List<ContactAddressEntry> 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<StackColors>()! - .buttonTextPrimary - : Theme.of(context) - .extension<StackColors>()! - .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<StackColors>()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .accentColorDark), + ), + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future<void>.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<StackColors>()! + .getPrimaryEnabledButtonColor( + context) + : Theme.of(context) + .extension<StackColors>()! + .getPrimaryDisabledButtonColor( + context), + onPressed: shouldEnableSave + ? () async { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context).unfocus(); + await Future<void>.delayed( + const Duration( + milliseconds: 75), + ); + } + List<ContactAddressEntry> 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<StackColors>()! + .buttonTextPrimary + : Theme.of(context) + .extension<StackColors>()! + .buttonTextPrimaryDisabled, + ), + ), + ); + }, + ), + ), + ], + ), + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ); }