diff --git a/lib/di.dart b/lib/di.dart index 1acdad2dd..8609f90fd 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.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'; @@ -819,6 +820,8 @@ Future setup( getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); + getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get())); + getIt.registerFactoryParam( (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) => IoniaPaymentStatusViewModel( diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index ea23bcd6a..d6f690514 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/main_actions.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.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:cake_wallet/generated/i18n.dart'; @@ -7,6 +9,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/constants.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; @@ -55,6 +58,15 @@ class DashboardPage extends BasePage { @override Widget get endDrawer => MenuWidget(walletViewModel); + @override + Widget? leading(BuildContext context) { + if (MediaQuery.of(context).size.width > ConstValues.minimumDesktopWidth) { + return getIt(); + } + + return null; + } + @override Widget middle(BuildContext context) { return SyncIndicator( @@ -94,7 +106,7 @@ class DashboardPage extends BasePage { return SafeArea( minimum: EdgeInsets.only(bottom: 24), child: LayoutBuilder(builder: (context, constraints) { - if (constraints.maxWidth > 900) { + if (constraints.maxWidth > ConstValues.minimumDesktopWidth) { return DesktopDashboardView(balancePage); } return Column( diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart new file mode 100644 index 000000000..fe98a0af9 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -0,0 +1,163 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; + +class DesktopWalletSelectionDropDown extends StatefulWidget { + final WalletListViewModel walletListViewModel; + + DesktopWalletSelectionDropDown(this.walletListViewModel, {Key? key}) : super(key: key); + + @override + State createState() => _DesktopWalletSelectionDropDownState(); +} + +class _DesktopWalletSelectionDropDownState extends State { + final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); + final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); + final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); + final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); + + final double tileHeight = 60; + + Flushbar? _progressBar; + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + return DropdownButton( + items: widget.walletListViewModel.wallets + .map((wallet) => DropdownMenuItem( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: walletListItemTile(wallet), + ), + value: wallet, + )) + .toList(), + onChanged: (selectedWallet) async { + if (selectedWallet!.isCurrent || !selectedWallet.isEnabled) { + return; + } + + final confirmed = await showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).change_wallet_alert_title, + alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).change, + actionLeftButton: () => Navigator.of(context).pop(false), + actionRightButton: () => Navigator.of(context).pop(true)); + }) ?? + false; + + if (confirmed) { + await _loadWallet(selectedWallet); + } + }, + dropdownColor: themeData.textTheme.bodyText1?.decorationColor, + style: TextStyle(color: themeData.primaryTextTheme.headline6?.color), + selectedItemBuilder: (context) => widget.walletListViewModel.wallets + .map((wallet) => ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: walletListItemTile(wallet), + )) + .toList(), + value: widget.walletListViewModel.wallets.firstWhere((element) => element.isCurrent), + underline: const SizedBox(), + focusColor: Colors.transparent, + ); + } + + Widget walletListItemTile(WalletListItem wallet) { + return Container( + height: tileHeight, + padding: EdgeInsets.symmetric(horizontal: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon, + SizedBox(width: 10), + Flexible( + child: Text( + wallet.name, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme.headline6!.color!, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ) + ], + ), + ); + } + + Image _imageFor({required WalletType type}) { + switch (type) { + case WalletType.bitcoin: + return bitcoinIcon; + case WalletType.monero: + return moneroIcon; + case WalletType.litecoin: + return litecoinIcon; + case WalletType.haven: + return havenIcon; + default: + return nonWalletTypeIcon; + } + } + + Future _loadWallet(WalletListItem wallet) async { + if (await widget.walletListViewModel.checkIfAuthRequired()) { + await Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (!isAuthenticatedSuccessfully) { + return; + } + + try { + auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); + await widget.walletListViewModel.loadWallet(wallet); + auth.hideProgressText(); + auth.close(); + setState(() {}); + } catch (e) { + auth.changeProcessText( + S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); + } + }); + } else { + try { + changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); + await widget.walletListViewModel.loadWallet(wallet); + hideProgressText(); + setState(() {}); + } catch (e) { + changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); + } + } + } + + void changeProcessText(String text) { + _progressBar = createBar(text, duration: null)..show(context); + } + + void hideProgressText() { + _progressBar?.dismiss(); + _progressBar = null; + } +} diff --git a/lib/src/widgets/nav_bar.dart b/lib/src/widgets/nav_bar.dart index f6d933c8b..0e58d5571 100644 --- a/lib/src/widgets/nav_bar.dart +++ b/lib/src/widgets/nav_bar.dart @@ -1,5 +1,5 @@ +import 'package:cake_wallet/utils/constants.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { factory NavBar( @@ -63,10 +63,24 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { final paddingTop = pad / 2; final _paddingBottom = (pad / 2); + if (MediaQuery.of(context).size.width > ConstValues.minimumDesktopWidth) { + return PreferredSize( + preferredSize: Size.fromHeight(height), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (leading != null) Flexible(child: leading!), + if (middle != null) middle!, + if (trailing != null) trailing!, + ], + ), + ); + } + return Container( decoration: decoration ?? BoxDecoration(color: backgroundColor), - padding: - EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop), + padding: EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop), child: CupertinoNavigationBar( leading: leading, automaticallyImplyLeading: false, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 068b087fe..a0755bc37 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,12 +10,13 @@ import cw_monero import devicelocale import flutter_secure_storage_macos import package_info -import path_provider_macos +import path_provider_foundation import platform_device_id import platform_device_id_macos import share_plus_macos import shared_preferences_foundation import url_launcher_macos +import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) @@ -29,4 +30,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 251e96725..03fdb2390 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -27,7 +27,8 @@ PODS: - FlutterMacOS (1.0.0) - package_info (0.0.1): - FlutterMacOS - - path_provider_macos (0.0.1): + - path_provider_foundation (0.0.1): + - Flutter - FlutterMacOS - platform_device_id (0.0.1): - FlutterMacOS @@ -41,6 +42,8 @@ PODS: - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - wakelock_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: - connectivity_macos (from `Flutter/ephemeral/.symlinks/plugins/connectivity_macos/macos`) @@ -50,12 +53,13 @@ DEPENDENCIES: - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) - - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) - platform_device_id (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos`) - platform_device_id_macos (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id_macos/macos`) - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`) SPEC REPOS: trunk: @@ -75,8 +79,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral package_info: :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos - path_provider_macos: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos platform_device_id: :path: Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos platform_device_id_macos: @@ -87,6 +91,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + wakelock_macos: + :path: Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos SPEC CHECKSUMS: connectivity_macos: 5dae6ee11d320fac7c05f0d08bd08fc32b5514d9 @@ -96,13 +102,14 @@ SPEC CHECKSUMS: flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 - path_provider_macos: 05fb0ef0cedf3e5bd179b9e41a638682b37133ea + path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 platform_device_id: 3e414428f45df149bbbfb623e2c0ca27c545b763 platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 PODFILE CHECKSUM: 95c2abf1742d9564d190610743847d385992e6cc