From 340cb3ccc38a0ca2ac36ddd45ce32353c3f1efeb Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 18 Sep 2022 19:27:25 -0600 Subject: [PATCH] WIP desktop restore ui and wallets overview layout --- lib/main.dart | 9 + .../restore_options_view.dart | 633 ++++++++++-------- .../restore_wallet_view.dart | 213 +++--- .../home/my_stack_view/my_stack_view.dart | 3 +- .../home/my_stack_view/my_wallets.dart | 70 ++ .../home/my_stack_view/wallet_table.dart | 155 +++++ lib/utilities/cfcolors.dart | 4 +- 7 files changed, 705 insertions(+), 382 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_table.dart diff --git a/lib/main.dart b/lib/main.dart index 3ae9a065b..82d75c5d8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,6 +29,7 @@ import 'package:stackwallet/pages/loading_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/change_now_provider.dart'; @@ -603,6 +604,14 @@ class _MaterialAppWithThemeState extends ConsumerState ref.read(prefsChangeNotifierProvider).startupWalletId; } + // TODO proper desktop auth view + if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { + Future.delayed(Duration.zero).then((value) => + Navigator.of(context).pushNamedAndRemoveUntil( + DesktopHomeView.routeName, (route) => false)); + return Container(); + } + return LockscreenView( isInitialAppLogin: true, routeOnSuccess: HomeView.routeName, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view.dart index d7f78041d..69234f9eb 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart'; @@ -13,6 +15,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -35,9 +39,9 @@ class RestoreOptionsView extends ConsumerStatefulWidget { class _RestoreOptionsViewState extends ConsumerState { late final String walletName; late final Coin coin; + late final bool isDesktop; late TextEditingController _dateController; - late TextEditingController _lengthController; late FocusNode textFieldFocusNode; final bool _nextEnabled = true; @@ -47,9 +51,9 @@ class _RestoreOptionsViewState extends ConsumerState { void initState() { walletName = widget.walletName; coin = widget.coin; + isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux; _dateController = TextEditingController(); - _lengthController = TextEditingController(); textFieldFocusNode = FocusNode(); super.initState(); @@ -58,7 +62,6 @@ class _RestoreOptionsViewState extends ConsumerState { @override void dispose() { _dateController.dispose(); - _lengthController.dispose(); textFieldFocusNode.dispose(); super.dispose(); } @@ -122,26 +125,225 @@ class _RestoreOptionsViewState extends ConsumerState { ); } + Future nextPressed() async { + if (!isDesktop) { + // hide keyboard if has focus + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + } + + if (mounted) { + await Navigator.of(context).pushNamed( + RestoreWalletView.routeName, + arguments: Tuple4( + walletName, + coin, + ref.read(mnemonicWordCountStateProvider.state).state, + _restoreFromDate, + ), + ); + } + } + + Future chooseDate() async { + final height = MediaQuery.of(context).size.height; + // check and hide keyboard + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 125)); + } + + final date = await showRoundedDatePicker( + context: context, + initialDate: DateTime.now(), + height: height * 0.5, + theme: ThemeData( + primarySwatch: CFColors.createMaterialColor(CFColors.stackAccent), + ), + //TODO pick a better initial date + // 2007 chosen as that is just before bitcoin launched + firstDate: DateTime(2007), + lastDate: DateTime.now(), + borderRadius: Constants.size.circularBorderRadius * 2, + + textPositiveButton: "SELECT", + + styleDatePicker: _buildDatePickerStyle(), + styleYearPicker: _buildYearPickerStyle(), + ); + if (date != null) { + _restoreFromDate = date; + _dateController.text = Format.formatDate(date); + } + } + + Future chooseMnemonicLength() async { + await showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) { + return MnemonicWordCountSelectSheet( + lengthOptions: Constants.possibleLengthsForCoin(coin), + ); + }, + ); + } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType with ${coin.name} $walletName"); - return Scaffold( - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - if (textFieldFocusNode.hasFocus) { - textFieldFocusNode.unfocus(); - Future.delayed(const Duration(milliseconds: 100)) - .then((value) => Navigator.of(context).pop()); - } else { - Navigator.of(context).pop(); - } - }, + return DesktopScaffold( + appBar: isDesktop + ? const DesktopAppBar( + isCompactHeight: false, + leading: AppBarBackButton(), + ) + : AppBar( + leading: AppBarBackButton( + onPressed: () { + if (textFieldFocusNode.hasFocus) { + textFieldFocusNode.unfocus(); + Future.delayed(const Duration(milliseconds: 100)) + .then((value) => Navigator.of(context).pop()); + } else { + Navigator.of(context).pop(); + } + }, + ), + ), + body: PlatformRestoreOptionsLayout( + isDesktop: isDesktop, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + const Spacer( + flex: 1, + ), + if (!isDesktop) + Image( + image: AssetImage( + Assets.png.imageFor(coin: coin), + ), + height: 100, + ), + SizedBox( + height: isDesktop ? 24 : 16, + ), + Text( + "Restore options", + textAlign: TextAlign.center, + style: + isDesktop ? STextStyles.desktopH2 : STextStyles.pageTitleH1, + ), + SizedBox( + height: isDesktop ? 40 : 24, + ), + if (coin == Coin.monero || coin == Coin.epicCash) + Text( + "Choose start date", + style: isDesktop + ? STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textFieldActiveSearchIconRight, + ) + : STextStyles.smallMed12, + textAlign: TextAlign.left, + ), + if (coin == Coin.monero || coin == Coin.epicCash) + SizedBox( + height: isDesktop ? 16 : 8, + ), + if (coin == Coin.monero || coin == Coin.epicCash) + + // if (!isDesktop) + RestoreFromDatePicker( + onTap: chooseDate, + ), + + // if (isDesktop) + // // TODO desktop date picker + if (coin == Coin.monero || coin == Coin.epicCash) + const SizedBox( + height: 8, + ), + if (coin == Coin.monero || coin == Coin.epicCash) + RoundedWhiteContainer( + child: Center( + child: Text( + "Choose the date you made the wallet (approximate is fine)", + style: isDesktop + ? STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textSubtitle1, + ) + : STextStyles.smallMed12.copyWith( + fontSize: 10, + ), + ), + ), + ), + if (coin == Coin.monero || coin == Coin.epicCash) + SizedBox( + height: isDesktop ? 24 : 16, + ), + Text( + "Choose recovery phrase length", + style: isDesktop + ? STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textFieldActiveSearchIconRight, + ) + : STextStyles.smallMed12, + textAlign: TextAlign.left, + ), + SizedBox( + height: isDesktop ? 16 : 8, + ), + MobileMnemonicLengthSelector( + chooseMnemonicLength: chooseMnemonicLength, + ), + if (!isDesktop) + const Spacer( + flex: 3, + ), + if (isDesktop) + const SizedBox( + height: 32, + ), + RestoreNextButton( + isDesktop: isDesktop, + onPressed: _nextEnabled ? nextPressed : null, + ), + ], ), ), - body: Container( - color: CFColors.almostWhite, + ); + } +} + +class PlatformRestoreOptionsLayout extends StatelessWidget { + const PlatformRestoreOptionsLayout({ + Key? key, + required this.isDesktop, + required this.child, + }) : super(key: key); + + final bool isDesktop; + final Widget child; + + @override + Widget build(BuildContext context) { + if (isDesktop) { + return Container(); + } else { + return Container( + color: CFColors.background, child: Padding( padding: const EdgeInsets.all(16), child: LayoutBuilder( @@ -150,259 +352,162 @@ class _RestoreOptionsViewState extends ConsumerState { child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Spacer( - flex: 1, - ), - Image( - image: AssetImage( - Assets.png.imageFor(coin: coin), - ), - height: 100, - ), - const SizedBox( - height: 16, - ), - Text( - "Restore options", - textAlign: TextAlign.center, - style: STextStyles.pageTitleH1, - ), - const SizedBox( - height: 24, - ), - if (coin == Coin.monero || coin == Coin.epicCash) - Text( - "Choose start date", - style: STextStyles.smallMed12, - textAlign: TextAlign.left, - ), - if (coin == Coin.monero || coin == Coin.epicCash) - const SizedBox( - height: 8, - ), - if (coin == Coin.monero || coin == Coin.epicCash) - Container( - color: Colors.transparent, - child: TextField( - onTap: () async { - final height = - MediaQuery.of(context).size.height; - // check and hide keyboard - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 125)); - } - - final date = await showRoundedDatePicker( - context: context, - initialDate: DateTime.now(), - height: height * 0.5, - theme: ThemeData( - primarySwatch: CFColors.createMaterialColor( - CFColors.stackAccent), - ), - //TODO pick a better initial date - // 2007 chosen as that is just before bitcoin launched - firstDate: DateTime(2007), - lastDate: DateTime.now(), - borderRadius: - Constants.size.circularBorderRadius * 2, - - textPositiveButton: "SELECT", - - styleDatePicker: _buildDatePickerStyle(), - styleYearPicker: _buildYearPickerStyle(), - ); - if (date != null) { - _restoreFromDate = date; - _dateController.text = - Format.formatDate(date); - } - }, - controller: _dateController, - style: STextStyles.field, - decoration: InputDecoration( - hintText: "Restore from...", - suffixIcon: UnconstrainedBox( - child: Row( - children: [ - const SizedBox( - width: 16, - ), - SvgPicture.asset( - Assets.svg.calendar, - color: CFColors.neutral50, - width: 16, - height: 16, - ), - const SizedBox( - width: 12, - ), - ], - ), - ), - ), - key: const Key("restoreOptionsViewDatePickerKey"), - readOnly: true, - toolbarOptions: const ToolbarOptions( - copy: true, - cut: false, - paste: false, - selectAll: false, - ), - onChanged: (newValue) {}, - ), - ), - if (coin == Coin.monero || coin == Coin.epicCash) - const SizedBox( - height: 8, - ), - if (coin == Coin.monero || coin == Coin.epicCash) - RoundedWhiteContainer( - child: Center( - child: Text( - "Choose the date you made the wallet (approximate is fine)", - style: STextStyles.smallMed12.copyWith( - fontSize: 10, - ), - ), - ), - ), - if (coin == Coin.monero || coin == Coin.epicCash) - const SizedBox( - height: 16, - ), - Text( - "Choose recovery phrase length", - style: STextStyles.smallMed12, - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, - ), - Stack( - children: [ - TextField( - controller: _lengthController, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - ), - child: RawMaterialButton( - splashColor: CFColors.splashLight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) { - return MnemonicWordCountSelectSheet( - lengthOptions: - Constants.possibleLengthsForCoin( - coin), - ); - }, - ); - }, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "${ref.watch(mnemonicWordCountStateProvider.state).state} words", - style: STextStyles.itemSubtitle12, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: CFColors.gray3, - ), - ], - ), - ), - ) - ], - ), - const Spacer( - flex: 3, - ), - TextButton( - onPressed: _nextEnabled - ? () async { - // hide keyboard if has focus - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - - if (mounted) { - Navigator.of(context).pushNamed( - RestoreWalletView.routeName, - arguments: Tuple4( - walletName, - coin, - ref - .read(mnemonicWordCountStateProvider - .state) - .state, - _restoreFromDate, - ), - ); - } - } - : null, - style: _nextEnabled - ? Theme.of(context) - .textButtonTheme - .style - ?.copyWith( - backgroundColor: - MaterialStateProperty.all( - CFColors.stackAccent, - ), - ) - : Theme.of(context) - .textButtonTheme - .style - ?.copyWith( - backgroundColor: - MaterialStateProperty.all( - CFColors.stackAccent.withOpacity( - 0.25, - ), - ), - ), - child: Text( - "Next", - style: STextStyles.button, - ), - ), - ], - ), + child: child, ), ), ); }, ), ), + ); + } + } +} + +class RestoreFromDatePicker extends StatefulWidget { + const RestoreFromDatePicker({Key? key, required this.onTap}) + : super(key: key); + + final VoidCallback onTap; + + @override + State createState() => _RestoreFromDatePickerState(); +} + +class _RestoreFromDatePickerState extends State { + late final TextEditingController _dateController; + late final VoidCallback onTap; + + @override + void initState() { + onTap = widget.onTap; + _dateController = TextEditingController(); + + super.initState(); + } + + @override + void dispose() { + _dateController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + child: TextField( + onTap: onTap, + controller: _dateController, + style: STextStyles.field, + decoration: InputDecoration( + hintText: "Restore from...", + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.calendar, + color: CFColors.neutral50, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + key: const Key("restoreOptionsViewDatePickerKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, + ), + ); + } +} + +class MobileMnemonicLengthSelector extends ConsumerWidget { + const MobileMnemonicLengthSelector({ + Key? key, + required this.chooseMnemonicLength, + }) : super(key: key); + + final VoidCallback chooseMnemonicLength; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Stack( + children: [ + const TextField( + // controller: _lengthController, + readOnly: true, + textInputAction: TextInputAction.none, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: RawMaterialButton( + splashColor: CFColors.splashLight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: chooseMnemonicLength, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${ref.watch(mnemonicWordCountStateProvider.state).state} words", + style: STextStyles.itemSubtitle12, + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: CFColors.gray3, + ), + ], + ), + ), + ) + ], + ); + } +} + +class RestoreNextButton extends StatelessWidget { + const RestoreNextButton({Key? key, required this.isDesktop, this.onPressed}) + : super(key: key); + + final bool isDesktop; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: isDesktop ? 70 : 0, + ), + child: TextButton( + onPressed: onPressed, + style: onPressed != null + ? CFColors.getPrimaryEnabledButtonColor(context) + : CFColors.getPrimaryDisabledButtonColor(context), + child: Text( + "Next", + style: STextStyles.button, + ), ), ); } 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 4e4871bda..d8067668b 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 @@ -16,6 +16,7 @@ import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widge import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -66,6 +67,7 @@ class RestoreWalletView extends ConsumerStatefulWidget { class _RestoreWalletViewState extends ConsumerState { final _formKey = GlobalKey(); late final int _seedWordCount; + late final bool isDesktop; final HashSet _wordListHashSet = HashSet.from(bip39wordlist.WORDLIST); final ScrollController controller = ScrollController(); @@ -85,13 +87,13 @@ class _RestoreWalletViewState extends ConsumerState { final text = data!.text!.trim(); if (text.isEmpty || _controllers.isEmpty) { - delegate.pasteText(SelectionChangedCause.toolbar); + unawaited(delegate.pasteText(SelectionChangedCause.toolbar)); return; } final words = text.split(" "); if (words.isEmpty) { - delegate.pasteText(SelectionChangedCause.toolbar); + unawaited(delegate.pasteText(SelectionChangedCause.toolbar)); return; } @@ -115,6 +117,7 @@ class _RestoreWalletViewState extends ConsumerState { @override void initState() { _seedWordCount = widget.seedWordsLength; + isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux; textSelectionControls = Platform.isIOS ? CustomCupertinoTextSelectionControls(onPaste: onControlsPaste) @@ -190,11 +193,11 @@ class _RestoreWalletViewState extends ConsumerState { // TODO: do actual check to make sure it is a valid mnemonic for monero if (bip39.validateMnemonic(mnemonic) == false && !(widget.coin == Coin.monero)) { - showFloatingFlushBar( + unawaited(showFloatingFlushBar( type: FlushBarType.warning, message: "Invalid seed phrase!", context: context, - ); + )); } else { if (!Platform.isLinux) Wakelock.enable(); final walletsService = ref.read(walletsServiceChangeNotifierProvider); @@ -206,7 +209,7 @@ class _RestoreWalletViewState extends ConsumerState { ); bool isRestoring = true; // show restoring in progress - showDialog( + unawaited(showDialog( context: context, useSafeArea: false, barrierDismissible: false, @@ -225,7 +228,7 @@ class _RestoreWalletViewState extends ConsumerState { }, ); }, - ); + )); var node = ref .read(nodeServiceChangeNotifierProvider) @@ -233,7 +236,7 @@ class _RestoreWalletViewState extends ConsumerState { if (node == null) { node = DefaultNodes.getNodeFor(widget.coin); - ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor( + await ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor( coin: widget.coin, node: node, ); @@ -282,26 +285,31 @@ class _RestoreWalletViewState extends ConsumerState { .addWallet(walletId: manager.walletId, manager: manager); if (mounted) { - Navigator.of(context).pushNamedAndRemoveUntil( - HomeView.routeName, (route) => false); + if (isDesktop) { + Navigator.of(context) + .popUntil(ModalRoute.withName(DesktopHomeView.routeName)); + } else { + unawaited(Navigator.of(context).pushNamedAndRemoveUntil( + HomeView.routeName, (route) => false)); + } } - showDialog( + await showDialog( context: context, useSafeArea: false, barrierDismissible: true, builder: (context) { return const RestoreSucceededDialog(); }, - ).then( - (_) { - if (!Platform.isLinux) Wakelock.disable(); - // timer.cancel(); - }, ); + if (!Platform.isLinux && !isDesktop) { + await Wakelock.disable(); + } } } catch (e) { - if (!Platform.isLinux) Wakelock.disable(); + if (!Platform.isLinux && !isDesktop) { + await Wakelock.disable(); + } // if (e is HiveError && // e.message == "Box has already been closed.") { @@ -316,7 +324,7 @@ class _RestoreWalletViewState extends ConsumerState { Navigator.pop(context); // show restoring wallet failed dialog - showDialog( + await showDialog( context: context, useSafeArea: false, barrierDismissible: true, @@ -331,7 +339,9 @@ class _RestoreWalletViewState extends ConsumerState { } } - if (!Platform.isLinux) Wakelock.disable(); + if (!Platform.isLinux && !isDesktop) { + await Wakelock.disable(); + } } } } @@ -441,8 +451,71 @@ class _RestoreWalletViewState extends ConsumerState { }); } - controller.animateTo(controller.position.maxScrollExtent, - duration: const Duration(milliseconds: 300), curve: Curves.decelerate); + if (!isDesktop) { + controller.animateTo( + controller.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.decelerate, + ); + } + } + + Future scanMnemonicQr() async { + try { + final qrResult = await scanner.scan(); + + final results = AddressUtils.decodeQRSeedData(qrResult.rawContent); + + Logging.instance.log("scan parsed: $results", level: LogLevel.Info); + + if (results["mnemonic"] != null) { + final list = (results["mnemonic"] as List) + .map((value) => value as String) + .toList(growable: false); + if (list.isNotEmpty) { + _clearAndPopulateMnemonic(list); + Logging.instance.log("mnemonic populated", level: LogLevel.Info); + } else { + Logging.instance + .log("mnemonic failed to populate", level: LogLevel.Info); + } + } + } on PlatformException catch (e) { + // likely failed to get camera permissions + Logging.instance + .log("Restore wallet qr scan failed: $e", level: LogLevel.Warning); + } + } + + Future pasteMnemonic() async { + debugPrint("restoreWalletPasteButton tapped"); + final ClipboardData? data = + await widget.clipboard.getData(Clipboard.kTextPlain); + + if (data?.text != null && data!.text!.isNotEmpty) { + final content = data.text!.trim(); + final list = content.split(" "); + _clearAndPopulateMnemonic(list); + } + } + + Future requestRestore() async { + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return ConfirmRecoveryDialog( + onConfirm: attemptRestore, + ); + }, + ); } @override @@ -479,35 +552,7 @@ class _RestoreWalletViewState extends ConsumerState { height: 20, color: CFColors.stackAccent, ), - onPressed: () async { - try { - final qrResult = await scanner.scan(); - - final results = - AddressUtils.decodeQRSeedData(qrResult.rawContent); - - Logging.instance - .log("scan parsed: $results", level: LogLevel.Info); - - if (results["mnemonic"] != null) { - final list = (results["mnemonic"] as List) - .map((value) => value as String) - .toList(growable: false); - if (list.isNotEmpty) { - _clearAndPopulateMnemonic(list); - Logging.instance - .log("mnemonic populated", level: LogLevel.Info); - } else { - Logging.instance.log("mnemonic failed to populate", - level: LogLevel.Info); - } - } - } on PlatformException catch (e) { - // likely failed to get camera permissions - Logging.instance.log("Restore wallet qr scan failed: $e", - level: LogLevel.Warning); - } - }, + onPressed: scanMnemonicQr, ), ), ), @@ -529,17 +574,7 @@ class _RestoreWalletViewState extends ConsumerState { height: 20, color: CFColors.stackAccent, ), - onPressed: () async { - debugPrint("restoreWalletPasteButton tapped"); - final ClipboardData? data = - await widget.clipboard.getData(Clipboard.kTextPlain); - - if (data?.text != null && data!.text!.isNotEmpty) { - final content = data.text!.trim(); - final list = content.split(" "); - _clearAndPopulateMnemonic(list); - } - }, + onPressed: pasteMnemonic, ), ), ), @@ -641,66 +676,14 @@ class _RestoreWalletViewState extends ConsumerState { ) ], ), - // if (widget.coin == Coin.monero || - // widget.coin == Coin.epicCash) - // Padding( - // padding: const EdgeInsets.only( - // top: 8.0, - // ), - // child: ClipRRect( - // borderRadius: BorderRadius.circular( - // Constants.size.circularBorderRadius, - // ), - // child: TextField( - // key: Key("restoreMnemonicFormField_height"), - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[0-9]*")), - // ], - // keyboardType: - // TextInputType.numberWithOptions(), - // controller: _heightController, - // focusNode: _heightFocusNode, - // style: STextStyles.field, - // decoration: standardInputDecoration( - // "Height", - // _heightFocusNode, - // ), - // ), - // ), - // ), Padding( padding: const EdgeInsets.only( top: 8.0, ), child: TextButton( - style: Theme.of(context) - .textButtonTheme - .style - ?.copyWith( - backgroundColor: - MaterialStateProperty.all( - CFColors.stackAccent, - ), - ), - onPressed: () async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); - - showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return ConfirmRecoveryDialog( - onConfirm: attemptRestore, - ); - }, - ); - }, + style: CFColors.getPrimaryEnabledButtonColor( + context), + onPressed: requestRestore, child: Text( "Restore", style: STextStyles.button, diff --git a/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart b/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart index 99312613b..2bbcd6251 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/my_stack_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_wallets.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -53,7 +54,7 @@ class _MyStackViewState extends ConsumerState { ), ), Expanded( - child: hasWallets ? Container() : const EmptyWallets(), + child: hasWallets ? const MyWallets() : const EmptyWallets(), ), ], ); diff --git a/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart new file mode 100644 index 000000000..01e95259e --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/my_wallets.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_table.dart'; +import 'package:stackwallet/utilities/cfcolors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; + +class MyWallets extends StatefulWidget { + const MyWallets({Key? key}) : super(key: key); + + @override + State createState() => _MyWalletsState(); +} + +class _MyWalletsState extends State { + @override + Widget build(BuildContext context) { + return Container( + color: Colors.greenAccent, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Favorite wallets", + style: STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textFieldActiveSearchIconRight, + ), + ), + const SizedBox( + height: 20, + ), + // TODO favorites grid + Container( + color: Colors.deepPurpleAccent, + height: 210, + ), + + const SizedBox( + height: 40, + ), + + Row( + children: [ + Text( + "All wallets", + style: STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textFieldActiveSearchIconRight, + ), + ), + const Spacer(), + BlueTextButton( + text: "Add new wallet", + onTap: () { + // TODO add wallet + }, + ), + ], + ), + + const SizedBox( + height: 20, + ), + const WalletTable(), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_table.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_table.dart new file mode 100644 index 000000000..4c8af3221 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_table.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/utilities/cfcolors.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class WalletTable extends ConsumerStatefulWidget { + const WalletTable({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _WalletTableState(); +} + +class _WalletTableState extends ConsumerState { + void tapRow(int index) { + print("row $index clicked"); + } + + TableRow getRowForCoin( + int index, + Map>> providersByCoin, + ) { + final coin = providersByCoin.keys.toList(growable: false)[index]; + final walletCount = providersByCoin[coin]!.length; + + final walletCountString = + walletCount == 1 ? "$walletCount wallet" : "$walletCount wallets"; + + return TableRow( + children: [ + GestureDetector( + onTap: () { + tapRow(index); + }, + child: Container( + decoration: BoxDecoration( + color: CFColors.background, + ), + child: Row( + children: [ + // logo/icon + const SizedBox( + width: 10, + ), + Text( + coin.prettyName, + style: STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textDark, + ), + ) + ], + ), + ), + ), + GestureDetector( + onTap: () { + tapRow(index); + }, + child: Container( + decoration: BoxDecoration( + color: CFColors.background, + ), + child: Text( + walletCountString, + style: STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textSubtitle1, + ), + ), + ), + ), + GestureDetector( + onTap: () { + tapRow(index); + }, + child: Container( + decoration: BoxDecoration( + color: CFColors.background, + ), + child: PriceInfoRow(coin: coin), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final providersByCoin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManagerProvidersByCoin())); + + return Table( + border: TableBorder.all(), + columnWidths: const { + 0: FlexColumnWidth(1), + 1: FlexColumnWidth(1.25), + 2: FlexColumnWidth(1.75), + }, + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + for (int i = 0; i < providersByCoin.length; i++) + getRowForCoin(i, providersByCoin) + ]); + } +} + +class PriceInfoRow extends ConsumerWidget { + const PriceInfoRow({Key? key, required this.coin}) : super(key: key); + + final Coin coin; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final tuple = ref.watch(priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + + final currency = ref + .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + + final priceString = Format.localizedStringAsFixed( + value: tuple.item1, + locale: ref.watch(localeServiceChangeNotifierProvider.notifier).locale, + decimalPlaces: 2, + ); + + final double percentChange = tuple.item2; + + var percentChangedColor = CFColors.stackAccent; + if (percentChange > 0) { + percentChangedColor = CFColors.stackGreen; + } else if (percentChange < 0) { + percentChangedColor = CFColors.stackRed; + } + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "$priceString $currency/${coin.ticker}", + style: STextStyles.desktopTextExtraSmall.copyWith( + color: CFColors.textSubtitle1, + ), + ), + Text( + "${percentChange.toStringAsFixed(2)}%", + style: STextStyles.desktopTextExtraSmall.copyWith( + color: percentChangedColor, + ), + ), + ], + ); + } +} diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index df9868cb9..6b274f180 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -170,6 +170,7 @@ abstract class CFColors { static const Color buttonBackPrimaryDisabled = Color(0xFFD7D7D7); static const Color textFieldDefaultSearchIconLeft = Color(0xFFA9ACAC); + static const Color textFieldActiveSearchIconRight = Color(0xFF747778); // button color themes @@ -186,8 +187,7 @@ abstract class CFColors { ), ); - static ButtonStyle? getSecondaryEnabledButtonColor( - BuildContext context) => + static ButtonStyle? getSecondaryEnabledButtonColor(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( CFColors.buttonBackSecondary,