diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart new file mode 100644 index 000000000..fbbf8c771 --- /dev/null +++ b/lib/entities/main_actions.dart @@ -0,0 +1,123 @@ +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class MainActions { + final String Function(BuildContext context) name; + final String image; + + final bool Function(DashboardViewModel viewModel)? isEnabled; + final bool Function(DashboardViewModel viewModel)? canShow; + final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; + + MainActions._({ + required this.name, + required this.image, + this.isEnabled, + this.canShow, + required this.onTap, + }); + + static List all = [ + buyAction, + receiveAction, + exchangeAction, + sendAction, + sellAction, + ]; + + static MainActions buyAction = MainActions._( + name: (context) => S.of(context).buy, + image: 'assets/images/buy.png', + isEnabled: (viewModel) => viewModel.isEnabledBuyAction, + canShow: (viewModel) => viewModel.hasBuyAction, + onTap: (BuildContext context, DashboardViewModel viewModel) async { + final walletType = viewModel.type; + + switch (walletType) { + case WalletType.bitcoin: + Navigator.of(context).pushNamed(Routes.onramperPage); + break; + case WalletType.litecoin: + Navigator.of(context).pushNamed(Routes.onramperPage); + break; + default: + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).buy, + alertContent: S.of(context).buy_alert_content, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + }, + ); + + static MainActions receiveAction = MainActions._( + name: (context) => S.of(context).receive, + image: 'assets/images/received.png', + onTap: (BuildContext context, DashboardViewModel viewModel) async { + Navigator.pushNamed(context, Routes.addressPage); + }, + ); + + static MainActions exchangeAction = MainActions._( + name: (context) => S.of(context).exchange, + image: 'assets/images/transfer.png', + isEnabled: (viewModel) => viewModel.isEnabledExchangeAction, + canShow: (viewModel) => viewModel.hasExchangeAction, + onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (viewModel.isEnabledExchangeAction) { + await Navigator.of(context).pushNamed(Routes.exchange); + } + }, + ); + + static MainActions sendAction = MainActions._( + name: (context) => S.of(context).send, + image: 'assets/images/upload.png', + onTap: (BuildContext context, DashboardViewModel viewModel) async { + Navigator.pushNamed(context, Routes.send); + }, + ); + + static MainActions sellAction = MainActions._( + name: (context) => S.of(context).sell, + image: 'assets/images/sell.png', + isEnabled: (viewModel) => viewModel.isEnabledSellAction, + canShow: (viewModel) => viewModel.hasSellAction, + onTap: (BuildContext context, DashboardViewModel viewModel) async { + final walletType = viewModel.type; + + switch (walletType) { + case WalletType.bitcoin: + final moonPaySellProvider = MoonPaySellProvider(); + final uri = await moonPaySellProvider.requestUrl( + currency: viewModel.wallet.currency, + refundWalletAddress: viewModel.wallet.walletAddresses.address, + ); + await launch(uri); + break; + default: + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).sell, + alertContent: S.of(context).sell_alert_content, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }, + ); + } + }, + ); +} diff --git a/lib/router.dart b/lib/router.dart index 896ddd99b..b5d1cdd9f 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; @@ -26,6 +27,7 @@ import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; @@ -511,6 +513,10 @@ Route createRoute(RouteSettings settings) { getIt.get(param1: type), )); + case Routes.desktop_actions: + return CupertinoPageRoute( + builder: (_) => DesktopDashboardActions(getIt())); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 5ea250310..293864a23 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -82,4 +82,5 @@ class Routes { static const displaySettingsPage = '/display_settings_page'; static const otherSettingsPage = '/other_settings_page'; static const advancedPrivacySettings = '/advanced_privacy_settings'; + static const desktop_actions = '/desktop_actions'; } diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index c21365aa4..3fe54809b 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:cake_wallet/entities/main_actions.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_view.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -30,8 +32,9 @@ class DashboardPage extends BasePage { required this.walletViewModel, required this.addressListViewModel, }); + final BalancePage balancePage; - + @override Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @@ -58,15 +61,15 @@ class DashboardPage extends BasePage { @override Widget middle(BuildContext context) { - return SyncIndicator(dashboardViewModel: walletViewModel, - onTap: () => Navigator.of(context, rootNavigator: true) - .pushNamed(Routes.connectionSync)); + return SyncIndicator( + dashboardViewModel: walletViewModel, + onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync)); } @override Widget trailing(BuildContext context) { final menuButton = Image.asset('assets/images/menu.png', - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!); + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!); return Container( alignment: Alignment.centerRight, @@ -93,125 +96,97 @@ class DashboardPage extends BasePage { final sendImage = Image.asset('assets/images/upload.png', height: 24, width: 24, - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!); + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!); final receiveImage = Image.asset('assets/images/received.png', height: 24, width: 24, - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!); + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!); _setEffects(context); return SafeArea( - minimum: EdgeInsets.only(bottom: 24), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: PageView.builder( - controller: controller, - itemCount: pages.length, - itemBuilder: (context, index) => pages[index])), - Padding( - padding: EdgeInsets.only(bottom: 24, top: 10), - child: SmoothPageIndicator( - controller: controller, - count: pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).indicatorColor, - activeDotColor: Theme.of(context) - .accentTextTheme! - .headline4! - .backgroundColor!), - )), - Observer(builder: (_) { - return ClipRect( - child:Container( - margin: const EdgeInsets.only(left: 16, right: 16), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.0), - border: Border.all(color: currentTheme.type == ThemeType.bright ? Color.fromRGBO(255, 255, 255, 0.2): Colors.transparent, width: 1, ), - color:Theme.of(context).textTheme!.headline6!.backgroundColor!), - child: Container( - padding: EdgeInsets.only(left: 32, right: 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (walletViewModel.hasBuyAction) - ActionButton( - image: Image.asset('assets/images/buy.png', - height: 24, - width: 24, - color: !walletViewModel.isEnabledBuyAction - ? Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor! - : Theme.of(context).accentTextTheme!.headline2!.backgroundColor!), - title: S.of(context).buy, - onClick: () async => await _onClickBuyButton(context), - textColor: !walletViewModel.isEnabledBuyAction - ? Theme.of(context) + minimum: EdgeInsets.only(bottom: 24), + child: LayoutBuilder(builder: (context, constraints) { + if (constraints.maxWidth > 900) { + return DesktopDashboardView(balancePage); + } + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: PageView.builder( + controller: controller, + itemCount: pages.length, + itemBuilder: (context, index) => pages[index])), + Padding( + padding: EdgeInsets.only(bottom: 24, top: 10), + child: SmoothPageIndicator( + controller: controller, + count: pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).indicatorColor, + activeDotColor: Theme.of(context) .accentTextTheme! - .headline3! - .backgroundColor! - : null), - ActionButton( - image: receiveImage, - title: S.of(context).receive, - route: Routes.addressPage), - if (walletViewModel.hasExchangeAction) - ActionButton( - image: Image.asset('assets/images/transfer.png', - height: 24, - width: 24, - color: !walletViewModel.isEnabledExchangeAction - ? Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor! - : Theme.of(context).accentTextTheme!.headline2!.backgroundColor!), - title: S.of(context).exchange, - onClick: () async => _onClickExchangeButton(context), - textColor: !walletViewModel.isEnabledExchangeAction - ? Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor! - : null), - ActionButton( - image: sendImage, - title: S.of(context).send, - route: Routes.send), - if (walletViewModel.hasSellAction) - ActionButton( - image: Image.asset('assets/images/sell.png', - height: 24, - width: 24, - color: !walletViewModel.isEnabledSellAction - ? Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor! - : Theme.of(context).accentTextTheme!.headline2!.backgroundColor!), - title: S.of(context).sell, - onClick: () async => await _onClickSellButton(context), - textColor: !walletViewModel.isEnabledSellAction - ? Theme.of(context) - .accentTextTheme! - .headline3! - .backgroundColor! - : null), - ], - ),), - ),),); - }), - - ], - )); + .headline4! + .backgroundColor!), + )), + Observer(builder: (_) { + return ClipRect( + child: Container( + margin: const EdgeInsets.only(left: 16, right: 16), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.0), + border: Border.all( + color: currentTheme.type == ThemeType.bright + ? Color.fromRGBO(255, 255, 255, 0.2) + : Colors.transparent, + width: 1, + ), + color: Theme.of(context).textTheme.headline6!.backgroundColor!, + ), + child: Container( + padding: EdgeInsets.only(left: 32, right: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: MainActions.all + .where((element) => element.canShow?.call(walletViewModel) ?? true) + .map((action) => ActionButton( + image: Image.asset(action.image, + height: 24, + width: 24, + color: action.isEnabled?.call(walletViewModel) ?? true + ? Theme.of(context) + .accentTextTheme + .headline2! + .backgroundColor! + : Theme.of(context) + .accentTextTheme + .headline3! + .backgroundColor!), + title: action.name(context), + onClick: () async => + await action.onTap(context, walletViewModel), + textColor: action.isEnabled?.call(walletViewModel) ?? true + ? null + : Theme.of(context) + .accentTextTheme + .headline3! + .backgroundColor!, + )) + .toList(), + ), + ), + ), + ), + ); + }), + ], + ); + })); } void _setEffects(BuildContext context) async { @@ -234,8 +209,7 @@ class DashboardPage extends BasePage { builder: (BuildContext context) { return AlertWithOneAction( alertTitle: S.of(context).pre_seed_title, - alertContent: - S.of(context).outdated_electrum_wallet_description, + alertContent: S.of(context).outdated_electrum_wallet_description, buttonText: S.of(context).understand, buttonAction: () => Navigator.of(context).pop()); }); @@ -297,8 +271,7 @@ class DashboardPage extends BasePage { final moonPaySellProvider = MoonPaySellProvider(); final uri = await moonPaySellProvider.requestUrl( currency: walletViewModel.wallet.currency, - refundWalletAddress: - walletViewModel.wallet.walletAddresses.address); + refundWalletAddress: walletViewModel.wallet.walletAddresses.address); await launch(uri); break; default: diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_action_button.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_action_button.dart new file mode 100644 index 000000000..5316c194d --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_action_button.dart @@ -0,0 +1,66 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; + +class DesktopActionButton extends StatelessWidget { + final String image; + final String title; + final bool canShow; + final bool isEnabled; + final Function() onTap; + + const DesktopActionButton({ + Key? key, + required this.title, + required this.image, + required this.onTap, + bool? canShow, + bool? isEnabled, + }) : this.isEnabled = isEnabled ?? true, + this.canShow = canShow ?? true, + super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 8), + child: Container( + padding: EdgeInsets.symmetric(vertical: 25), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.0), + color: Theme.of(context).textTheme.headline6!.backgroundColor!, + ), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + image, + height: 30, + width: 30, + color: isEnabled + ? Theme.of(context).accentTextTheme.headline2!.backgroundColor! + : Theme.of(context).accentTextTheme.headline3!.backgroundColor!, + ), + const SizedBox(width: 10), + AutoSizeText( + title, + style: TextStyle( + fontSize: 24, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + color: isEnabled + ? Theme.of(context).accentTextTheme.headline2!.backgroundColor! + : null, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.center, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart new file mode 100644 index 000000000..3e6b8afd9 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart @@ -0,0 +1,71 @@ +import 'package:cake_wallet/entities/main_actions.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:flutter/material.dart'; + +class DesktopDashboardActions extends StatelessWidget { + final DashboardViewModel dashboardViewModel; + + const DesktopDashboardActions(this.dashboardViewModel, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const SizedBox(height: 16), + DesktopActionButton( + title: MainActions.exchangeAction.name(context), + image: MainActions.exchangeAction.image, + canShow: MainActions.exchangeAction.canShow?.call(dashboardViewModel), + isEnabled: MainActions.exchangeAction.isEnabled?.call(dashboardViewModel), + onTap: () async => await MainActions.exchangeAction.onTap(context, dashboardViewModel), + ), + Row( + children: [ + Expanded( + child: DesktopActionButton( + title: MainActions.receiveAction.name(context), + image: MainActions.receiveAction.image, + canShow: MainActions.receiveAction.canShow?.call(dashboardViewModel), + isEnabled: MainActions.receiveAction.isEnabled?.call(dashboardViewModel), + onTap: () async => + await MainActions.receiveAction.onTap(context, dashboardViewModel), + ), + ), + Expanded( + child: DesktopActionButton( + title: MainActions.sendAction.name(context), + image: MainActions.sendAction.image, + canShow: MainActions.sendAction.canShow?.call(dashboardViewModel), + isEnabled: MainActions.sendAction.isEnabled?.call(dashboardViewModel), + onTap: () async => await MainActions.sendAction.onTap(context, dashboardViewModel), + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: DesktopActionButton( + title: MainActions.buyAction.name(context), + image: MainActions.buyAction.image, + canShow: MainActions.buyAction.canShow?.call(dashboardViewModel), + isEnabled: MainActions.buyAction.isEnabled?.call(dashboardViewModel), + onTap: () async => await MainActions.buyAction.onTap(context, dashboardViewModel), + ), + ), + Expanded( + child: DesktopActionButton( + title: MainActions.sellAction.name(context), + image: MainActions.sellAction.image, + canShow: MainActions.sellAction.canShow?.call(dashboardViewModel), + isEnabled: MainActions.sellAction.isEnabled?.call(dashboardViewModel), + onTap: () async => await MainActions.sellAction.onTap(context, dashboardViewModel), + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_view.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_view.dart new file mode 100644 index 000000000..dccf6c441 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_view.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/router.dart' as Router; + +class DesktopDashboardView extends StatelessWidget { + final Widget balancePage; + + const DesktopDashboardView(this.balancePage, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: balancePage, + ), + Expanded( + flex: 5, + child: Navigator( + initialRoute: Routes.desktop_actions, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { + return [navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!]; + }, + ), + ), + ], + ); + } +}